laif-ds 0.2.65 → 0.2.66

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
@@ -1,26 +1,37 @@
1
- ## PUBBLICARE:
1
+ # Laif Design System
2
2
 
3
- npm login
4
- npm whoami (verifica se sei loggato con accoutn corretto)
5
- yarn build (farà partire build per tailwindv3 e tailwindv4)
6
- npm publish --access public
3
+ ![NPM Version](https://img.shields.io/npm/v/laif-ds?style=flat&logo=npm&link=https%3A%2F%2Fwww.npmjs.com%2Fpackage%2Flaif-ds)
7
4
 
8
- ## INSTALL:
5
+ ## Install Laif DS on your project:
9
6
 
7
+ ```sh
10
8
  npm i @laif/ds
9
+ ```
11
10
 
12
- ## HOW IT WORKS
11
+ ## Development mode
13
12
 
14
13
  Per vedere i componenti con storybook:
15
14
 
16
- ```
15
+ ```sh
17
16
  yarn storybook
18
17
  ```
19
18
 
20
19
  Il comando esegue due operazioni in sequenza: prima compila il CSS con tailwindcss v4 salvandolo in un file output.css, poi avvia Storybook che utilizza questo file CSS precompilato.
21
20
  Questo approccio è necessario con Tailwind CSS v4 perché, a differenza delle versioni precedenti, v4 utilizza un'architettura "zero-runtime" che non si integra più direttamente con Storybook. La generazione separata del CSS è quindi la soluzione raccomandata per utilizzare Tailwind v4 con Storybook.
22
21
 
23
- ## COME FUNZIONANO LE STORIES
22
+ ## Publish on NPM:
23
+
24
+ ```sh
25
+ npm login
26
+
27
+ npm whoami # verifica se sei loggato con account corretto
28
+
29
+ yarn build # farà partire build per tailwindv3 e tailwindv4
30
+
31
+ npm publish --access public
32
+ ```
33
+
34
+ ## How stories work
24
35
 
25
36
  Le stories in Storybook sono il modo in cui documentiamo e testiamo visivamente i nostri componenti. Ogni story rappresenta uno stato specifico di un componente.
26
37
 
@@ -82,7 +93,7 @@ export const Secondary: Story = {
82
93
  // Altre stories...
83
94
  ```
84
95
 
85
- ### Come creare una nuova Story
96
+ ### How to create a new story
86
97
 
87
98
  1. Crea un nuovo file nella directory `src/components/stories/` con nome `nome-componente.stories.tsx`
88
99
  2. Importa il componente e le dipendenze necessarie
@@ -0,0 +1,225 @@
1
+ # AppCheckbox
2
+
3
+ ## Overview
4
+
5
+ Enhanced checkbox group component with support for icons, descriptions, card layout, horizontal/vertical orientation, and comprehensive error handling. Built on top of the base Checkbox component with additional styling and features.
6
+
7
+ ---
8
+
9
+ ## Types
10
+
11
+ ### AppCheckboxOption
12
+
13
+ ```ts
14
+ export interface AppCheckboxOption {
15
+ value: string; // Unique value for the option
16
+ label: string | React.ReactNode; // Display label (can be JSX)
17
+ description?: string | React.ReactNode; // Optional description text
18
+ disabled?: boolean; // Disables this specific option
19
+ icon?: IconName; // Optional icon name
20
+ }
21
+ ```
22
+
23
+ ---
24
+
25
+ ## Props
26
+
27
+ | Prop | Type | Default | Description |
28
+ | ----------------- | ---------------------------- | ------------ | ----------------------------------------------- |
29
+ | `options` | `AppCheckboxOption[]` | **required** | Array of checkbox options |
30
+ | `value` | `string[]` | `undefined` | Controlled value (array of selected values) |
31
+ | `defaultValue` | `string[]` | `undefined` | Uncontrolled default value |
32
+ | `onValueChange` | `(value: string[]) => void` | `undefined` | Callback when value changes |
33
+ | `label` | `string \| React.ReactNode` | `undefined` | Label for the checkbox group |
34
+ | `description` | `string \| React.ReactNode` | `undefined` | Description text below label |
35
+ | `disabled` | `boolean` | `false` | Disables all options |
36
+ | `required` | `boolean` | `false` | Shows required asterisk |
37
+ | `name` | `string` | `undefined` | Form name attribute |
38
+ | `orientation` | `"horizontal" \| "vertical"` | `"vertical"` | Layout orientation |
39
+ | `className` | `string` | `""` | Additional classes for checkbox group container |
40
+ | `wrpClassName` | `string` | `""` | Additional classes for outer wrapper |
41
+ | `optionClassName` | `string` | `""` | Additional classes for each option |
42
+ | `layout` | `"default" \| "card"` | `"default"` | Visual layout style |
43
+ | `error` | `string` | `undefined` | Error message to display |
44
+
45
+ ---
46
+
47
+ ## Behavior
48
+
49
+ - **Controlled/Uncontrolled**: Supports both controlled (`value` + `onValueChange`) and uncontrolled (`defaultValue`) modes
50
+ - **Multiple Selection**: Users can select multiple options simultaneously
51
+ - **Icons**: Automatically renders icons when provided in options
52
+ - **Card Layout**: In card mode, options have border, shadow, and background styling
53
+ - **Error State**: Red text for label and error message display when `error` prop is provided
54
+ - **Disabled State**: Applies opacity and cursor changes to disabled options
55
+
56
+ ---
57
+
58
+ ## Examples
59
+
60
+ ### Basic Usage
61
+
62
+ ```tsx
63
+ import { AppCheckbox } from "laif-ds";
64
+ import { useState } from "react";
65
+
66
+ export function BasicCheckbox() {
67
+ const [value, setValue] = useState(["option1"]);
68
+
69
+ return (
70
+ <AppCheckbox
71
+ label="Choose options"
72
+ options={[
73
+ { value: "option1", label: "Option 1" },
74
+ { value: "option2", label: "Option 2" },
75
+ { value: "option3", label: "Option 3" },
76
+ ]}
77
+ value={value}
78
+ onValueChange={setValue}
79
+ />
80
+ );
81
+ }
82
+ ```
83
+
84
+ ### With Icons and Descriptions
85
+
86
+ ```tsx
87
+ import { AppCheckbox } from "laif-ds";
88
+ import { useState } from "react";
89
+
90
+ export function CheckboxWithIcons() {
91
+ const [value, setValue] = useState(["email"]);
92
+
93
+ return (
94
+ <AppCheckbox
95
+ label="Notification Preferences"
96
+ description="Choose how you want to receive notifications"
97
+ options={[
98
+ {
99
+ value: "email",
100
+ label: "Email",
101
+ description: "Receive notifications via email",
102
+ icon: "Mail",
103
+ },
104
+ {
105
+ value: "sms",
106
+ label: "SMS",
107
+ description: "Receive notifications via text message",
108
+ icon: "MessageSquare",
109
+ },
110
+ {
111
+ value: "push",
112
+ label: "Push",
113
+ description: "Receive push notifications",
114
+ icon: "Bell",
115
+ },
116
+ ]}
117
+ value={value}
118
+ onValueChange={setValue}
119
+ />
120
+ );
121
+ }
122
+ ```
123
+
124
+ ### Card Layout
125
+
126
+ ```tsx
127
+ import { AppCheckbox } from "laif-ds";
128
+ import { useState } from "react";
129
+
130
+ export function CardCheckbox() {
131
+ const [value, setValue] = useState(["feature1"]);
132
+
133
+ return (
134
+ <AppCheckbox
135
+ label="Select features"
136
+ layout="card"
137
+ options={[
138
+ {
139
+ value: "feature1",
140
+ label: "Advanced Analytics",
141
+ description: "Detailed insights and reports",
142
+ icon: "BarChart",
143
+ },
144
+ {
145
+ value: "feature2",
146
+ label: "Team Collaboration",
147
+ description: "Work together in real-time",
148
+ icon: "Users",
149
+ },
150
+ {
151
+ value: "feature3",
152
+ label: "Priority Support",
153
+ description: "24/7 dedicated support",
154
+ icon: "Headphones",
155
+ },
156
+ ]}
157
+ value={value}
158
+ onValueChange={setValue}
159
+ />
160
+ );
161
+ }
162
+ ```
163
+
164
+ ### Horizontal Orientation
165
+
166
+ ```tsx
167
+ import { AppCheckbox } from "laif-ds";
168
+ import { useState } from "react";
169
+
170
+ export function HorizontalCheckbox() {
171
+ const [value, setValue] = useState(["small"]);
172
+
173
+ return (
174
+ <AppCheckbox
175
+ label="Available Sizes"
176
+ orientation="horizontal"
177
+ options={[
178
+ { value: "small", label: "Small" },
179
+ { value: "medium", label: "Medium" },
180
+ { value: "large", label: "Large" },
181
+ ]}
182
+ value={value}
183
+ onValueChange={setValue}
184
+ />
185
+ );
186
+ }
187
+ ```
188
+
189
+ ### With Error State
190
+
191
+ ```tsx
192
+ import { AppCheckbox } from "laif-ds";
193
+ import { useState } from "react";
194
+
195
+ export function CheckboxWithError() {
196
+ const [value, setValue] = useState<string[]>([]);
197
+
198
+ return (
199
+ <AppCheckbox
200
+ label="Terms and Conditions"
201
+ required
202
+ options={[
203
+ { value: "terms", label: "I accept the terms and conditions" },
204
+ { value: "privacy", label: "I accept the privacy policy" },
205
+ ]}
206
+ value={value}
207
+ onValueChange={setValue}
208
+ error={
209
+ value.length === 0 ? "You must accept at least one option" : undefined
210
+ }
211
+ />
212
+ );
213
+ }
214
+ ```
215
+
216
+ ---
217
+
218
+ ## Notes
219
+
220
+ - **JSX Labels**: Both `label` and option labels support JSX for custom rendering
221
+ - **Icon Support**: Uses the Icon component from laif-ds (Feather Icons)
222
+ - **Accessibility**: Includes proper ARIA attributes and keyboard navigation
223
+ - **Card Styling**: Card layout includes hover effects and focus states
224
+ - **Disabled Options**: Individual options can be disabled while keeping others enabled
225
+ - **Multiple Selection**: Unlike radio groups, checkboxes allow selecting multiple options
@@ -28,24 +28,27 @@ The component uses discriminated union types based on the `multiple` prop.
28
28
 
29
29
  ### Base Props (Common to both modes)
30
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 |
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
+ | `noGroupLabel` | `string` | `"Nessun gruppo"` | Label shown for options without a group |
39
+ | `itemCountMessage` | `(selected: number) => string` | `(n) => "${n} elementi selezionati"` | Message function for selected count display |
40
+ | `maxSelectedMessage` | `(max: number) => string` | `(m) => "Puoi selezionare fino a ${m} elementi"` | Message shown when max selection reached |
41
+ | `label` | `string \| React.ReactNode` | `undefined` | Label displayed above the select |
42
+ | `labelClassName` | `string` | `""` | Additional Tailwind classes for the label |
43
+ | `className` | `string` | `""` | Additional Tailwind classes for the trigger |
44
+ | `wrpClassName` | `string` | `""` | Additional Tailwind classes for the wrapper |
45
+ | `searchable` | `boolean` | `false` | Enables search/filter functionality |
46
+ | `creatable` | `boolean` | `false` | Allows creating new options from search input |
47
+ | `disabled` | `boolean` | `false` | Disables the entire select component |
48
+ | `size` | `"sm" \| "default" \| "lg"` | `"default"` | Size variant affecting height and text size |
49
+ | `onClear` | `() => void` | `undefined` | Callback fired when clear button is clicked |
50
+ | `id` | `string` | `undefined` | HTML id attribute for the trigger (useful for testing) |
51
+ | `data-testid` | `string` | `undefined` | Test identifier attribute for E2E testing (e.g. Playwright) |
49
52
 
50
53
  ### Single Select Props
51
54
 
@@ -55,6 +58,7 @@ export type SingleSelectProps = BaseProps & {
55
58
  value?: string | number; // Controlled value
56
59
  defaultValue?: string | number; // Uncontrolled default value
57
60
  onValueChange?: (value: string | number | undefined) => void; // Value change callback
61
+ renderValue?: (option: AppSelectOption) => React.ReactNode; // Custom render for selected value
58
62
  isSingleSelectClearable?: boolean; // Shows clear button (default: false)
59
63
  };
60
64
  ```
@@ -67,6 +71,7 @@ export type MultiSelectProps = BaseProps & {
67
71
  value?: (string | number)[]; // Controlled values array
68
72
  defaultValue?: (string | number)[]; // Uncontrolled default values
69
73
  onValueChange?: (value: (string | number)[]) => void; // Value change callback
74
+ renderValue?: (option: AppSelectOption) => React.ReactNode; // Custom render for selected values
70
75
  maxSelected?: number; // Maximum number of selectable items
71
76
  showChipsInsteadOfCount?: boolean; // Display chips instead of count (default: false)
72
77
  };
@@ -146,8 +151,8 @@ When options have a `group` property:
146
151
 
147
152
  - Options are automatically grouped under headings
148
153
  - 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
154
+ - Options without a group value (empty string, undefined, or missing) appear under the `noGroupLabel` heading
155
+ - Grouping is based on the `group` property of `AppSelectOption`
151
156
 
152
157
  ### Max Selection Limit
153
158
 
@@ -270,7 +275,6 @@ export function GroupedSelect() {
270
275
  onValueChange={setValues}
271
276
  placeholder="Select up to 2 technologies"
272
277
  label="Tech Stack"
273
- groupBy="group"
274
278
  maxSelected={2}
275
279
  searchable
276
280
  />
@@ -416,6 +420,37 @@ export function SelectWithDisabled() {
416
420
  }
417
421
  ```
418
422
 
423
+ ### Custom Render Value
424
+
425
+ ```tsx
426
+ import { AppSelect, AppSelectOption } from "laif-ds";
427
+ import { Badge } from "laif-ds";
428
+ import { useState } from "react";
429
+
430
+ const options = [
431
+ { value: "react", label: "React" },
432
+ { value: "vue", label: "Vue" },
433
+ { value: "angular", label: "Angular" },
434
+ ];
435
+
436
+ export function CustomRenderSelect() {
437
+ const [value, setValue] = useState<string | number | undefined>("react");
438
+
439
+ return (
440
+ <AppSelect
441
+ options={options}
442
+ value={value}
443
+ onValueChange={setValue}
444
+ placeholder="Select a framework"
445
+ label="Framework"
446
+ renderValue={(option: AppSelectOption) => (
447
+ <Badge variant="success">{option.label}</Badge>
448
+ )}
449
+ />
450
+ );
451
+ }
452
+ ```
453
+
419
454
  ---
420
455
 
421
456
  ## Notes
@@ -425,3 +460,5 @@ export function SelectWithDisabled() {
425
460
  - **Keyboard Navigation**: Full keyboard support via Command component
426
461
  - **Accessibility**: Proper ARIA attributes and focus management
427
462
  - **Performance**: Efficient rendering with useMemo for computed values
463
+ - **Custom Rendering**: Use `renderValue` prop to customize how selected options appear in the trigger
464
+ - **Controlled Component Detection**: Uses `props.hasOwnProperty('value')` to determine if component is controlled, ensuring stable behavior even when value is `undefined`