# 布局

## 使用方法

`Layout` 最多排五个区域：页眉、起始边、正文、结束边、页脚。默认插槽是正文，其余是具名插槽。页面外框交给 `AppShell`；`Layout` 用在页面内部、卡片或对话框里。内容只是一行或一列时用 `HStack` 或 `VStack`。

```vue
<script setup lang="ts">
import { Layout, LayoutContent, LayoutHeader } from '@astryx-vue/core/Layout'
</script>

<template>
  <Layout>
    <template #header>
      <LayoutHeader has-divider>
        订单
      </LayoutHeader>
    </template>
    <LayoutContent>
      选择一笔订单。
    </LayoutContent>
  </Layout>
</template>
```

`height` 取 `fill` 或 `auto`。`fill` 撑满容器，正文在中间滚动；`auto` 随内容长高。`contentWidth` 限制正文宽度并居中，分隔线仍然拉满。百分比、`fit-content`、`max-content`、`min-content` 或裸 `var()` 走约束宽度。`padding` 只作用于这一层，不会传给嵌套的 `Layout`。页眉或页脚没有写 `hasDivider` 时，用 `defaultHasDividers`。

`LayoutHeader`、`LayoutFooter`、`LayoutContent`、`LayoutPanel` 是四个区域组件。面板的 `width` 传数字时按像素。宽度由 `ResizeHandle` 控制时，把 `useResizable` 返回的 `props` 传给 `resizable`。

### 结构

- **布局容器**: 摆放页眉、起始侧、内容、末尾侧和页脚这些插槽的通用布局。
- **页眉**: 使用方提供的可选 LayoutHeader，一般放在 header 插槽。
- **面板**: 使用方放在起始侧或末尾侧插槽里的可选 LayoutPanel。
- **内容区**: 使用方放在 content 插槽里的可选 LayoutContent。
- **页脚**: 使用方提供的可选 LayoutFooter，一般放在 footer 插槽。

## 最佳实践

| 指引 | 实践 |
| --- | --- |
| 推荐 | 页眉、侧栏或页脚需要固定，正文单独滚动时用 Layout。 |
| 推荐 | 滚动放在 `LayoutContent` 上，页眉和页脚留在外面。 |
| 避免 | 用 Layout 当应用外框。顶部和侧边导航用 AppShell。 |
| 避免 | 面板分隔线和拖拽分隔条的线画在同一条边上。 |

## 示例

### 基础卡片

卡片包含页眉、可滚动的正文，以及放着操作按钮的页脚。

```vue
<script setup lang="ts">
import { Button } from '@astryx-vue/core/Button'
import { Card } from '@astryx-vue/core/Card'
import { Layout, LayoutContent, LayoutFooter, LayoutHeader } from '@astryx-vue/core/Layout'
import { HStack, VStack } from '@astryx-vue/core/Stack'
import { Text } from '@astryx-vue/core/Text'
</script>

<template>
  <Card :height="300" width="100%" style="max-width: 400px">
    <Layout>
      <template #header>
        <LayoutHeader has-divider>
          <p class="layout-title">
            Card Title
          </p>
        </LayoutHeader>
      </template>
      <LayoutContent>
        <VStack :gap="3">
          <Text type="body">
            This is a basic card layout with a header, scrollable content area, and footer. The layout automatically handles padding and spacing between sections.
          </Text>
          <Text type="body">
            When content exceeds the available height, the content area scrolls independently while the header and footer stay fixed in place.
          </Text>
          <Text type="body">
            This pattern works well for modal dialogs, detail panels, and any card where the amount of content is unpredictable.
          </Text>
          <Text type="body">
            The dividers between header, content, and footer provide clear visual boundaries between the fixed and scrollable regions.
          </Text>
        </VStack>
      </LayoutContent>
      <template #footer>
        <LayoutFooter has-divider>
          <HStack :gap="2" h-align="end">
            <Button label="Cancel" variant="secondary" />
            <Button label="Save" variant="primary" />
          </HStack>
        </LayoutFooter>
      </template>
    </Layout>
  </Card>
</template>
```

### 仅正文

卡片里只有正文，没有页眉和页脚。

```vue
<script setup lang="ts">
import { Card } from '@astryx-vue/core/Card'
import { Layout, LayoutContent } from '@astryx-vue/core/Layout'
import { VStack } from '@astryx-vue/core/Stack'
import { Text } from '@astryx-vue/core/Text'
</script>

<template>
  <Card width="100%" style="max-width: 400px">
    <Layout>
      <LayoutContent>
        <VStack :gap="3">
          <p class="layout-title">
            Simple Content
          </p>
          <Text type="body">
            A layout can have just content without header or footer. This is useful for simple cards or content blocks that don't need structured sections.
          </Text>
        </VStack>
      </LayoutContent>
    </Layout>
  </Card>
</template>
```

### 内容宽度

contentWidth 把文字约束并居中，分隔线仍然拉满。

```vue
<script setup lang="ts">
import { Button } from '@astryx-vue/core/Button'
import { Layout, LayoutContent, LayoutFooter, LayoutHeader } from '@astryx-vue/core/Layout'
import { HStack, VStack } from '@astryx-vue/core/Stack'
import { Text } from '@astryx-vue/core/Text'
</script>

<template>
  <Layout :content-width="360" height="fill">
    <template #header>
      <LayoutHeader has-divider>
        <p class="layout-title">
          Centered Form
        </p>
      </LayoutHeader>
    </template>
    <LayoutContent>
      <VStack :gap="3">
        <Text type="body">
          The contentWidth prop constrains content to a maximum width and centers it within the layout. Dividers remain full-bleed while content stays narrow and readable.
        </Text>
        <Text color="secondary" type="body">
          Common widths: 640 for forms, 960 for content pages.
        </Text>
      </VStack>
    </LayoutContent>
    <template #footer>
      <LayoutFooter has-divider>
        <HStack :gap="2" h-align="end">
          <Button label="Cancel" variant="secondary" />
          <Button label="Submit" variant="primary" />
        </HStack>
      </LayoutFooter>
    </template>
  </Layout>
</template>
```

### 双侧面板

文件浏览器：左侧是文件夹，中间是文件，右侧是详情。

```vue
<script setup lang="ts">
import { Card } from '@astryx-vue/core/Card'
import { Layout, LayoutContent, LayoutHeader, LayoutPanel } from '@astryx-vue/core/Layout'
import { List, ListItem } from '@astryx-vue/core/List'
import { VStack } from '@astryx-vue/core/Stack'
import { Text } from '@astryx-vue/core/Text'
</script>

<template>
  <Card width="100%" style="max-width: 500px">
    <Layout>
      <template #header>
        <LayoutHeader has-divider>
          <p class="layout-title">
            File Browser
          </p>
        </LayoutHeader>
      </template>
      <template #start>
        <LayoutPanel :width="120" has-divider>
          <VStack :gap="1">
            <Text color="secondary" type="label">
              Folders
            </Text>
            <List>
              <ListItem label="Documents" />
              <ListItem is-selected label="Projects" />
              <ListItem label="Downloads" />
            </List>
          </VStack>
        </LayoutPanel>
      </template>
      <LayoutContent>
        <VStack :gap="2">
          <Text color="secondary" type="label">
            Files
          </Text>
          <Card variant="muted">
            <Text type="body">
              Select a folder to view its contents
            </Text>
          </Card>
        </VStack>
      </LayoutContent>
      <template #end>
        <LayoutPanel :width="120" has-divider>
          <VStack :gap="2">
            <Text color="secondary" type="label">
              Details
            </Text>
            <Text type="body">
              Select a file to view details
            </Text>
          </VStack>
        </LayoutPanel>
      </template>
    </Layout>
  </Card>
</template>
```

### 通栏正文

正文没有内边距，一直铺到边缘，适合表格或图片。

```vue
<script setup lang="ts">
import { Button } from '@astryx-vue/core/Button'
import { Card } from '@astryx-vue/core/Card'
import { Layout, LayoutContent, LayoutFooter, LayoutHeader } from '@astryx-vue/core/Layout'
import { Section } from '@astryx-vue/core/Section'
import { HStack } from '@astryx-vue/core/Stack'
import { Text } from '@astryx-vue/core/Text'
</script>

<template>
  <Card width="100%" style="max-width: 400px">
    <Layout>
      <template #header>
        <LayoutHeader has-divider>
          <p class="layout-title">
            Full Bleed Example
          </p>
        </LayoutHeader>
      </template>
      <LayoutContent :padding="0">
        <Section variant="muted">
          <Text type="body">
            Section automatically escapes the parent container padding to fill edge-to-edge. Useful for wash backgrounds, tables, or images that need to span the full width.
          </Text>
        </Section>
      </LayoutContent>
      <template #footer>
        <LayoutFooter has-divider>
          <HStack :gap="2" h-align="end">
            <Button label="Close" variant="secondary" />
          </HStack>
        </LayoutFooter>
      </template>
    </Layout>
  </Card>
</template>
```

### 侧栏导航

设置页有导航侧栏、正文、页眉和页脚。

```vue
<script setup lang="ts">
import { Button } from '@astryx-vue/core/Button'
import { Card } from '@astryx-vue/core/Card'
import { Layout, LayoutContent, LayoutFooter, LayoutHeader, LayoutPanel } from '@astryx-vue/core/Layout'
import { List, ListItem } from '@astryx-vue/core/List'
import { HStack, VStack } from '@astryx-vue/core/Stack'
import { Text } from '@astryx-vue/core/Text'
</script>

<template>
  <Card width="100%" style="max-width: 500px">
    <Layout>
      <template #header>
        <LayoutHeader has-divider>
          <p class="layout-title">
            Settings
          </p>
        </LayoutHeader>
      </template>
      <template #start>
        <LayoutPanel :width="150" has-divider label="Settings sections" role="navigation">
          <List>
            <ListItem is-selected label="General" />
            <ListItem label="Account" />
            <ListItem label="Privacy" />
            <ListItem label="Notifications" />
            <ListItem label="Security" />
          </List>
        </LayoutPanel>
      </template>
      <LayoutContent>
        <VStack :gap="3">
          <p class="layout-title">
            General Settings
          </p>
          <Text type="body">
            Configure your general preferences here. The sidebar navigation allows you to switch between different settings sections.
          </Text>
        </VStack>
      </LayoutContent>
      <template #footer>
        <LayoutFooter has-divider>
          <HStack :gap="2" h-align="end">
            <Button label="Reset" variant="secondary" />
            <Button label="Save Changes" variant="primary" />
          </HStack>
        </LayoutFooter>
      </template>
    </Layout>
  </Card>
</template>
```

## Layout Props

| 名称 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `contentWidth` | `number \| string` | - | 对齐内容的最大宽度。分隔线仍然拉满。数字按像素处理。百分比、`fit-content`、`max-content`、`min-content` 或裸 `var()` 走约束宽度。 |
| `height` | `'fill' \| 'auto'` | `'fill'` | `fill` 撑满容器，正文在中间滚动。`auto` 随内容长高。 |
| `padding` | `SpacingStep` | - | 只作用于这一层的外缘间距，不传给嵌套的布局。`0` 表示通栏。 |
| `defaultHasDividers` | `boolean` | - | 页眉或页脚没有写 `hasDivider` 时用的默认值。 |

`SpacingStep` 的取值是 `0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10`。

## Layout 插槽

| 名称 | 说明 |
| --- | --- |
| `default` | 正文区域。这里放 `LayoutContent`。 |
| `header` | 顶部区域。这里放 `LayoutHeader`。 |
| `start` | 行起始边。这里放 `LayoutPanel`。面板可拖拽时，旁边再放 `ResizeHandle`。 |
| `end` | 行结束边。 |
| `footer` | 底部区域。这里放 `LayoutFooter`。 |

## Layout Header Props

| 名称 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `hasDivider` | `boolean` | 继承 | 底边分隔线。不传时用 `defaultHasDividers`；布局也没写时是 false。 |
| `height` | `number \| string` | - | 高度。数字按像素处理，字符串原样使用。 |
| `padding` | `SpacingStep` | - | 内边距。`0` 表示内容自己管边距。 |
| `paddingBlockEnd` | `SpacingStep` | - | 只改底边内边距，方便标签下划线和分隔线贴住。 |
| `label` | `string` | - | 设置了 `role` 时的无障碍名称。 |
| `role` | `AriaRole` | - | ARIA 地标角色。 |

`LayoutHeader` 有一个默认插槽，放页眉内容。

## Layout Footer Props

`LayoutFooter` 接受 <code>hasDivider</code>、<code>height</code>、<code>padding</code>、<code>label</code>、<code>role</code>，含义和页眉相同。<code>hasDivider</code> 画在顶边。它有一个默认插槽。

## Layout Content Props

| 名称 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `padding` | `SpacingStep` | - | 内边距。`0` 让 `Section` 这类子元素铺到边缘。 |
| `isScrollable` | `boolean` | `true` | 内容超出时在这块里滚动。 |
| `label` | `string` | - | 设置了 `role` 时的无障碍名称。 |
| `role` | `AriaRole` | - | ARIA 地标角色。 |

`LayoutContent` 有一个默认插槽。

## Layout Panel Props

| 名称 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `hasDivider` | `boolean` | `false` | 朝向正文的那条边。旁边的 `ResizeHandle` 已经画线时不要再开。 |
| `padding` | `SpacingStep` | - | 内边距。不传、而且没有分隔线时，朝向正文的外边距会收起来。 |
| `isScrollable` | `boolean` | `true` | 内容超出时在面板里滚动。 |
| `width` | `number \| string` | - | 宽度。数字按像素处理。传了 `resizable` 时以钩子的尺寸为准。 |
| `resizable` | `LayoutResizable` | - | `useResizable` 返回的 `props`。面板宽度跟着 `_size`；对象里还带着这块区域的轴 `_direction`（`'horizontal' \| 'vertical'`），要和旁边的 `ResizeHandle` 一致。 |
| `label` | `string` | - | 设置了 `role` 时的无障碍名称。 |
| `role` | `AriaRole` | - | ARIA 地标角色，例如 `navigation`。 |

`LayoutPanel` 有一个默认插槽。

## 原生属性

每一块的根元素都是 `<div>`。

以下内容落到各自的根上：

- HTML 属性。
- `aria-*` 属性。
- `data-*` 属性。
- `class`。
- `style`。

`class` 会合并。`Layout` 写 `data-height`。
