laif-ds 0.2.43 → 0.2.45
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/dist/_virtual/index6.js +2 -2
- package/dist/_virtual/index7.js +2 -2
- package/dist/agent-docs/components/Accordion.md +157 -0
- package/dist/agent-docs/components/Alert.md +95 -0
- package/dist/agent-docs/components/AlertDialog.md +126 -0
- package/dist/agent-docs/components/AppEditor.md +90 -0
- package/dist/agent-docs/components/AppForm.md +242 -0
- package/dist/agent-docs/components/AppMultipleSelectDropdown.md +38 -0
- package/dist/agent-docs/components/AppRadioGroup.md +223 -0
- package/dist/agent-docs/components/AppSelect.md +427 -0
- package/dist/agent-docs/components/AppSidebar.md +122 -0
- package/dist/agent-docs/components/AppStepper.md +77 -0
- package/dist/agent-docs/components/AspectRatio.md +87 -0
- package/dist/agent-docs/components/AsyncSelect.md +127 -0
- package/dist/agent-docs/components/AudioVisualizer.md +41 -0
- package/dist/agent-docs/components/Avatar.md +113 -0
- package/dist/agent-docs/components/Badge.md +118 -0
- package/dist/agent-docs/components/Breadcrumb.md +78 -0
- package/dist/agent-docs/components/Button.md +129 -0
- package/dist/agent-docs/components/Calendar.md +222 -0
- package/dist/agent-docs/components/Card.md +147 -0
- package/dist/agent-docs/components/Carousel.md +129 -0
- package/dist/agent-docs/components/Chart.md +75 -0
- package/dist/agent-docs/components/Chat.md +109 -0
- package/dist/agent-docs/components/ChatMessage.md +61 -0
- package/dist/agent-docs/components/Checkbox.md +135 -0
- package/dist/agent-docs/components/CircularProgress.md +49 -0
- package/dist/agent-docs/components/CodeHighlighter.md +31 -0
- package/dist/agent-docs/components/Collapsible.md +95 -0
- package/dist/agent-docs/components/Command.md +142 -0
- package/dist/agent-docs/components/Confirmer.md +175 -0
- package/dist/agent-docs/components/ContextMenu.md +191 -0
- package/dist/agent-docs/components/CopyButton.md +26 -0
- package/dist/agent-docs/components/DataCrossTable.md +94 -0
- package/dist/agent-docs/components/DataTable.md +254 -0
- package/dist/agent-docs/components/DatePicker.md +109 -0
- package/dist/agent-docs/components/Dialog.md +125 -0
- package/dist/agent-docs/components/Drawer.md +127 -0
- package/dist/agent-docs/components/DropdownMenu.md +57 -0
- package/dist/agent-docs/components/FilePreview.md +99 -0
- package/dist/agent-docs/components/FilePreviewer.md +139 -0
- package/dist/agent-docs/components/FileUploader.md +129 -0
- package/dist/agent-docs/components/Form.md +62 -0
- package/dist/agent-docs/components/FormComposer.md +137 -0
- package/dist/agent-docs/components/GanttChart.md +122 -0
- package/dist/agent-docs/components/HoverCard.md +37 -0
- package/dist/agent-docs/components/Icon.md +99 -0
- package/dist/agent-docs/components/Input.md +138 -0
- package/dist/agent-docs/components/InputOtp.md +40 -0
- package/dist/agent-docs/components/InputSelector.md +97 -0
- package/dist/agent-docs/components/InterruptPrompt.md +32 -0
- package/dist/agent-docs/components/Label.md +28 -0
- package/dist/agent-docs/components/MarkdownRenderer.md +36 -0
- package/dist/agent-docs/components/Menubar.md +164 -0
- package/dist/agent-docs/components/MessageInput.md +131 -0
- package/dist/agent-docs/components/MessageList.md +96 -0
- package/dist/agent-docs/components/MultipleSelector.md +146 -0
- package/dist/agent-docs/components/NavigationMenu.md +51 -0
- package/dist/agent-docs/components/Pagination.md +55 -0
- package/dist/agent-docs/components/Popover.md +103 -0
- package/dist/agent-docs/components/Progress.md +30 -0
- package/dist/agent-docs/components/PromptSuggestions.md +33 -0
- package/dist/agent-docs/components/RadioGroup.md +90 -0
- package/dist/agent-docs/components/Resizable.md +35 -0
- package/dist/agent-docs/components/ResizePrompt.md +13 -0
- package/dist/agent-docs/components/ScrollArea.md +49 -0
- package/dist/agent-docs/components/SecurePdfViewer.md +38 -0
- package/dist/agent-docs/components/Select.md +132 -0
- package/dist/agent-docs/components/Separator.md +32 -0
- package/dist/agent-docs/components/Sheet.md +40 -0
- package/dist/agent-docs/components/ShikiHighlighter.md +31 -0
- package/dist/agent-docs/components/Sidebar.md +85 -0
- package/dist/agent-docs/components/Skeleton.md +29 -0
- package/dist/agent-docs/components/Slider.md +58 -0
- package/dist/agent-docs/components/Sonner.md +21 -0
- package/dist/agent-docs/components/Spinner.md +139 -0
- package/dist/agent-docs/components/Stepper.md +67 -0
- package/dist/agent-docs/components/Switch.md +42 -0
- package/dist/agent-docs/components/Table.md +63 -0
- package/dist/agent-docs/components/TableSkeleton.md +46 -0
- package/dist/agent-docs/components/Tabs.md +86 -0
- package/dist/agent-docs/components/TextArea.md +52 -0
- package/dist/agent-docs/components/ThemeSwitcher.md +69 -0
- package/dist/agent-docs/components/Toaster.md +23 -0
- package/dist/agent-docs/components/Toggle.md +31 -0
- package/dist/agent-docs/components/ToggleGroup.md +30 -0
- package/dist/agent-docs/components/Tooltip.md +91 -0
- package/dist/agent-docs/components/TypingIndicator.md +21 -0
- package/dist/agent-docs/components/Typo.md +65 -0
- package/dist/agent-docs/components/WeeklyCalendar.md +64 -0
- package/dist/agent-docs/components-list.md +144 -0
- package/dist/components/ui/spinner.js +67 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +363 -361
- package/dist/node_modules/eventemitter3/index2.js +1 -1
- package/dist/node_modules/style-to-object/cjs/index.js +1 -1
- package/dist/styles.v3.css +1 -1
- package/package.json +3 -2
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
# AppSelect
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
A flexible select component supporting both single and multiple selection modes with advanced features including search, grouping, custom labels (JSX), clear actions, chips display, and optional item creation. Built on top of Command and Popover components.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Types
|
|
10
|
+
|
|
11
|
+
### AppSelectOption
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
export interface AppSelectOption {
|
|
15
|
+
value: string | number; // Unique identifier for the option
|
|
16
|
+
label: string | React.ReactNode; // Display label (can be JSX for custom rendering)
|
|
17
|
+
disabled?: boolean; // Disables selection of this option
|
|
18
|
+
group?: string; // Group name for grouping options
|
|
19
|
+
fixed?: boolean; // In chip mode, prevents removal of this option
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Props
|
|
26
|
+
|
|
27
|
+
The component uses discriminated union types based on the `multiple` prop.
|
|
28
|
+
|
|
29
|
+
### Base Props (Common to both modes)
|
|
30
|
+
|
|
31
|
+
| Prop | Type | Default | Description |
|
|
32
|
+
| -------------------- | ------------------------------ | ------------------------------------------------ | --------------------------------------------- |
|
|
33
|
+
| `options` | `AppSelectOption[]` | **required** | Array of selectable options |
|
|
34
|
+
| `placeholder` | `string` | `"Seleziona..."` | Text shown in trigger when no selection |
|
|
35
|
+
| `searchPlaceholder` | `string` | `"Cerca..."` | Placeholder for search input |
|
|
36
|
+
| `emptyPlaceholder` | `string` | `"Nessun risultato"` | Text shown when no options match search |
|
|
37
|
+
| `addItemPlaceholder` | `string` | `"Aggiungi"` | Text prefix for creating new items |
|
|
38
|
+
| `itemCountMessage` | `(selected: number) => string` | `(n) => "${n} elementi selezionati"` | Message function for selected count display |
|
|
39
|
+
| `maxSelectedMessage` | `(max: number) => string` | `(m) => "Puoi selezionare fino a ${m} elementi"` | Message shown when max selection reached |
|
|
40
|
+
| `label` | `string \| React.ReactNode` | `undefined` | Label displayed above the select |
|
|
41
|
+
| `className` | `string` | `""` | Additional Tailwind classes for the trigger |
|
|
42
|
+
| `wrpClassName` | `string` | `""` | Additional Tailwind classes for the wrapper |
|
|
43
|
+
| `searchable` | `boolean` | `false` | Enables search/filter functionality |
|
|
44
|
+
| `creatable` | `boolean` | `false` | Allows creating new options from search input |
|
|
45
|
+
| `disabled` | `boolean` | `false` | Disables the entire select component |
|
|
46
|
+
| `groupBy` | `keyof AppSelectOption` | `"group"` | Property key used to group options |
|
|
47
|
+
| `size` | `"sm" \| "default" \| "lg"` | `"default"` | Size variant affecting height and text size |
|
|
48
|
+
| `onClear` | `() => void` | `undefined` | Callback fired when clear button is clicked |
|
|
49
|
+
|
|
50
|
+
### Single Select Props
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
export type SingleSelectProps = BaseProps & {
|
|
54
|
+
multiple?: false; // Single selection mode (default)
|
|
55
|
+
value?: string | number; // Controlled value
|
|
56
|
+
defaultValue?: string | number; // Uncontrolled default value
|
|
57
|
+
onValueChange?: (value: string | number | undefined) => void; // Value change callback
|
|
58
|
+
isSingleSelectClearable?: boolean; // Shows clear button (default: false)
|
|
59
|
+
};
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Multi Select Props
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
export type MultiSelectProps = BaseProps & {
|
|
66
|
+
multiple: true; // Multiple selection mode
|
|
67
|
+
value?: (string | number)[]; // Controlled values array
|
|
68
|
+
defaultValue?: (string | number)[]; // Uncontrolled default values
|
|
69
|
+
onValueChange?: (value: (string | number)[]) => void; // Value change callback
|
|
70
|
+
maxSelected?: number; // Maximum number of selectable items
|
|
71
|
+
showChipsInsteadOfCount?: boolean; // Display chips instead of count (default: false)
|
|
72
|
+
};
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Combined Type
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
export type AppSelectProps = SingleSelectProps | MultiSelectProps;
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Prop Details
|
|
84
|
+
|
|
85
|
+
### Size Variants
|
|
86
|
+
|
|
87
|
+
The `size` prop controls the trigger height and text size:
|
|
88
|
+
|
|
89
|
+
- **`sm`**: `min-h-8 h-8 text-xs py-1.5`
|
|
90
|
+
- **`default`**: `min-h-9 h-9 text-sm py-2`
|
|
91
|
+
- **`lg`**: `min-h-10 h-10 text-base py-2`
|
|
92
|
+
|
|
93
|
+
### Custom Labels (JSX Support)
|
|
94
|
+
|
|
95
|
+
The `label` property in `AppSelectOption` supports both strings and React nodes, allowing for rich custom rendering with icons, badges, or any JSX content. When using JSX labels, the component properly handles them without wrapping issues (React 19 compatible).
|
|
96
|
+
|
|
97
|
+
### Controlled vs Uncontrolled
|
|
98
|
+
|
|
99
|
+
- **Controlled**: Component is controlled when the `value` prop is provided (checked via `props.hasOwnProperty('value')`)
|
|
100
|
+
- **Uncontrolled**: Uses internal state when only `defaultValue` is provided
|
|
101
|
+
- The component remains in its initial mode throughout its lifecycle
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Behavior
|
|
106
|
+
|
|
107
|
+
### Selection Modes
|
|
108
|
+
|
|
109
|
+
#### Single Select
|
|
110
|
+
|
|
111
|
+
- Clicking an option selects it and closes the popover
|
|
112
|
+
- Only one option can be selected at a time
|
|
113
|
+
- Clear button appears when `isSingleSelectClearable={true}` and a value is selected
|
|
114
|
+
- Clearing sets value to `undefined`
|
|
115
|
+
|
|
116
|
+
#### Multiple Select
|
|
117
|
+
|
|
118
|
+
- Clicking an option toggles its selection state
|
|
119
|
+
- Popover remains open for multiple selections
|
|
120
|
+
- Selected count or chips displayed in trigger
|
|
121
|
+
- Clear button always visible when selections exist
|
|
122
|
+
- Clearing sets value to empty array `[]`
|
|
123
|
+
|
|
124
|
+
### Search Functionality
|
|
125
|
+
|
|
126
|
+
When `searchable={true}`:
|
|
127
|
+
|
|
128
|
+
- Search input appears at the top of the popover
|
|
129
|
+
- Options are filtered client-side based on label text
|
|
130
|
+
- Search is case-insensitive
|
|
131
|
+
- Empty state shows `emptyPlaceholder` when no matches
|
|
132
|
+
|
|
133
|
+
### Creatable Options
|
|
134
|
+
|
|
135
|
+
When `creatable={true}`:
|
|
136
|
+
|
|
137
|
+
- Requires `searchable={true}` to work
|
|
138
|
+
- Shows "Aggiungi '[input]'" option when search text doesn't match existing options
|
|
139
|
+
- Selecting the create option adds the input text as a new value
|
|
140
|
+
- In single mode, closes popover after creation
|
|
141
|
+
- In multiple mode, keeps popover open for more selections
|
|
142
|
+
|
|
143
|
+
### Grouping
|
|
144
|
+
|
|
145
|
+
When options have a `group` property:
|
|
146
|
+
|
|
147
|
+
- Options are automatically grouped under headings
|
|
148
|
+
- Groups are rendered in the order they appear in the options array
|
|
149
|
+
- Use `groupBy` prop to specify which property to group by (default: `"group"`)
|
|
150
|
+
- Options without a group value appear in an unnamed group
|
|
151
|
+
|
|
152
|
+
### Max Selection Limit
|
|
153
|
+
|
|
154
|
+
In multiple mode with `maxSelected` set:
|
|
155
|
+
|
|
156
|
+
- Prevents selecting more than the specified number of items
|
|
157
|
+
- Unselected options become disabled when limit is reached
|
|
158
|
+
- Footer message appears showing the limit via `maxSelectedMessage`
|
|
159
|
+
- Selected items can still be deselected
|
|
160
|
+
|
|
161
|
+
### Chips Display
|
|
162
|
+
|
|
163
|
+
In multiple mode with `showChipsInsteadOfCount={true}`:
|
|
164
|
+
|
|
165
|
+
- Shows individual badge chips for each selected item
|
|
166
|
+
- Each chip has an X button to remove the item
|
|
167
|
+
- Options with `fixed={true}` show chips without X button (cannot be removed)
|
|
168
|
+
- Chips are scrollable horizontally if they overflow
|
|
169
|
+
|
|
170
|
+
### Disabled Options
|
|
171
|
+
|
|
172
|
+
Options with `disabled={true}`:
|
|
173
|
+
|
|
174
|
+
- Appear grayed out with reduced opacity
|
|
175
|
+
- Cannot be selected or deselected
|
|
176
|
+
- Cursor changes to `not-allowed`
|
|
177
|
+
|
|
178
|
+
### Disabled Component
|
|
179
|
+
|
|
180
|
+
When `disabled={true}`:
|
|
181
|
+
|
|
182
|
+
- Entire component is non-interactive
|
|
183
|
+
- Trigger shows reduced opacity
|
|
184
|
+
- Popover cannot be opened
|
|
185
|
+
- Cursor changes to `not-allowed`
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Examples
|
|
190
|
+
|
|
191
|
+
### Basic Single Select
|
|
192
|
+
|
|
193
|
+
```tsx
|
|
194
|
+
import { AppSelect } from "laif-ds";
|
|
195
|
+
import { useState } from "react";
|
|
196
|
+
|
|
197
|
+
const options = [
|
|
198
|
+
{ value: "react", label: "React" },
|
|
199
|
+
{ value: "vue", label: "Vue" },
|
|
200
|
+
{ value: "angular", label: "Angular" },
|
|
201
|
+
];
|
|
202
|
+
|
|
203
|
+
export function BasicSelect() {
|
|
204
|
+
const [value, setValue] = useState<string | number | undefined>();
|
|
205
|
+
|
|
206
|
+
return (
|
|
207
|
+
<AppSelect
|
|
208
|
+
options={options}
|
|
209
|
+
value={value}
|
|
210
|
+
onValueChange={setValue}
|
|
211
|
+
placeholder="Select a framework"
|
|
212
|
+
label="Framework"
|
|
213
|
+
/>
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Multiple Select with Search and Chips
|
|
219
|
+
|
|
220
|
+
```tsx
|
|
221
|
+
import { AppSelect } from "laif-ds";
|
|
222
|
+
import { useState } from "react";
|
|
223
|
+
|
|
224
|
+
const options = [
|
|
225
|
+
{ value: 1, label: "React" },
|
|
226
|
+
{ value: 2, label: "Vue" },
|
|
227
|
+
{ value: 3, label: "Angular" },
|
|
228
|
+
{ value: 4, label: "Svelte" },
|
|
229
|
+
];
|
|
230
|
+
|
|
231
|
+
export function MultiSelectWithChips() {
|
|
232
|
+
const [values, setValues] = useState<(string | number)[]>([]);
|
|
233
|
+
|
|
234
|
+
return (
|
|
235
|
+
<AppSelect
|
|
236
|
+
multiple
|
|
237
|
+
options={options}
|
|
238
|
+
value={values}
|
|
239
|
+
onValueChange={setValues}
|
|
240
|
+
placeholder="Select frameworks"
|
|
241
|
+
label="Frameworks"
|
|
242
|
+
searchable
|
|
243
|
+
showChipsInsteadOfCount
|
|
244
|
+
/>
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Grouped Options with Max Selection
|
|
250
|
+
|
|
251
|
+
```tsx
|
|
252
|
+
import { AppSelect } from "laif-ds";
|
|
253
|
+
import { useState } from "react";
|
|
254
|
+
|
|
255
|
+
const options = [
|
|
256
|
+
{ value: "react", label: "React", group: "Frontend" },
|
|
257
|
+
{ value: "vue", label: "Vue", group: "Frontend" },
|
|
258
|
+
{ value: "node", label: "Node.js", group: "Backend" },
|
|
259
|
+
{ value: "django", label: "Django", group: "Backend" },
|
|
260
|
+
];
|
|
261
|
+
|
|
262
|
+
export function GroupedSelect() {
|
|
263
|
+
const [values, setValues] = useState<(string | number)[]>([]);
|
|
264
|
+
|
|
265
|
+
return (
|
|
266
|
+
<AppSelect
|
|
267
|
+
multiple
|
|
268
|
+
options={options}
|
|
269
|
+
value={values}
|
|
270
|
+
onValueChange={setValues}
|
|
271
|
+
placeholder="Select up to 2 technologies"
|
|
272
|
+
label="Tech Stack"
|
|
273
|
+
groupBy="group"
|
|
274
|
+
maxSelected={2}
|
|
275
|
+
searchable
|
|
276
|
+
/>
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### Creatable Select
|
|
282
|
+
|
|
283
|
+
```tsx
|
|
284
|
+
import { AppSelect } from "laif-ds";
|
|
285
|
+
import { useState } from "react";
|
|
286
|
+
|
|
287
|
+
const initialOptions = [
|
|
288
|
+
{ value: "tag1", label: "Tag 1" },
|
|
289
|
+
{ value: "tag2", label: "Tag 2" },
|
|
290
|
+
];
|
|
291
|
+
|
|
292
|
+
export function CreatableSelect() {
|
|
293
|
+
const [values, setValues] = useState<(string | number)[]>([]);
|
|
294
|
+
|
|
295
|
+
return (
|
|
296
|
+
<AppSelect
|
|
297
|
+
multiple
|
|
298
|
+
options={initialOptions}
|
|
299
|
+
value={values}
|
|
300
|
+
onValueChange={setValues}
|
|
301
|
+
placeholder="Create or select tags"
|
|
302
|
+
label="Tags"
|
|
303
|
+
searchable
|
|
304
|
+
creatable
|
|
305
|
+
showChipsInsteadOfCount
|
|
306
|
+
/>
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Custom JSX Labels
|
|
312
|
+
|
|
313
|
+
```tsx
|
|
314
|
+
import { AppSelect } from "laif-ds";
|
|
315
|
+
import { useState } from "react";
|
|
316
|
+
|
|
317
|
+
const options = [
|
|
318
|
+
{
|
|
319
|
+
value: "react",
|
|
320
|
+
label: (
|
|
321
|
+
<div className="flex items-center gap-2">
|
|
322
|
+
<span className="text-blue-600">⚛</span>
|
|
323
|
+
React
|
|
324
|
+
</div>
|
|
325
|
+
),
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
value: "vue",
|
|
329
|
+
label: (
|
|
330
|
+
<div className="flex items-center gap-2">
|
|
331
|
+
<span className="text-green-600">●</span>
|
|
332
|
+
Vue
|
|
333
|
+
</div>
|
|
334
|
+
),
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
value: "angular",
|
|
338
|
+
label: (
|
|
339
|
+
<div className="flex items-center gap-2">
|
|
340
|
+
<span className="text-red-600">▲</span>
|
|
341
|
+
Angular
|
|
342
|
+
</div>
|
|
343
|
+
),
|
|
344
|
+
},
|
|
345
|
+
];
|
|
346
|
+
|
|
347
|
+
export function CustomLabelsSelect() {
|
|
348
|
+
const [value, setValue] = useState<string | number | undefined>();
|
|
349
|
+
|
|
350
|
+
return (
|
|
351
|
+
<AppSelect
|
|
352
|
+
options={options}
|
|
353
|
+
value={value}
|
|
354
|
+
onValueChange={setValue}
|
|
355
|
+
placeholder="Select a framework"
|
|
356
|
+
label="Framework with Icons"
|
|
357
|
+
/>
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### Clearable Single Select
|
|
363
|
+
|
|
364
|
+
```tsx
|
|
365
|
+
import { AppSelect } from "laif-ds";
|
|
366
|
+
import { useState } from "react";
|
|
367
|
+
|
|
368
|
+
const options = [
|
|
369
|
+
{ value: 1, label: "Option 1" },
|
|
370
|
+
{ value: 2, label: "Option 2" },
|
|
371
|
+
{ value: 3, label: "Option 3" },
|
|
372
|
+
];
|
|
373
|
+
|
|
374
|
+
export function ClearableSelect() {
|
|
375
|
+
const [value, setValue] = useState<string | number | undefined>(1);
|
|
376
|
+
|
|
377
|
+
return (
|
|
378
|
+
<AppSelect
|
|
379
|
+
options={options}
|
|
380
|
+
value={value}
|
|
381
|
+
onValueChange={setValue}
|
|
382
|
+
placeholder="Select an option"
|
|
383
|
+
label="Status"
|
|
384
|
+
isSingleSelectClearable
|
|
385
|
+
onClear={() => console.log("Cleared!")}
|
|
386
|
+
/>
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
### With Disabled Options
|
|
392
|
+
|
|
393
|
+
```tsx
|
|
394
|
+
import { AppSelect } from "laif-ds";
|
|
395
|
+
import { useState } from "react";
|
|
396
|
+
|
|
397
|
+
const options = [
|
|
398
|
+
{ value: "it", label: "Italia" },
|
|
399
|
+
{ value: "fr", label: "Francia" },
|
|
400
|
+
{ value: "de", label: "Germania" },
|
|
401
|
+
{ value: "uk", label: "Regno Unito", disabled: true },
|
|
402
|
+
];
|
|
403
|
+
|
|
404
|
+
export function SelectWithDisabled() {
|
|
405
|
+
const [value, setValue] = useState<string | number | undefined>();
|
|
406
|
+
|
|
407
|
+
return (
|
|
408
|
+
<AppSelect
|
|
409
|
+
options={options}
|
|
410
|
+
value={value}
|
|
411
|
+
onValueChange={setValue}
|
|
412
|
+
placeholder="Select a country"
|
|
413
|
+
label="Country"
|
|
414
|
+
/>
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
---
|
|
420
|
+
|
|
421
|
+
## Notes
|
|
422
|
+
|
|
423
|
+
- **React 19 Compatible**: Properly handles JSX labels without ref warnings
|
|
424
|
+
- **Responsive Width**: Popover automatically matches trigger width
|
|
425
|
+
- **Keyboard Navigation**: Full keyboard support via Command component
|
|
426
|
+
- **Accessibility**: Proper ARIA attributes and focus management
|
|
427
|
+
- **Performance**: Efficient rendering with useMemo for computed values
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# AppSidebar
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
High-level app sidebar built on top of `Sidebar` primitives. Renders navigation groups, nested items, header/footer content, optional versions area and an optional rail for resizing/collapsing.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Props
|
|
10
|
+
|
|
11
|
+
| Prop | Type | Default | Description |
|
|
12
|
+
| ------------------ | ------------------------------------ | ----------- | ----------- |
|
|
13
|
+
| `navigation` | `NavGroup[]` | **required**| Main navigation groups. |
|
|
14
|
+
| `navigationFooter` | `NavGroup[]` | `undefined` | Footer navigation groups. |
|
|
15
|
+
| `versions` | `string[]` | `undefined` | Optional versions list. |
|
|
16
|
+
| `defaultVersion` | `string` | `undefined` | Default selected version. |
|
|
17
|
+
| `headerContent` | `React.ReactNode` | `undefined` | Custom content at the top. |
|
|
18
|
+
| `footerContent` | `React.ReactNode` | `undefined` | Custom content at the bottom. |
|
|
19
|
+
| `showRail` | `boolean` | `true` | Show the draggable sidebar rail. |
|
|
20
|
+
| `linkComponent` | `React.ComponentType<any>` | `undefined` | Custom link component for items. |
|
|
21
|
+
| `linkProps` | `Record<string, any>` | `{}` | Extra props passed to the link component. |
|
|
22
|
+
|
|
23
|
+
`NavGroup` → `{ title: string; url?: string; items: NavItem[] }`
|
|
24
|
+
|
|
25
|
+
`NavItem` → `{ title: string; url: string; isActive?: boolean; iconName?: IconName; subItems?: NavSubItem[] }`
|
|
26
|
+
|
|
27
|
+
`NavSubItem` → `{ title: string; url: string; isActive?: boolean; iconName?: IconName }`
|
|
28
|
+
|
|
29
|
+
Inherits other props from `Sidebar` via `React.ComponentProps<typeof Sidebar>`.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Behavior
|
|
34
|
+
|
|
35
|
+
- **Collapsible groups**: Clicking items with `subItems` toggles nested lists.
|
|
36
|
+
- **Active state**: `isActive` highlights and expands relevant items/groups.
|
|
37
|
+
- **Links**: Use `linkComponent` for framework routers (e.g., Next.js `Link`).
|
|
38
|
+
- **Rail**: `showRail` displays a thin draggable handle to collapse/expand.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Examples
|
|
43
|
+
|
|
44
|
+
### Basic
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
import { SidebarProvider, SidebarTrigger } from "laif-ds";
|
|
48
|
+
import { AppSidebar } from "laif-ds";
|
|
49
|
+
|
|
50
|
+
const navigation = [
|
|
51
|
+
{
|
|
52
|
+
title: "Generale",
|
|
53
|
+
items: [
|
|
54
|
+
{ title: "Dashboard", url: "#", isActive: true, iconName: "Home" },
|
|
55
|
+
{ title: "Utenti", url: "#", iconName: "Users" },
|
|
56
|
+
{
|
|
57
|
+
title: "Messaggi",
|
|
58
|
+
url: "#",
|
|
59
|
+
iconName: "MessageSquare",
|
|
60
|
+
subItems: [
|
|
61
|
+
{ title: "Inbox", url: "#inbox", iconName: "Inbox" },
|
|
62
|
+
{ title: "Inviati", url: "#sent", iconName: "Send" },
|
|
63
|
+
],
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
},
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
export function Layout() {
|
|
70
|
+
return (
|
|
71
|
+
<SidebarProvider>
|
|
72
|
+
<div className="flex h-[100vh] overflow-hidden">
|
|
73
|
+
<AppSidebar navigation={navigation} showRail />
|
|
74
|
+
<div className="flex-1 overflow-auto p-4">
|
|
75
|
+
<SidebarTrigger />
|
|
76
|
+
{/* Page content */}
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
</SidebarProvider>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### With Header/Footer Content
|
|
85
|
+
|
|
86
|
+
```tsx
|
|
87
|
+
import { Button, Input } from "laif-ds";
|
|
88
|
+
|
|
89
|
+
export function WithHeaderFooter() {
|
|
90
|
+
return (
|
|
91
|
+
<SidebarProvider>
|
|
92
|
+
<div className="flex h-[100vh] overflow-hidden">
|
|
93
|
+
<AppSidebar
|
|
94
|
+
navigation={[]}
|
|
95
|
+
headerContent={
|
|
96
|
+
<div className="p-4">
|
|
97
|
+
<Input iconLeft="Search" placeholder="Cerca..." className="bg-d-background pl-8" />
|
|
98
|
+
</div>
|
|
99
|
+
}
|
|
100
|
+
footerContent={
|
|
101
|
+
<div className="border-d-border border-t p-4">
|
|
102
|
+
<Button variant="ghost" className="w-full justify-start" iconLeft="Settings">
|
|
103
|
+
Impostazioni
|
|
104
|
+
</Button>
|
|
105
|
+
</div>
|
|
106
|
+
}
|
|
107
|
+
/>
|
|
108
|
+
<div className="flex-1 overflow-auto p-4" />
|
|
109
|
+
</div>
|
|
110
|
+
</SidebarProvider>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Notes
|
|
118
|
+
|
|
119
|
+
- **Routing**: Prefer `linkComponent` to avoid full page reloads (e.g., `next/link`).
|
|
120
|
+
- **Icons**: Use `laif-ds` `Icon` by passing `iconName` in navigation items.
|
|
121
|
+
- **Accessibility**: Sidebar primitives manage keyboard navigation and focus.
|
|
122
|
+
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# AppStepper
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
High-level stepper built on `Stepper` primitives. Renders labeled steps with indicators and panels, supports horizontal/vertical layouts, separators, controlled navigation and custom click behavior.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Props
|
|
10
|
+
|
|
11
|
+
| Prop | Type | Default | Description |
|
|
12
|
+
| ------------------------ | ------------------------------------------------ | ------------- | ----------- |
|
|
13
|
+
| `steps` | `{ id: number; label: ReactNode; component: ReactNode; completed?; disabled?; loading?; }[]` | **required** | Steps to render (id is the step value). |
|
|
14
|
+
| `align` | `"horizontal" | "vertical"` | `"horizontal"` | Layout orientation. |
|
|
15
|
+
| `size` | `"sm" | "md"` | `"sm"` | Indicator/title sizing. |
|
|
16
|
+
| `showSeparators` | `boolean` | `true` | Show separators between steps. |
|
|
17
|
+
| `value` | `number` | first step id | Current step value (controlled). |
|
|
18
|
+
| `onValueChange` | `(value: number) => void` | `undefined` | Called when step changes. |
|
|
19
|
+
| `defaultStep` | `number` | `undefined` | Initial step if `value` is not provided. |
|
|
20
|
+
| `allowStepNavigation` | `boolean` | `true` | If `false`, steps are not clickable. |
|
|
21
|
+
| `allowClickOnlyCompleted`| `boolean` | `false` | If `true`, only completed steps are clickable. |
|
|
22
|
+
| `onStepClick` | `(step, index) => void` | `undefined` | Custom click handler; if provided, handles activation manually. |
|
|
23
|
+
| `indicators` | `{ active?; completed?; inactive?; loading? }` | `undefined` | Custom indicator content per state. |
|
|
24
|
+
|
|
25
|
+
Inherits the rest of `StepperProps` (e.g., `orientation` is controlled by `align`).
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Behavior
|
|
30
|
+
|
|
31
|
+
- **Controlled**: Always passes a `value` to `Stepper`. Update via `onValueChange`.
|
|
32
|
+
- **Clickable steps**: Governed by `allowStepNavigation` and `allowClickOnlyCompleted`.
|
|
33
|
+
- **Indicators**: Customize the indicator content through `indicators`.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Examples
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
import * as React from "react";
|
|
41
|
+
import { AppStepper } from "laif-ds";
|
|
42
|
+
|
|
43
|
+
const steps = [
|
|
44
|
+
{ id: 1, label: "Account Setup", component: <div>Step 1</div> },
|
|
45
|
+
{ id: 2, label: "Profile Information", component: <div>Step 2</div> },
|
|
46
|
+
{ id: 3, label: "Preferences", component: <div>Step 3</div> },
|
|
47
|
+
] as const;
|
|
48
|
+
|
|
49
|
+
export function HorizontalStepper() {
|
|
50
|
+
const [current, setCurrent] = React.useState(1);
|
|
51
|
+
return (
|
|
52
|
+
<AppStepper
|
|
53
|
+
steps={steps}
|
|
54
|
+
value={current}
|
|
55
|
+
onValueChange={setCurrent}
|
|
56
|
+
align="horizontal"
|
|
57
|
+
size="md"
|
|
58
|
+
showSeparators
|
|
59
|
+
/>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function VerticalStepper() {
|
|
64
|
+
const [current, setCurrent] = React.useState(1);
|
|
65
|
+
return (
|
|
66
|
+
<AppStepper steps={steps} value={current} onValueChange={setCurrent} align="vertical" />
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Notes
|
|
74
|
+
|
|
75
|
+
- **Accessibility**: Based on `Stepper` primitives which manage keyboard and focus.
|
|
76
|
+
- **Custom click logic**: Provide `onStepClick` to intercept clicks (e.g., validation before navigation).
|
|
77
|
+
|