# Fumadocs (Framework Mode, Astro): 無頭式
URL: /zh-TW/docs/astro/integrations/story/headless
Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/(framework)/[framework]/integrations/story/headless.zh-TW.mdx

替換 stories 的 UI，或基於無頭式層自行建構 UI。

## 概覽 [#overview]

`@fumadocs/story` 匯出了 `<StoryProvider />`、`useStory()` 與 `useStoryArgs()`，可在不含 UI 的情況下提供元件情境的狀態：

* 所選的變體，以及其對應的預設組合
* 已渲染元件的引數，並經過防抖 (debounce) 處理，因此輸入時不會每按一個鍵就重新算繪元件

內建面板就是以這些 API 建構而成，您也可以改用自己實作的面板。

## 自訂面板 [#your-own-panel]

`defineStoryFactory()` 可接受一個 `WithControl` 元件，並以它取代內建面板進行算繪。

```tsx title="lib/story.tsx"
import { defineStoryFactory } from '@fumadocs/story/vite/client';
import { StoryPanel } from '@/components/story-panel';

export const { defineStory } = defineStoryFactory({ WithControl: StoryPanel });
```

此元件會接收元件情境的預設組合，以及要算繪的元件。

```tsx title="components/story-panel.tsx"
'use client';
import { StoryProvider, useStory, useStoryArgs, type WithControlProps } from '@fumadocs/story';

export function StoryPanel(props: WithControlProps) {
  return (
    <StoryProvider {...props}>
      <Content />
    </StoryProvider>
  );
}

function Content() {
  const { presets, variant, setVariant, preset, Component } = useStory();
  const args = useStoryArgs();

  return (
    <div>
      <select value={variant} onChange={(e) => setVariant(e.target.value)}>
        {presets.map((item) => (
          <option key={item.variant} value={item.variant}>
            {item.variant}
          </option>
        ))}
      </select>
      <Component {...args} />
      <pre>{JSON.stringify(preset?.controls, null, 2)}</pre>
    </div>
  );
}
```

| Hook              | 說明                                                                           |
| ----------------- | ---------------------------------------------------------------------------- |
| `useStory()`      | 預設組合、目前選取的 `variant` 與 `setVariant`，以及 `Component` 和 `frame`。                |
| `useStoryArgs()`  | 已渲染元件的引數，會隨控制項同步更新。                                                          |
| `useStoryFrame()` | [`frame`](/docs/astro/integrations/story#frame) 的 iframe 所用的 ref，負責將引數傳送給該 iframe。 |

控制項透過 [`@fumari/stf`](https://github.com/fuma-nama/fumadocs/tree/dev/packages/stf) 編輯，也就是 `<StoryProvider />` 所設定的表單引擎。`preset.controls` 是元件 props 的[型別樹](/docs/astro/integrations/story#controls)。

<Callout title="從內建面板開始">
要撰寫完整的控制面板，就必須處理型別樹中的每一種節點。建議您安裝下方的內建
面板，再加以修改。
</Callout>

## 安裝 UI [#install-the-ui]

整個面板，包括其中的引數控制項：

```npm
npx @fumadocs/cli add story/controls
```

```tsx title="lib/story.tsx"
import { defineStoryFactory } from '@fumadocs/story/vite/client';
import { WithControl } from '@/components/story';

export const { defineStory } = defineStoryFactory({ WithControl });
```