compote-ui 0.62.1 → 0.62.2

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 CHANGED
@@ -26,6 +26,12 @@ yarn add compote-ui
26
26
  bun add compote-ui
27
27
  ```
28
28
 
29
+ If you use an AI agent, run:
30
+
31
+ ```bash
32
+ npx @tanstack/intent@latest install
33
+ ```
34
+
29
35
  ## Peer Dependencies
30
36
 
31
37
  Compote UI requires Svelte 5 or later:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "compote-ui",
3
- "version": "0.62.1",
3
+ "version": "0.62.2",
4
4
  "license": "MIT",
5
5
  "scripts": {
6
6
  "dev": "vite dev --open",
@@ -19,7 +19,9 @@
19
19
  "files": [
20
20
  "dist",
21
21
  "!dist/**/*.test.*",
22
- "!dist/**/*.spec.*"
22
+ "!dist/**/*.spec.*",
23
+ "skills",
24
+ "!skills/_artifacts"
23
25
  ],
24
26
  "sideEffects": [
25
27
  "**/*.css"
@@ -62,6 +64,7 @@
62
64
  "@sveltejs/package": "^2.5.8",
63
65
  "@sveltejs/vite-plugin-svelte": "7.1.2",
64
66
  "@tailwindcss/vite": "^4.3.1",
67
+ "@tanstack/intent": "^0.3.2",
65
68
  "@tanstack/svelte-table": "^9.0.0-beta.17",
66
69
  "@tanstack/svelte-virtual": "^3.13.29",
67
70
  "@types/node": "^22.20.0",
@@ -83,7 +86,8 @@
83
86
  "vite": "^8.0.16"
84
87
  },
85
88
  "keywords": [
86
- "svelte"
89
+ "svelte",
90
+ "tanstack-intent"
87
91
  ],
88
92
  "dependencies": {
89
93
  "@ark-ui/svelte": "^5.22.1",
@@ -93,5 +97,11 @@
93
97
  "svelte-tel-input": "^4.2.0",
94
98
  "tailwind-merge": "^3.6.0",
95
99
  "tailwind-variants": "^3.2.2"
100
+ },
101
+ "intent": {
102
+ "skills": [
103
+ "compote-ui",
104
+ "@tanstack/svelte-table"
105
+ ]
96
106
  }
97
107
  }
@@ -0,0 +1,202 @@
1
+ ---
2
+ name: component-usage
3
+ description: >
4
+ Load when choosing or using compote-ui components and utilities from the root
5
+ package. Covers public exports, single components vs namespaced compound
6
+ components, forms, overlays, layout/display components, files/images, buttons,
7
+ collections, and common component usage mistakes.
8
+ metadata:
9
+ type: core
10
+ library: compote-ui
11
+ library_version: '0.62.1'
12
+ sources:
13
+ - src/lib/index.ts
14
+ - src/lib/utils/collections.ts
15
+ - src/lib/components/button/button.svelte
16
+ - src/lib/components/field/field.svelte
17
+ - src/lib/components/select/select.svelte
18
+ - src/lib/components/dialog/dialog-root.svelte
19
+ - src/lib/components/file-upload/file-upload.svelte
20
+ - src/lib/components/image-cropper/image-cropper.svelte
21
+ - src/lib/utils/image-processing.ts
22
+ ---
23
+
24
+ # Compote UI — Component Usage
25
+
26
+ Use root exports from `compote-ui` for ordinary components and namespaces for compound component families.
27
+
28
+ ## Setup
29
+
30
+ ```svelte
31
+ <script lang="ts">
32
+ import { Button, Field, Select, Dialog, createListCollection } from 'compote-ui';
33
+
34
+ const fruits = [
35
+ { value: 'apple', label: 'Apple' },
36
+ { value: 'banana', label: 'Banana', disabled: true }
37
+ ];
38
+
39
+ let name = $state('');
40
+ let fruit = $state<string | null>(null);
41
+ let open = $state(false);
42
+ const collection = createListCollection(fruits);
43
+ </script>
44
+
45
+ <Field.Root required>
46
+ <Field.Label>Name</Field.Label>
47
+ <Field.Input bind:value={name} />
48
+ </Field.Root>
49
+
50
+ <Select items={fruits} label="Fruit" bind:value={fruit} />
51
+
52
+ <Button onclick={() => (open = true)}>Open</Button>
53
+ <Dialog.Root bind:open>
54
+ <Dialog.Title>Selected fruit</Dialog.Title>
55
+ <p>{fruit}</p>
56
+ </Dialog.Root>
57
+ ```
58
+
59
+ ## Core Patterns
60
+
61
+ ### Import compound components as namespaces
62
+
63
+ ```svelte
64
+ <script lang="ts">
65
+ import { Card, Tabs, Menu } from 'compote-ui';
66
+ </script>
67
+
68
+ <Card.Root>
69
+ <Card.Header>
70
+ <Card.Title>Summary</Card.Title>
71
+ </Card.Header>
72
+ <Card.Content>
73
+ <Tabs.Root defaultValue="one">
74
+ <Tabs.List>
75
+ <Tabs.Trigger value="one">One</Tabs.Trigger>
76
+ </Tabs.List>
77
+ <Tabs.Content value="one">Content</Tabs.Content>
78
+ </Tabs.Root>
79
+ </Card.Content>
80
+ </Card.Root>
81
+ ```
82
+
83
+ ### Use item shapes expected by collections
84
+
85
+ ```ts
86
+ import { createListCollection, createTreeCollection } from 'compote-ui';
87
+
88
+ const list = createListCollection([
89
+ { value: 1, label: 'One' },
90
+ { value: 2, label: 'Two', disabled: true }
91
+ ]);
92
+
93
+ const tree = createTreeCollection([
94
+ { value: 'src', label: 'src', children: [{ value: 'app', label: 'App.svelte' }] }
95
+ ]);
96
+ ```
97
+
98
+ ### Prefer wrappers before raw Ark UI primitives
99
+
100
+ ```svelte
101
+ <script lang="ts">
102
+ import { Drawer } from 'compote-ui';
103
+ </script>
104
+
105
+ <Drawer.Root>
106
+ <Drawer.Trigger>Open</Drawer.Trigger>
107
+ <Drawer.Content>
108
+ <Drawer.Header>
109
+ <Drawer.Title>Edit record</Drawer.Title>
110
+ </Drawer.Header>
111
+ <Drawer.Body>Content</Drawer.Body>
112
+ </Drawer.Content>
113
+ </Drawer.Root>
114
+ ```
115
+
116
+ Use Ark UI directly only for advanced cases the wrapper does not model, such as nested splitters.
117
+
118
+ ## Common Mistakes
119
+
120
+ ### HIGH Importing compound parts as root exports
121
+
122
+ Wrong:
123
+
124
+ ```svelte
125
+ <script lang="ts">
126
+ import { DialogRoot, DialogTitle } from 'compote-ui';
127
+ </script>
128
+ ```
129
+
130
+ Correct:
131
+
132
+ ```svelte
133
+ <script lang="ts">
134
+ import { Dialog } from 'compote-ui';
135
+ </script>
136
+
137
+ <Dialog.Root>
138
+ <Dialog.Title>Title</Dialog.Title>
139
+ </Dialog.Root>
140
+ ```
141
+
142
+ Compound components are exported as namespaces.
143
+
144
+ Source: `src/lib/index.ts`
145
+
146
+ ### HIGH Using arbitrary item shapes
147
+
148
+ Wrong:
149
+
150
+ ```svelte
151
+ <script lang="ts">
152
+ const items = [{ id: 1, name: 'One' }];
153
+ </script>
154
+
155
+ <Select {items} bind:value />
156
+ ```
157
+
158
+ Correct:
159
+
160
+ ```svelte
161
+ <script lang="ts">
162
+ const items = [{ value: 1, label: 'One' }];
163
+ </script>
164
+
165
+ <Select {items} bind:value />
166
+ ```
167
+
168
+ List-based components and collection helpers derive value and display text from `value` and `label`.
169
+
170
+ Source: `src/lib/utils/collections.ts`, `src/lib/components/select/select.svelte`
171
+
172
+ ### MEDIUM Icon-only button without label
173
+
174
+ Wrong:
175
+
176
+ ```svelte
177
+ <Button size="icon">
178
+ <SettingsIcon />
179
+ </Button>
180
+ ```
181
+
182
+ Correct:
183
+
184
+ ```svelte
185
+ <Button size="icon" aria-label="Settings">
186
+ <SettingsIcon class="size-4" />
187
+ </Button>
188
+ ```
189
+
190
+ Icon-only controls need an accessible name.
191
+
192
+ Source: `C:/Users/tihom/.claude/skills/compote-ui/references/button.md`
193
+
194
+ ## References
195
+
196
+ - [Buttons](references/buttons.md)
197
+ - [Forms and inputs](references/forms-and-inputs.md)
198
+ - [Overlays and floating UI](references/overlays-and-floating-ui.md)
199
+ - [Layout and display](references/layout-and-display.md)
200
+ - [Files and images](references/files-and-images.md)
201
+ - [Interactive components](references/interactive-components.md)
202
+ - [Component export map](references/component-export-map.md)
@@ -0,0 +1,27 @@
1
+ # Buttons
2
+
3
+ ```svelte
4
+ <Button>Default</Button>
5
+ <Button variant="outline">Outline</Button>
6
+ <Button variant="ghost">Ghost</Button>
7
+ <Button size="sm">Small</Button>
8
+ <Button size="lg">Large</Button>
9
+ <Button size="icon" aria-label="Settings">
10
+ <SettingsIcon class="size-4" />
11
+ </Button>
12
+ <Button disabled>Disabled</Button>
13
+ ```
14
+
15
+ `Button` props:
16
+
17
+ - `variant?: 'default' | 'outline' | 'ghost'`
18
+ - `size?: 'sm' | 'default' | 'lg' | 'icon' | 'icon-sm' | 'icon-lg'`
19
+ - `class?: ClassValue`
20
+ - standard button attributes such as `onclick`, `disabled`, `type`
21
+
22
+ Use `LinkButton` for navigation links that should look like buttons:
23
+
24
+ ```svelte
25
+ <LinkButton href="/dashboard">Dashboard</LinkButton>
26
+ <LinkButton href="https://example.com" variant="outline" target="_blank">External</LinkButton>
27
+ ```
@@ -0,0 +1,54 @@
1
+ # Component Export Map
2
+
3
+ Root exports:
4
+
5
+ - `Avatar`
6
+ - `Button`, `LinkButton`
7
+ - `Checkbox`, `CheckboxGroup`
8
+ - `Combobox`
9
+ - `DateField`, `DateRangeField`, `DateInput`, `DatePicker`
10
+ - `AlertDialog`
11
+ - `FileUploadDropzone`, `FileUpload`
12
+ - `ImageCropper`, `ImageCropDialog`
13
+ - `JsonTreeView`
14
+ - `NumberInput`, `PasswordInput`, `PhoneInput`
15
+ - `QrCode`
16
+ - `Select`
17
+ - `Splitter`
18
+ - `Switch`
19
+ - `Toggle`
20
+ - `TreeView`
21
+ - `LocaleProvider`, `useLocaleContext`
22
+ - `Portal`
23
+ - `PersistedState`, `Debounced`
24
+ - `cn`
25
+
26
+ Namespace exports:
27
+
28
+ - `Card`
29
+ - `Collapsible`
30
+ - `HoverCard`
31
+ - `ScrollArea`
32
+ - `Carousel`
33
+ - `Dialog`
34
+ - `DataTable`
35
+ - `VirtualDataTable`
36
+ - `Drawer`
37
+ - `Listbox`
38
+ - `Popover`
39
+ - `Tabs`
40
+ - `Toast`
41
+ - `ToggleGroup`
42
+ - `Menu`
43
+ - `Tooltip`
44
+ - `Field`
45
+ - `Fieldset`
46
+
47
+ Utility exports:
48
+
49
+ - `loadImage`
50
+ - `fileToDataUrl`
51
+ - `cropImage`
52
+ - `processImage`
53
+ - `createListCollection`
54
+ - `createTreeCollection`
@@ -0,0 +1,75 @@
1
+ # Files And Images
2
+
3
+ Use `ImageCropDialog` for the complete upload/crop flow.
4
+
5
+ ```svelte
6
+ <script lang="ts">
7
+ import { FileUploadDropzone, ImageCropDialog, fileToDataUrl } from 'compote-ui';
8
+
9
+ let cropOpen = $state(false);
10
+ let imageSrc = $state('');
11
+ let previewUrl = $state<string | undefined>();
12
+
13
+ async function handleFile(file: File) {
14
+ imageSrc = await fileToDataUrl(file);
15
+ cropOpen = true;
16
+ }
17
+
18
+ function handleConfirm(blob: Blob) {
19
+ cropOpen = false;
20
+ if (previewUrl) URL.revokeObjectURL(previewUrl);
21
+ previewUrl = URL.createObjectURL(blob);
22
+ }
23
+ </script>
24
+
25
+ <FileUploadDropzone
26
+ fileType="image"
27
+ onFileAccept={(details) => {
28
+ if (details.files[0]) handleFile(details.files[0]);
29
+ }}
30
+ />
31
+
32
+ <ImageCropDialog
33
+ bind:open={cropOpen}
34
+ {imageSrc}
35
+ onConfirm={handleConfirm}
36
+ onCancel={() => (cropOpen = false)}
37
+ />
38
+ ```
39
+
40
+ Image utilities are browser-only:
41
+
42
+ ```ts
43
+ import { fileToDataUrl, processImage, cropImage } from 'compote-ui';
44
+ ```
45
+
46
+ `ProcessImageOptions`:
47
+
48
+ ```ts
49
+ type ProcessImageOptions = {
50
+ maxWidth?: number;
51
+ maxHeight?: number;
52
+ quality?: number;
53
+ format?: 'image/webp' | 'image/jpeg' | 'image/png';
54
+ trim?: boolean;
55
+ trimThreshold?: number;
56
+ };
57
+ ```
58
+
59
+ Prefer `getProcessedImage` over Ark UI display-resolution crop output:
60
+
61
+ ```svelte
62
+ <script lang="ts">
63
+ let getProcessedImage = $state<((opts?: ProcessImageOptions) => Promise<Blob>) | undefined>();
64
+ </script>
65
+
66
+ <ImageCropper src={imageSrc} bind:getProcessedImage aspectRatio={1} />
67
+
68
+ <Button
69
+ onclick={async () => {
70
+ const blob = await getProcessedImage?.({ maxWidth: 1200 });
71
+ }}
72
+ >
73
+ Save
74
+ </Button>
75
+ ```
@@ -0,0 +1,94 @@
1
+ # Forms And Inputs
2
+
3
+ Use `Field.Root` to propagate `invalid`, `disabled`, `required`, and `readOnly` state.
4
+
5
+ ```svelte
6
+ <Field.Root required invalid={!email}>
7
+ <Field.Label>Email</Field.Label>
8
+ <Field.Input bind:value={email} type="email" />
9
+ <Field.ErrorText>Email is required.</Field.ErrorText>
10
+ </Field.Root>
11
+ ```
12
+
13
+ With a form adapter, `Field.Root` derives invalid/required state and renders the first error:
14
+
15
+ ```svelte
16
+ <Field.Root {form} field="email" helperText="We'll never share your email.">
17
+ <Field.Label>Email</Field.Label>
18
+ <Field.Input bind:value={email} type="email" />
19
+ </Field.Root>
20
+ ```
21
+
22
+ List controls use `{ value, label }` items:
23
+
24
+ ```svelte
25
+ <script lang="ts">
26
+ const items = [
27
+ { value: 'apple', label: 'Apple' },
28
+ { value: 'banana', label: 'Banana', disabled: true }
29
+ ];
30
+
31
+ let value = $state<string | null>(null);
32
+ </script>
33
+
34
+ <Select {items} label="Fruit" bind:value placeholder="Select..." />
35
+ <Combobox {items} label="Fruit" bind:value />
36
+ ```
37
+
38
+ Use `DateField` as the default accessible date input:
39
+
40
+ ```svelte
41
+ <script lang="ts">
42
+ import type { DateValue } from 'compote-ui';
43
+
44
+ let value = $state<DateValue | null>(null);
45
+ </script>
46
+
47
+ <DateField label="Date" bind:value />
48
+ ```
49
+
50
+ Use `DateRangeField` for ranges:
51
+
52
+ ```svelte
53
+ <script lang="ts">
54
+ import type { DateValue } from 'compote-ui';
55
+
56
+ let range = $state<DateValue[]>([]);
57
+ </script>
58
+
59
+ <DateRangeField label="Range" bind:value={range} />
60
+ ```
61
+
62
+ Use `DateInput` when binding existing `string`, `Date`, or `DateValue` values without a calendar popup.
63
+
64
+ `PhoneInput` stores E.164 values:
65
+
66
+ ```svelte
67
+ <script lang="ts">
68
+ let phone = $state('');
69
+ let country = $state(null);
70
+ let valid = $state(false);
71
+ </script>
72
+
73
+ <PhoneInput label="Phone" bind:value={phone} bind:country bind:valid />
74
+ ```
75
+
76
+ `Listbox.Content` exposes filtered `items` and grouped `group` snippet data:
77
+
78
+ ```svelte
79
+ <Listbox.Root {items} bind:value={selected}>
80
+ <Listbox.Label>Pick items</Listbox.Label>
81
+ <Listbox.Input placeholder="Search..." />
82
+ <Listbox.Content>
83
+ {#snippet items({ items })}
84
+ {#each items as item (item.value)}
85
+ <Listbox.Item {item}>
86
+ <Listbox.ItemText>{item.label}</Listbox.ItemText>
87
+ <Listbox.ItemIndicator />
88
+ </Listbox.Item>
89
+ {/each}
90
+ <Listbox.Empty>No results</Listbox.Empty>
91
+ {/snippet}
92
+ </Listbox.Content>
93
+ </Listbox.Root>
94
+ ```
@@ -0,0 +1,33 @@
1
+ # Interactive Components
2
+
3
+ Use `Toggle` for one standalone binary action:
4
+
5
+ ```svelte
6
+ <script lang="ts">
7
+ let favorite = $state(false);
8
+ </script>
9
+
10
+ <Toggle bind:pressed={favorite} aria-label="Toggle favorite">Favorite</Toggle>
11
+ ```
12
+
13
+ Use `ToggleGroup` for toolbar-style groups:
14
+
15
+ ```svelte
16
+ <script lang="ts">
17
+ let alignment = $state(['left']);
18
+ </script>
19
+
20
+ <ToggleGroup.Root bind:value={alignment} icon>
21
+ <ToggleGroup.Item value="left">
22
+ <AlignLeft />
23
+ </ToggleGroup.Item>
24
+ <ToggleGroup.Item value="center">
25
+ <AlignCenter />
26
+ </ToggleGroup.Item>
27
+ <ToggleGroup.Item value="right">
28
+ <AlignRight />
29
+ </ToggleGroup.Item>
30
+ </ToggleGroup.Root>
31
+ ```
32
+
33
+ `Toggle` defaults to `variant="ghost"`. `ToggleGroup` defaults to `variant="outline"`.
@@ -0,0 +1,82 @@
1
+ # Layout And Display
2
+
3
+ Tabs:
4
+
5
+ ```svelte
6
+ <Tabs.Root bind:value={tab} defaultValue="account">
7
+ <Tabs.List>
8
+ <Tabs.Trigger value="account">Account</Tabs.Trigger>
9
+ <Tabs.Trigger value="settings">Settings</Tabs.Trigger>
10
+ <Tabs.Indicator />
11
+ </Tabs.List>
12
+ <Tabs.Content value="account">Account content</Tabs.Content>
13
+ <Tabs.Content value="settings">Settings content</Tabs.Content>
14
+ </Tabs.Root>
15
+ ```
16
+
17
+ Splitter fills its container:
18
+
19
+ ```svelte
20
+ <div class="h-64">
21
+ {#snippet left()}
22
+ <div class="p-3">Left</div>
23
+ {/snippet}
24
+ {#snippet right()}
25
+ <div class="p-3">Right</div>
26
+ {/snippet}
27
+
28
+ <Splitter
29
+ panels={[
30
+ { id: 'left', minSize: 20, content: left },
31
+ { id: 'right', minSize: 20, content: right }
32
+ ]}
33
+ />
34
+ </div>
35
+ ```
36
+
37
+ For nested splitters, use Ark UI directly with one shared registry:
38
+
39
+ ```svelte
40
+ <script lang="ts">
41
+ import { Splitter } from '@ark-ui/svelte/splitter';
42
+
43
+ const registry = Splitter.createRegistry();
44
+ </script>
45
+ ```
46
+
47
+ ScrollArea needs explicit size:
48
+
49
+ ```svelte
50
+ <ScrollArea.Root class="h-80 w-56">
51
+ <ScrollArea.Viewport>
52
+ <ScrollArea.Content class="p-3">Content</ScrollArea.Content>
53
+ </ScrollArea.Viewport>
54
+ <ScrollArea.Scrollbar orientation="vertical">
55
+ <ScrollArea.Thumb />
56
+ </ScrollArea.Scrollbar>
57
+ </ScrollArea.Root>
58
+ ```
59
+
60
+ Vertical Carousel needs explicit height:
61
+
62
+ ```svelte
63
+ <Carousel.Root orientation="vertical" class="h-[512px]" slideCount={images.length}>
64
+ <Carousel.Control>
65
+ <Carousel.ItemGroup>
66
+ {#each images as image, index (image.src)}
67
+ <Carousel.Item {index}>
68
+ <img src={image.src} alt={image.alt} class="h-full w-full object-cover" />
69
+ </Carousel.Item>
70
+ {/each}
71
+ </Carousel.ItemGroup>
72
+ </Carousel.Control>
73
+ </Carousel.Root>
74
+ ```
75
+
76
+ TreeView fills its container:
77
+
78
+ ```svelte
79
+ <div class="h-80 rounded border">
80
+ <TreeView {items} label="Files" bind:selectedValue={selected} />
81
+ </div>
82
+ ```