@svelte-atoms/core 1.0.0-alpha.23 → 1.0.0-alpha.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +645 -645
- package/dist/components/accordion/accordion-root.svelte.d.ts +2 -2
- package/dist/components/alert/alert-actions.svelte.d.ts +1 -0
- package/dist/components/alert/alert-close-button.svelte.d.ts +1 -0
- package/dist/components/alert/alert-content.svelte.d.ts +1 -0
- package/dist/components/atom/html-atom.svelte +151 -4
- package/dist/components/atom/html-atom.svelte.d.ts +4 -2
- package/dist/components/atom/types.d.ts +7 -0
- package/dist/components/button/button.stories.svelte +57 -17
- package/dist/components/button/button.stories.svelte.d.ts +6 -14
- package/dist/components/checkbox/checkbox.svelte.d.ts +1 -1
- package/dist/components/combobox/combobox-root.svelte.d.ts +2 -2
- package/dist/components/datagrid/datagrid.stories.svelte +75 -75
- package/dist/components/drawer/drawer-backdrop.svelte.d.ts +1 -0
- package/dist/components/icon/icon.svelte.d.ts +1 -0
- package/dist/components/input/input-placeholder.svelte +56 -56
- package/dist/components/input/input-placeholder.svelte.d.ts +1 -0
- package/dist/components/input/input-root.svelte +79 -79
- package/dist/components/input/input-root.svelte.d.ts +1 -0
- package/dist/components/input/input-value.svelte +113 -113
- package/dist/components/input/input-value.svelte.d.ts +1 -1
- package/dist/components/input/input.stories.svelte +38 -38
- package/dist/components/label/label.svelte.d.ts +1 -0
- package/dist/components/layer/layer-inner.svelte.d.ts +1 -0
- package/dist/components/layer/layer-root.svelte.d.ts +1 -0
- package/dist/components/popover/popover-arrow.svelte.d.ts +1 -0
- package/dist/components/portal/portal-inner.svelte.d.ts +1 -0
- package/dist/components/portal/portal-root.svelte.d.ts +1 -0
- package/dist/components/portal/teleport.svelte.d.ts +1 -0
- package/dist/components/radio/radio.svelte.d.ts +2 -2
- package/dist/components/root/root.svelte +121 -103
- package/dist/components/root/root.svelte.d.ts +1 -0
- package/dist/components/stack/stack-root.svelte.d.ts +1 -0
- package/dist/components/toast/toast-description.svelte.d.ts +1 -0
- package/dist/components/toast/toast-root.svelte.d.ts +1 -0
- package/dist/components/toast/toast-title.svelte.d.ts +1 -0
- package/dist/components/tree/tree-header.svelte.d.ts +1 -0
- package/dist/context/preset.svelte.d.ts +3 -0
- package/dist/runes/index.d.ts +3 -0
- package/dist/runes/index.js +3 -0
- package/dist/runes/reduced-motion.svelte.d.ts +23 -0
- package/dist/runes/reduced-motion.svelte.js +41 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/variant.d.ts +213 -0
- package/dist/utils/variant.js +137 -0
- package/llm/composition.md +395 -0
- package/llm/crafting.md +838 -0
- package/llm/motion.md +970 -0
- package/llm/philosophy.md +23 -0
- package/llm/preset-variant-integration.md +516 -0
- package/llm/preset.md +383 -0
- package/llm/styling.md +216 -0
- package/llm/usage.md +46 -0
- package/llm/variants.md +712 -0
- package/package.json +2 -1
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Variant System for @svelte-atoms/core
|
|
3
|
+
*
|
|
4
|
+
* A simple, single-function approach to defining component variants.
|
|
5
|
+
* Define variants once, use everywhere with full type safety and bond state access.
|
|
6
|
+
*
|
|
7
|
+
* Goals:
|
|
8
|
+
* 1. Single function returns all variants for a component
|
|
9
|
+
* 2. Access internal component state (via bond) for reactive variants
|
|
10
|
+
* 3. Return both classes AND other attributes (aria, data, etc.)
|
|
11
|
+
* 4. Type-safe with autocompletion
|
|
12
|
+
* 5. No context, no complexity
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Define variants for a component
|
|
16
|
+
* Returns a function that takes variant props and returns classes + attributes
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* // Option 1: Static config (no bond access needed)
|
|
21
|
+
* const buttonVariants = defineVariants({
|
|
22
|
+
* class: 'rounded-md font-medium transition-colors',
|
|
23
|
+
* variants: {
|
|
24
|
+
* variant: {
|
|
25
|
+
* primary: 'bg-blue-500 text-white hover:bg-blue-600',
|
|
26
|
+
* secondary: 'bg-gray-500 text-white hover:bg-gray-600'
|
|
27
|
+
* },
|
|
28
|
+
* size: {
|
|
29
|
+
* sm: 'px-2 py-1 text-sm',
|
|
30
|
+
* md: 'px-4 py-2 text-base',
|
|
31
|
+
* lg: 'px-6 py-3 text-lg'
|
|
32
|
+
* }
|
|
33
|
+
* },
|
|
34
|
+
* compounds: [
|
|
35
|
+
* {
|
|
36
|
+
* variant: 'primary',
|
|
37
|
+
* size: 'lg',
|
|
38
|
+
* class: 'shadow-lg'
|
|
39
|
+
* }
|
|
40
|
+
* ],
|
|
41
|
+
* defaults: {
|
|
42
|
+
* variant: 'primary',
|
|
43
|
+
* size: 'md'
|
|
44
|
+
* }
|
|
45
|
+
* });
|
|
46
|
+
*
|
|
47
|
+
* // Option 2: Dynamic config (with bond access)
|
|
48
|
+
* const buttonVariants = defineVariants((bond) => ({
|
|
49
|
+
* class: 'rounded-md font-medium transition-colors',
|
|
50
|
+
* variants: {
|
|
51
|
+
* variant: {
|
|
52
|
+
* primary: 'bg-blue-500 text-white hover:bg-blue-600',
|
|
53
|
+
* secondary: 'bg-gray-500 text-white hover:bg-gray-600',
|
|
54
|
+
* danger: bond?.state?.disabled
|
|
55
|
+
* ? 'bg-red-300 text-white'
|
|
56
|
+
* : 'bg-red-500 text-white hover:bg-red-600'
|
|
57
|
+
* },
|
|
58
|
+
* size: {
|
|
59
|
+
* sm: 'px-2 py-1 text-sm',
|
|
60
|
+
* md: 'px-4 py-2 text-base',
|
|
61
|
+
* lg: 'px-6 py-3 text-lg'
|
|
62
|
+
* }
|
|
63
|
+
* },
|
|
64
|
+
* compounds: [
|
|
65
|
+
* {
|
|
66
|
+
* variant: 'primary',
|
|
67
|
+
* size: 'lg',
|
|
68
|
+
* class: 'shadow-lg'
|
|
69
|
+
* }
|
|
70
|
+
* ],
|
|
71
|
+
* defaults: {
|
|
72
|
+
* variant: 'primary',
|
|
73
|
+
* size: 'md'
|
|
74
|
+
* }
|
|
75
|
+
* }));
|
|
76
|
+
*
|
|
77
|
+
* // Usage in component:
|
|
78
|
+
* const props = buttonVariants({ variant: 'primary', size: 'lg', bond });
|
|
79
|
+
* // Returns: { class: '...' }
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
export function defineVariants(config) {
|
|
83
|
+
return (bond, props) => {
|
|
84
|
+
// Get config (either static or dynamic based on bond)
|
|
85
|
+
const resolvedConfig = typeof config === 'function' ? config(bond) : config;
|
|
86
|
+
// Merge with defaults
|
|
87
|
+
const finalProps = { ...resolvedConfig.defaults, ...props };
|
|
88
|
+
// Collect classes and attributes
|
|
89
|
+
const classes = [];
|
|
90
|
+
const attributes = {};
|
|
91
|
+
// Add base class
|
|
92
|
+
if (resolvedConfig.class)
|
|
93
|
+
classes.push(resolvedConfig.class);
|
|
94
|
+
// Add variant classes and attributes
|
|
95
|
+
for (const [key, value] of Object.entries(finalProps)) {
|
|
96
|
+
const variantValue = resolvedConfig.variants?.[key]?.[value];
|
|
97
|
+
if (variantValue !== undefined) {
|
|
98
|
+
const resolved = typeof variantValue === 'function' ? variantValue(bond) : variantValue;
|
|
99
|
+
if (typeof resolved === 'string') {
|
|
100
|
+
classes.push(resolved);
|
|
101
|
+
}
|
|
102
|
+
else if (typeof resolved === 'object' && resolved !== null) {
|
|
103
|
+
if ('class' in resolved) {
|
|
104
|
+
classes.push(resolved.class);
|
|
105
|
+
}
|
|
106
|
+
// Add other attributes (aria-*, data-*, etc.)
|
|
107
|
+
Object.entries(resolved).forEach(([k, v]) => {
|
|
108
|
+
if (k !== 'class') {
|
|
109
|
+
attributes[k] = v;
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// Add compound variants
|
|
116
|
+
if (resolvedConfig.compounds) {
|
|
117
|
+
for (const compound of resolvedConfig.compounds) {
|
|
118
|
+
const { class: compoundClass, ...compoundProps } = compound;
|
|
119
|
+
const matches = Object.entries(compoundProps).every(([key, value]) => finalProps[key] === value);
|
|
120
|
+
if (matches) {
|
|
121
|
+
if (compoundClass)
|
|
122
|
+
classes.push(compoundClass);
|
|
123
|
+
// Add compound attributes
|
|
124
|
+
Object.entries(compound).forEach(([k, v]) => {
|
|
125
|
+
if (k !== 'class' && !Object.keys(compoundProps).includes(k)) {
|
|
126
|
+
attributes[k] = v;
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
class: classes,
|
|
134
|
+
...attributes
|
|
135
|
+
};
|
|
136
|
+
};
|
|
137
|
+
}
|
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
# Component Composition in @svelte-atoms/core
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
@svelte-atoms/core components are designed to be **composable** — you can combine them to extend both behavior and UI, creating powerful component combinations. This is achieved through three main patterns:
|
|
6
|
+
|
|
7
|
+
1. **Base Prop Composition** - Replace a component's root element with another component
|
|
8
|
+
2. **Bond Pattern** - Access and share component state across nested components
|
|
9
|
+
3. **Nested Children** - Compose components as siblings/parents for complex UIs
|
|
10
|
+
|
|
11
|
+
## 1. Base Prop Composition
|
|
12
|
+
|
|
13
|
+
The `base=` prop allows you to replace a component's root element with another component, **inheriting all the base component's behavior** while adding new functionality.
|
|
14
|
+
|
|
15
|
+
### Basic Pattern
|
|
16
|
+
|
|
17
|
+
```svelte
|
|
18
|
+
<Tooltip.Trigger base={Button}>Open Tooltip</Tooltip.Trigger>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The `Tooltip.Trigger` now **behaves as both** a Tooltip trigger AND a Button:
|
|
22
|
+
|
|
23
|
+
- Gets Button's styling, hover states, click handling
|
|
24
|
+
- Gets Tooltip.Trigger's `aria-*` attributes, open/close logic, positioning
|
|
25
|
+
|
|
26
|
+
### Form Field Composition
|
|
27
|
+
|
|
28
|
+
The most powerful example is `Field.Control`, which adapts to any input component:
|
|
29
|
+
|
|
30
|
+
```svelte
|
|
31
|
+
<!-- Text Input -->
|
|
32
|
+
<Field.Control base={Input.Value} placeholder="Enter name" />
|
|
33
|
+
|
|
34
|
+
<!-- Checkbox -->
|
|
35
|
+
<Field.Control base={Checkbox} />
|
|
36
|
+
|
|
37
|
+
<!-- Radio Group -->
|
|
38
|
+
<Field.Control base={RadioGroup}>
|
|
39
|
+
<Radio value="yes" />
|
|
40
|
+
<Radio value="no" />
|
|
41
|
+
</Field.Control>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`Field.Control` automatically:
|
|
45
|
+
|
|
46
|
+
- Connects the base component to form validation
|
|
47
|
+
- Syncs the input value with field state
|
|
48
|
+
- Handles validation on blur/change
|
|
49
|
+
- Links labels via `aria-labelledby`
|
|
50
|
+
|
|
51
|
+
### DataGrid Cell Composition
|
|
52
|
+
|
|
53
|
+
Compose table cells with interactive components:
|
|
54
|
+
|
|
55
|
+
```svelte
|
|
56
|
+
<DataGrid.Td base={Dropdown.Root} placement="bottom-end">
|
|
57
|
+
<Dropdown.Trigger>
|
|
58
|
+
<Icon src={MoreVerticalIcon} />
|
|
59
|
+
</Dropdown.Trigger>
|
|
60
|
+
<Dropdown.List>
|
|
61
|
+
<Dropdown.Item value="edit">Edit</Dropdown.Item>
|
|
62
|
+
<Dropdown.Item value="delete">Delete</Dropdown.Item>
|
|
63
|
+
</Dropdown.List>
|
|
64
|
+
</DataGrid.Td>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The `DataGrid.Td` now:
|
|
68
|
+
|
|
69
|
+
- Maintains table cell semantics and styling
|
|
70
|
+
- Adds Dropdown positioning, open/close state
|
|
71
|
+
- Handles keyboard navigation and focus management
|
|
72
|
+
|
|
73
|
+
### Menu & Dropdown Triggers
|
|
74
|
+
|
|
75
|
+
```svelte
|
|
76
|
+
<Menu.Root>
|
|
77
|
+
<Menu.Trigger base={Button}>Select Language</Menu.Trigger>
|
|
78
|
+
<Menu.List>
|
|
79
|
+
<Menu.Item>English</Menu.Item>
|
|
80
|
+
<Menu.Item>Spanish</Menu.Item>
|
|
81
|
+
</Menu.List>
|
|
82
|
+
</Menu.Root>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
`Menu.Trigger` with `base={Button}`:
|
|
86
|
+
|
|
87
|
+
- Button appearance and interaction
|
|
88
|
+
- Menu trigger accessibility (`aria-haspopup`, `aria-expanded`)
|
|
89
|
+
- Menu open/close toggle on click
|
|
90
|
+
|
|
91
|
+
### How Base Prop Works
|
|
92
|
+
|
|
93
|
+
When you write:
|
|
94
|
+
|
|
95
|
+
```svelte
|
|
96
|
+
<Component base={BaseComponent} {...props}>
|
|
97
|
+
{children}
|
|
98
|
+
</Component>
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Internally, the component renders:
|
|
102
|
+
|
|
103
|
+
```svelte
|
|
104
|
+
<BaseComponent {...props} {...componentSpecificProps}>
|
|
105
|
+
{children}
|
|
106
|
+
</BaseComponent>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
All props are forwarded, and the component adds its own behavior (event handlers, ARIA attributes, state bindings) to the base component.
|
|
110
|
+
|
|
111
|
+
## 2. Bond Pattern (State Sharing)
|
|
112
|
+
|
|
113
|
+
Components expose their internal state via a **bond** object through the `{#snippet children({ bond })}` pattern. This allows parent components to access and control child component state.
|
|
114
|
+
|
|
115
|
+
### Accessing Component State
|
|
116
|
+
|
|
117
|
+
```svelte
|
|
118
|
+
<Popover.Root>
|
|
119
|
+
{#snippet children({ popover })}
|
|
120
|
+
<Popover.Trigger base={Button}>
|
|
121
|
+
<div>Open Popover</div>
|
|
122
|
+
<!-- Access popover state to show indicator -->
|
|
123
|
+
<Popover.Indicator />
|
|
124
|
+
</Popover.Trigger>
|
|
125
|
+
|
|
126
|
+
<Popover.Content>
|
|
127
|
+
<!-- Can access popover.state.isOpen, popover.state.close(), etc. -->
|
|
128
|
+
</Popover.Content>
|
|
129
|
+
{/snippet}
|
|
130
|
+
</Popover.Root>
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
The `popover` bond provides:
|
|
134
|
+
|
|
135
|
+
- `popover.state.isOpen` - Current open state
|
|
136
|
+
- `popover.state.close()` - Close the popover programmatically
|
|
137
|
+
- `popover.state.toggle()` - Toggle open/close state
|
|
138
|
+
|
|
139
|
+
### Field Validation Access
|
|
140
|
+
|
|
141
|
+
```svelte
|
|
142
|
+
<Field.Root name="firstName" schema={nameSchema}>
|
|
143
|
+
{#snippet children({ field })}
|
|
144
|
+
<Field.Label>First Name</Field.Label>
|
|
145
|
+
|
|
146
|
+
<Input.Root>
|
|
147
|
+
<Field.Control
|
|
148
|
+
base={Input.Value}
|
|
149
|
+
onblur={() => {
|
|
150
|
+
const results = field?.state.validate();
|
|
151
|
+
console.log(results);
|
|
152
|
+
}}
|
|
153
|
+
/>
|
|
154
|
+
</Input.Root>
|
|
155
|
+
|
|
156
|
+
<!-- Access validation errors -->
|
|
157
|
+
{#if field?.state?.errors?.length > 0}
|
|
158
|
+
<div class="text-xs text-red-600">
|
|
159
|
+
{#each field.state.errors as error}
|
|
160
|
+
<div>{error.message}</div>
|
|
161
|
+
{/each}
|
|
162
|
+
</div>
|
|
163
|
+
{/if}
|
|
164
|
+
{/snippet}
|
|
165
|
+
</Field.Root>
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
The `field` bond provides:
|
|
169
|
+
|
|
170
|
+
- `field.state.validate()` - Trigger validation manually
|
|
171
|
+
- `field.state.errors` - Array of validation errors
|
|
172
|
+
- `field.state.value` - Current field value
|
|
173
|
+
|
|
174
|
+
### Collapsible State Control
|
|
175
|
+
|
|
176
|
+
```svelte
|
|
177
|
+
<Collapsible.Root>
|
|
178
|
+
{#snippet children({ collapsible })}
|
|
179
|
+
<Collapsible.Trigger base={Button}>Toggle Content</Collapsible.Trigger>
|
|
180
|
+
|
|
181
|
+
<Collapsible.Content>
|
|
182
|
+
<div>Collapsible content here</div>
|
|
183
|
+
|
|
184
|
+
<!-- Nested button that can control parent collapsible -->
|
|
185
|
+
<Button onclick={() => collapsible.state.close()}>Close from inside</Button>
|
|
186
|
+
</Collapsible.Content>
|
|
187
|
+
{/snippet}
|
|
188
|
+
</Collapsible.Root>
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Dropdown State Inspection
|
|
192
|
+
|
|
193
|
+
```svelte
|
|
194
|
+
<Dropdown.Root>
|
|
195
|
+
{#snippet children({ dropdown })}
|
|
196
|
+
<Dropdown.Trigger base={Button}>Select Option</Dropdown.Trigger>
|
|
197
|
+
|
|
198
|
+
<Dropdown.List>
|
|
199
|
+
<Dropdown.Item value="option1">Option 1</Dropdown.Item>
|
|
200
|
+
<Dropdown.Item value="option2">Option 2</Dropdown.Item>
|
|
201
|
+
</Dropdown.List>
|
|
202
|
+
|
|
203
|
+
<!-- Display selected value outside dropdown -->
|
|
204
|
+
<div>Selected: {dropdown.state.value || 'None'}</div>
|
|
205
|
+
{/snippet}
|
|
206
|
+
</Dropdown.Root>
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### How Bond Pattern Works
|
|
210
|
+
|
|
211
|
+
Components create a **Bond** instance that holds state and methods:
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
// Inside component implementation
|
|
215
|
+
const bond = new PopoverBond();
|
|
216
|
+
bond.state.isOpen = $state(false);
|
|
217
|
+
bond.state.close = () => {
|
|
218
|
+
bond.state.isOpen = false;
|
|
219
|
+
};
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
The bond is passed to children via snippet props, allowing descendants to access and modify component state.
|
|
223
|
+
|
|
224
|
+
## 3. Nested Component Composition
|
|
225
|
+
|
|
226
|
+
Components can be nested as siblings or parent-child to create complex interactions.
|
|
227
|
+
|
|
228
|
+
### Dialog with Nested Dropdown
|
|
229
|
+
|
|
230
|
+
```svelte
|
|
231
|
+
<Dialog.Root>
|
|
232
|
+
{#snippet children({ dialog })}
|
|
233
|
+
<Dialog.Trigger base={Button}>Open Dialog</Dialog.Trigger>
|
|
234
|
+
|
|
235
|
+
<Dialog.Content>
|
|
236
|
+
<Dialog.Header>
|
|
237
|
+
<Dialog.Title>Select an option</Dialog.Title>
|
|
238
|
+
</Dialog.Header>
|
|
239
|
+
|
|
240
|
+
<!-- Dropdown inside Dialog -->
|
|
241
|
+
<Dropdown.Root>
|
|
242
|
+
{#snippet children({ dropdown })}
|
|
243
|
+
<Dropdown.Trigger base={Button}>Choose Language</Dropdown.Trigger>
|
|
244
|
+
<Dropdown.List>
|
|
245
|
+
<Dropdown.Item value="en">English</Dropdown.Item>
|
|
246
|
+
<Dropdown.Item value="es">Spanish</Dropdown.Item>
|
|
247
|
+
</Dropdown.List>
|
|
248
|
+
{/snippet}
|
|
249
|
+
</Dropdown.Root>
|
|
250
|
+
|
|
251
|
+
<Dialog.Footer>
|
|
252
|
+
<Button onclick={() => dialog.state.close()}>Close</Button>
|
|
253
|
+
</Dialog.Footer>
|
|
254
|
+
</Dialog.Content>
|
|
255
|
+
{/snippet}
|
|
256
|
+
</Dialog.Root>
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Each component manages its own state independently, but both bonds are accessible for coordination.
|
|
260
|
+
|
|
261
|
+
### Scrollable with Nested Content
|
|
262
|
+
|
|
263
|
+
```svelte
|
|
264
|
+
<Scrollable.Root>
|
|
265
|
+
{#snippet children({ scrollable })}
|
|
266
|
+
<Scrollable.Content>
|
|
267
|
+
<!-- Access scroll position -->
|
|
268
|
+
<div>Scroll Y: {scrollable.state.scrollTop}</div>
|
|
269
|
+
|
|
270
|
+
<!-- Nested interactive components -->
|
|
271
|
+
<Accordion.Root>
|
|
272
|
+
<Accordion.Item value="item1">
|
|
273
|
+
<Accordion.Trigger>Section 1</Accordion.Trigger>
|
|
274
|
+
<Accordion.Content>Content 1</Accordion.Content>
|
|
275
|
+
</Accordion.Item>
|
|
276
|
+
</Accordion.Root>
|
|
277
|
+
</Scrollable.Content>
|
|
278
|
+
{/snippet}
|
|
279
|
+
</Scrollable.Root>
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Combobox with Custom Trigger
|
|
283
|
+
|
|
284
|
+
```svelte
|
|
285
|
+
<Combobox.Root>
|
|
286
|
+
{#snippet children({ combobox })}
|
|
287
|
+
<Combobox.Trigger base={Input.Root}>
|
|
288
|
+
<Input.Value placeholder="Search..." />
|
|
289
|
+
<Input.Icon src={SearchIcon} />
|
|
290
|
+
</Combobox.Trigger>
|
|
291
|
+
|
|
292
|
+
<Combobox.List>
|
|
293
|
+
<Combobox.Item value="option1">Option 1</Combobox.Item>
|
|
294
|
+
<Combobox.Item value="option2">Option 2</Combobox.Item>
|
|
295
|
+
</Combobox.List>
|
|
296
|
+
{/snippet}
|
|
297
|
+
</Combobox.Root>
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
The `Input.Root` is composed into the trigger, bringing input styling and icon support to the combobox.
|
|
301
|
+
|
|
302
|
+
## Key Principles
|
|
303
|
+
|
|
304
|
+
1. **Base Prop = Behavior Inheritance**: Use `base=` to layer component behaviors (Button + Menu.Trigger = clickable menu opener)
|
|
305
|
+
|
|
306
|
+
2. **Bond = State Access**: Use `{#snippet children({ bond })}` to access component state and methods for coordination
|
|
307
|
+
|
|
308
|
+
3. **Nesting = UI Composition**: Nest components as needed; each maintains independent state while bonds allow communication
|
|
309
|
+
|
|
310
|
+
4. **Forward Everything**: Components forward all props and classes to their base, enabling full customization
|
|
311
|
+
|
|
312
|
+
5. **Type Safety**: TypeScript infers correct props when using `base=`, providing autocompletion for both the component and the base
|
|
313
|
+
|
|
314
|
+
## Common Patterns
|
|
315
|
+
|
|
316
|
+
### Interactive Table Cell
|
|
317
|
+
|
|
318
|
+
```svelte
|
|
319
|
+
<DataGrid.Td base={Dropdown.Root}>
|
|
320
|
+
<Dropdown.Trigger base={Button} variant="ghost" size="sm">Actions</Dropdown.Trigger>
|
|
321
|
+
<Dropdown.List>
|
|
322
|
+
<Dropdown.Item>Edit</Dropdown.Item>
|
|
323
|
+
<Dropdown.Item>Delete</Dropdown.Item>
|
|
324
|
+
</Dropdown.List>
|
|
325
|
+
</DataGrid.Td>
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
**Combines**: Table cell + Dropdown + Button
|
|
329
|
+
|
|
330
|
+
### Validated Form Field
|
|
331
|
+
|
|
332
|
+
```svelte
|
|
333
|
+
<Field.Root name="email" schema={emailSchema}>
|
|
334
|
+
{#snippet children({ field })}
|
|
335
|
+
<Field.Label>Email</Field.Label>
|
|
336
|
+
<Field.Control
|
|
337
|
+
base={Input.Value}
|
|
338
|
+
type="email"
|
|
339
|
+
placeholder="you@example.com"
|
|
340
|
+
onblur={() => field?.state.validate()}
|
|
341
|
+
/>
|
|
342
|
+
{#if field?.state?.errors}
|
|
343
|
+
<Field.Error />
|
|
344
|
+
{/if}
|
|
345
|
+
{/snippet}
|
|
346
|
+
</Field.Root>
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
**Combines**: Form validation + Input + Label + Error display
|
|
350
|
+
|
|
351
|
+
### Tooltip Button
|
|
352
|
+
|
|
353
|
+
```svelte
|
|
354
|
+
<Tooltip.Root>
|
|
355
|
+
<Tooltip.Trigger base={Button} variant="outline">Delete</Tooltip.Trigger>
|
|
356
|
+
<Tooltip.Content>This action cannot be undone</Tooltip.Content>
|
|
357
|
+
</Tooltip.Root>
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
**Combines**: Tooltip positioning + Button interaction
|
|
361
|
+
|
|
362
|
+
### Controlled Popover with Custom Content
|
|
363
|
+
|
|
364
|
+
```svelte
|
|
365
|
+
<Popover.Root bind:open>
|
|
366
|
+
{#snippet children({ popover })}
|
|
367
|
+
<Popover.Trigger base={Button}>
|
|
368
|
+
Open {popover.state.isOpen ? '(Open)' : '(Closed)'}
|
|
369
|
+
</Popover.Trigger>
|
|
370
|
+
|
|
371
|
+
<Popover.Content class="w-80">
|
|
372
|
+
<Card>
|
|
373
|
+
<Card.Header>
|
|
374
|
+
<Card.Title>Settings</Card.Title>
|
|
375
|
+
</Card.Header>
|
|
376
|
+
<Card.Content>
|
|
377
|
+
<!-- Form fields here -->
|
|
378
|
+
</Card.Content>
|
|
379
|
+
<Card.Footer>
|
|
380
|
+
<Button onclick={() => popover.state.close()}>Close</Button>
|
|
381
|
+
</Card.Footer>
|
|
382
|
+
</Card>
|
|
383
|
+
</Popover.Content>
|
|
384
|
+
{/snippet}
|
|
385
|
+
</Popover.Root>
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
**Combines**: Popover positioning + Button trigger + Card layout + Form
|
|
389
|
+
|
|
390
|
+
## Summary
|
|
391
|
+
|
|
392
|
+
- **`base=` prop**: Inherit behavior from another component (Trigger + Button = clickable trigger)
|
|
393
|
+
- **`{#snippet children({ bond })}`**: Access component state for coordination
|
|
394
|
+
- **Nesting**: Compose complex UIs by nesting components as needed
|
|
395
|
+
- **Composition > Configuration**: Combine simple components instead of creating complex ones with many props
|