Skip to content

Commit ae2bd5e

Browse files
feat(Splitter): new component (#6670)
1 parent 3d2129d commit ae2bd5e

19 files changed

Lines changed: 815 additions & 0 deletions

File tree

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<script setup lang="ts">
2+
import type { SplitterItem } from '@nuxt/ui'
3+
4+
const items: SplitterItem[] = [
5+
{ slot: 'sidebar', sizeUnit: 'px', minSize: 150, defaultSize: 250, collapsible: true, collapsedSize: 48, class: 'bg-elevated/50 border border-default rounded-xl' },
6+
{ slot: 'main', class: 'bg-elevated/50 border border-default rounded-xl items-center justify-center text-muted font-medium' }
7+
]
8+
</script>
9+
10+
<template>
11+
<div class="w-full h-96">
12+
<USplitter id="splitter-collapsible-example" :items="items">
13+
<template #sidebar="{ collapsed, collapse, expand }">
14+
<div class="flex-1 flex items-center justify-center p-2">
15+
<UButton
16+
:icon="collapsed ? 'i-lucide-panel-left-open' : 'i-lucide-panel-left-close'"
17+
:label="collapsed ? undefined : 'Collapse'"
18+
:aria-label="collapsed ? 'Expand' : undefined"
19+
color="neutral"
20+
variant="subtle"
21+
@click="collapsed ? expand() : collapse()"
22+
/>
23+
</div>
24+
</template>
25+
26+
<template #main>
27+
Main
28+
</template>
29+
</USplitter>
30+
</div>
31+
</template>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<script setup lang="ts">
2+
import type { SplitterItem } from '@nuxt/ui'
3+
4+
const items: SplitterItem[] = [
5+
{ slot: 'left', minSize: 20, defaultSize: 30, class: 'items-center justify-center text-muted font-medium' },
6+
{ slot: 'right', defaultSize: 70, class: 'items-center justify-center text-muted font-medium' }
7+
]
8+
</script>
9+
10+
<template>
11+
<div class="w-full h-96">
12+
<USplitter id="splitter-custom-handle-example" :items="items" :ui="{ handle: 'data-[orientation=horizontal]:w-px data-[orientation=vertical]:h-px bg-border transition-colors data-[state=hover]:bg-primary data-[state=drag]:bg-primary' }" class="rounded-lg border border-default overflow-hidden">
13+
<template #left>
14+
Left
15+
</template>
16+
17+
<template #right>
18+
Right
19+
</template>
20+
</USplitter>
21+
</div>
22+
</template>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<script setup lang="ts">
2+
import type { SplitterItem } from '@nuxt/ui'
3+
4+
const card = 'bg-elevated/50 border border-default rounded-xl items-center justify-center text-muted font-medium'
5+
6+
const items: SplitterItem[] = [
7+
{ slot: 'left', minSize: 15, defaultSize: 25, class: card },
8+
{ slot: 'main', minSize: 30, defaultSize: 50, class: card },
9+
{ slot: 'right', minSize: 15, defaultSize: 25, class: card }
10+
]
11+
</script>
12+
13+
<template>
14+
<div class="w-full h-96">
15+
<USplitter id="splitter-example" :items="items">
16+
<template #left>
17+
Left
18+
</template>
19+
20+
<template #main>
21+
Main
22+
</template>
23+
24+
<template #right>
25+
Right
26+
</template>
27+
</USplitter>
28+
</div>
29+
</template>
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<script setup lang="ts">
2+
import type { SplitterItem } from '@nuxt/ui'
3+
4+
const card = 'bg-elevated/50 border border-default rounded-xl items-center justify-center text-muted font-medium'
5+
6+
const items: SplitterItem[] = [
7+
{ slot: 'left', minSize: 20, class: card },
8+
{ slot: 'right', minSize: 20 }
9+
]
10+
11+
const nested: SplitterItem[] = [
12+
{ slot: 'top', minSize: 20, class: card },
13+
{ slot: 'bottom', minSize: 20, class: card }
14+
]
15+
</script>
16+
17+
<template>
18+
<div class="w-full h-96">
19+
<USplitter id="splitter-nested-example" :items="items">
20+
<template #left>
21+
Left
22+
</template>
23+
24+
<template #right>
25+
<USplitter id="splitter-nested-example-inner" orientation="vertical" :items="nested">
26+
<template #top>
27+
Top
28+
</template>
29+
30+
<template #bottom>
31+
Bottom
32+
</template>
33+
</USplitter>
34+
</template>
35+
</USplitter>
36+
</div>
37+
</template>
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
---
2+
description: A set of resizable panels separated by draggable handles.
3+
category: layout
4+
links:
5+
- label: Splitter
6+
icon: i-custom-reka-ui
7+
to: https://reka-ui.com/docs/components/splitter
8+
- label: GitHub
9+
icon: i-simple-icons-github
10+
to: https://github.com/nuxt/ui/blob/v4/src/runtime/components/Splitter.vue
11+
navigation.badge: Soon
12+
---
13+
14+
## Usage
15+
16+
Use the Splitter component to display a list of resizable panels separated by draggable handles.
17+
18+
::component-example
19+
---
20+
collapse: true
21+
name: 'splitter-example'
22+
---
23+
::
24+
25+
::note
26+
The Splitter fills the height of its container, so make sure a parent element defines one.
27+
::
28+
29+
### Items
30+
31+
Use the `items` prop as an array of objects with the following properties:
32+
33+
- `defaultSize?: number`{lang="ts-type"}
34+
- `minSize?: number`{lang="ts-type"}
35+
- `maxSize?: number`{lang="ts-type"}
36+
- `collapsible?: boolean`{lang="ts-type"}
37+
- `collapsedSize?: number`{lang="ts-type"}
38+
- `sizeUnit?: '%' | 'px'`{lang="ts-type"}
39+
- `order?: number`{lang="ts-type"}
40+
- `id?: string`{lang="ts-type"}
41+
- `slot?: string`{lang="ts-type"}
42+
- `class?: any`{lang="ts-type"}
43+
- `ui?: { panel?: ClassNameValue }`{lang="ts-type"}
44+
45+
Use the `slot` key to fill the content of a panel and the `class` key to style it. Items without a `slot` key fall back to a `panel-{index}` slot. Sizes are percentages by default, set `sizeUnit: 'px'` on an item for pixel values.
46+
47+
::caution
48+
When rendering on the server, set the `id` prop and give `defaultSize` to all items or to none. Ids are generated automatically otherwise and the server and the client can disagree, which breaks the layout on hydration. An item without a `defaultSize` falls back to an equal share on the server, so mixing the two makes panels jump once hydrated. Pixel sizes are measured on the client and always shift a little.
49+
::
50+
51+
::component-code
52+
---
53+
collapse: true
54+
class: 'h-96'
55+
prettier: true
56+
ignore:
57+
- items
58+
- id
59+
external:
60+
- items
61+
externalTypes:
62+
- SplitterItem[]
63+
props:
64+
id: 'splitter-items'
65+
items:
66+
- slot: 'sidebar'
67+
minSize: 15
68+
maxSize: 40
69+
defaultSize: 25
70+
class: 'bg-elevated/50 border border-default rounded-xl items-center justify-center text-muted font-medium'
71+
- slot: 'main'
72+
defaultSize: 75
73+
class: 'bg-elevated/50 border border-default rounded-xl items-center justify-center text-muted font-medium'
74+
slots:
75+
sidebar: Sidebar
76+
main: Main
77+
---
78+
79+
#sidebar
80+
Sidebar
81+
82+
#main
83+
Main
84+
::
85+
86+
### Orientation
87+
88+
Use the `orientation` prop to change the direction of the splitter. Defaults to `horizontal`.
89+
90+
::component-code
91+
---
92+
collapse: true
93+
class: 'h-96'
94+
prettier: true
95+
ignore:
96+
- items
97+
- id
98+
external:
99+
- items
100+
externalTypes:
101+
- SplitterItem[]
102+
props:
103+
id: 'splitter-orientation'
104+
orientation: 'vertical'
105+
items:
106+
- slot: 'first'
107+
class: 'bg-elevated/50 border border-default rounded-xl items-center justify-center text-muted font-medium'
108+
- slot: 'second'
109+
class: 'bg-elevated/50 border border-default rounded-xl items-center justify-center text-muted font-medium'
110+
slots:
111+
first: First
112+
second: Second
113+
---
114+
115+
#first
116+
First
117+
118+
#second
119+
Second
120+
::
121+
122+
## Examples
123+
124+
### With collapsible panel
125+
126+
Set `collapsible: true` on an item to let it collapse past its `minSize`, and use `collapsedSize` to keep part of the panel visible when collapsed. The panel slot exposes `collapsed`, `collapse` and `expand` so you can control it programmatically, and the `collapse`, `expand` and `resize` events fire with the panel index.
127+
128+
::component-example
129+
---
130+
collapse: true
131+
name: 'splitter-collapsible-example'
132+
---
133+
::
134+
135+
### With nested splitters
136+
137+
Nest a `Splitter` inside a panel to build two-dimensional, IDE-style layouts.
138+
139+
::component-example
140+
---
141+
collapse: true
142+
name: 'splitter-nested-example'
143+
---
144+
::
145+
146+
### With custom handle
147+
148+
The handle is invisible by default. Use the `ui` prop to restyle it, for example as a visible divider for flush layouts, and the `resize-handle` slot to render content inside it like a grip.
149+
150+
::component-example
151+
---
152+
collapse: true
153+
name: 'splitter-custom-handle-example'
154+
---
155+
::
156+
157+
### With persistence
158+
159+
Provide an `auto-save-id` to persist the layout to `localStorage` and restore it on reload.
160+
161+
```vue
162+
<template>
163+
<USplitter id="my-layout" auto-save-id="my-layout" :items="items">
164+
<!-- ... -->
165+
</USplitter>
166+
</template>
167+
```
168+
169+
## API
170+
171+
### Props
172+
173+
:component-props
174+
175+
### Slots
176+
177+
:component-slots
178+
179+
### Emits
180+
181+
:component-emits
182+
183+
## Theme
184+
185+
:component-theme
186+
187+
## Changelog
188+
189+
:component-changelog
5.75 KB
Loading
5.66 KB
Loading

playgrounds/nuxt/app/composables/useNavigation.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ const components = [
8181
'skeleton',
8282
'slideover',
8383
'slider',
84+
'splitter',
8485
'stepper',
8586
'switch',
8687
'table',
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<script setup lang="ts">
2+
import type { SplitterItem } from '@nuxt/ui'
3+
4+
const card = 'bg-elevated/50 border border-default rounded-xl items-center justify-center text-muted font-semibold'
5+
6+
const items: SplitterItem[] = [
7+
{ slot: 'a', minSize: 20, class: card },
8+
{ slot: 'bc', minSize: 20 }
9+
]
10+
11+
const nested: SplitterItem[] = [
12+
{ slot: 'b', minSize: 20, class: card },
13+
{ slot: 'c', minSize: 20, class: card }
14+
]
15+
</script>
16+
17+
<template>
18+
<Navbar />
19+
20+
<div class="flex-1 w-full">
21+
<USplitter id="splitter" orientation="horizontal" :items="items">
22+
<template #a>
23+
Panel A
24+
</template>
25+
26+
<template #bc>
27+
<USplitter id="splitter-nested" orientation="vertical" :items="nested">
28+
<template #b>
29+
Panel B
30+
</template>
31+
32+
<template #c>
33+
Panel C
34+
</template>
35+
</USplitter>
36+
</template>
37+
</USplitter>
38+
</div>
39+
</template>

skills/nuxt-ui/references/components.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Quick-reference index of all 125+ components. For full API docs (props, slots, e
1212
| `UFooterColumns` | Multi-column footer with link groups |
1313
| `UMain` | Main content area |
1414
| `UContainer` | Centered max-width container |
15+
| `USplitter` | Resizable panels separated by draggable handles |
1516
| `ULink` | Enhanced link — NuxtLink/RouterLink with active states |
1617

1718
## Element

0 commit comments

Comments
 (0)