handy-fluentui 0.1.0

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.
Files changed (78) hide show
  1. package/README.md +681 -0
  2. package/dist/components/fui-button-panel.d.ts +13 -0
  3. package/dist/components/fui-button-panel.d.ts.map +1 -0
  4. package/dist/components/fui-image-carousell.d.ts +17 -0
  5. package/dist/components/fui-image-carousell.d.ts.map +1 -0
  6. package/dist/components/fui-tab.d.ts +29 -0
  7. package/dist/components/fui-tab.d.ts.map +1 -0
  8. package/dist/components/fui-table.d.ts +89 -0
  9. package/dist/components/fui-table.d.ts.map +1 -0
  10. package/dist/components/input-checkbox.d.ts +17 -0
  11. package/dist/components/input-checkbox.d.ts.map +1 -0
  12. package/dist/components/input-date.d.ts +27 -0
  13. package/dist/components/input-date.d.ts.map +1 -0
  14. package/dist/components/input-dropdown.d.ts +33 -0
  15. package/dist/components/input-dropdown.d.ts.map +1 -0
  16. package/dist/components/input-group.d.ts +25 -0
  17. package/dist/components/input-group.d.ts.map +1 -0
  18. package/dist/components/input-multi-lang.d.ts +35 -0
  19. package/dist/components/input-multi-lang.d.ts.map +1 -0
  20. package/dist/components/input-number.d.ts +39 -0
  21. package/dist/components/input-number.d.ts.map +1 -0
  22. package/dist/components/input-radio.d.ts +33 -0
  23. package/dist/components/input-radio.d.ts.map +1 -0
  24. package/dist/components/input-switch.d.ts +17 -0
  25. package/dist/components/input-switch.d.ts.map +1 -0
  26. package/dist/components/input-text.d.ts +19 -0
  27. package/dist/components/input-text.d.ts.map +1 -0
  28. package/dist/components/input-textarea.d.ts +20 -0
  29. package/dist/components/input-textarea.d.ts.map +1 -0
  30. package/dist/components/input-time.d.ts +31 -0
  31. package/dist/components/input-time.d.ts.map +1 -0
  32. package/dist/components/with-input-field.d.ts +49 -0
  33. package/dist/components/with-input-field.d.ts.map +1 -0
  34. package/dist/contexts/breadcrumb-context.d.ts +20 -0
  35. package/dist/contexts/breadcrumb-context.d.ts.map +1 -0
  36. package/dist/contexts/dialog-context.d.ts +28 -0
  37. package/dist/contexts/dialog-context.d.ts.map +1 -0
  38. package/dist/contexts/handy-fluent-ui-context.d.ts +48 -0
  39. package/dist/contexts/handy-fluent-ui-context.d.ts.map +1 -0
  40. package/dist/contexts/spinner-context.d.ts +19 -0
  41. package/dist/contexts/spinner-context.d.ts.map +1 -0
  42. package/dist/contexts/toast-context.d.ts +17 -0
  43. package/dist/contexts/toast-context.d.ts.map +1 -0
  44. package/dist/hooks/use-breadcrumb.d.ts +19 -0
  45. package/dist/hooks/use-breadcrumb.d.ts.map +1 -0
  46. package/dist/hooks/use-dialog.d.ts +6 -0
  47. package/dist/hooks/use-dialog.d.ts.map +1 -0
  48. package/dist/hooks/use-logger.d.ts +9 -0
  49. package/dist/hooks/use-logger.d.ts.map +1 -0
  50. package/dist/hooks/use-mobile.d.ts +4 -0
  51. package/dist/hooks/use-mobile.d.ts.map +1 -0
  52. package/dist/hooks/use-spinner.d.ts +7 -0
  53. package/dist/hooks/use-spinner.d.ts.map +1 -0
  54. package/dist/hooks/use-theme.d.ts +8 -0
  55. package/dist/hooks/use-theme.d.ts.map +1 -0
  56. package/dist/hooks/use-time-zone.d.ts +17 -0
  57. package/dist/hooks/use-time-zone.d.ts.map +1 -0
  58. package/dist/hooks/use-toast.d.ts +9 -0
  59. package/dist/hooks/use-toast.d.ts.map +1 -0
  60. package/dist/index.cjs +2 -0
  61. package/dist/index.cjs.map +1 -0
  62. package/dist/index.d.ts +45 -0
  63. package/dist/index.d.ts.map +1 -0
  64. package/dist/index.js +1571 -0
  65. package/dist/index.js.map +1 -0
  66. package/dist/providers/breadcrumb-provider.d.ts +10 -0
  67. package/dist/providers/breadcrumb-provider.d.ts.map +1 -0
  68. package/dist/providers/dialog-provider.d.ts +6 -0
  69. package/dist/providers/dialog-provider.d.ts.map +1 -0
  70. package/dist/providers/handy-fluent-ui-provider.d.ts +10 -0
  71. package/dist/providers/handy-fluent-ui-provider.d.ts.map +1 -0
  72. package/dist/providers/spinner-provider.d.ts +10 -0
  73. package/dist/providers/spinner-provider.d.ts.map +1 -0
  74. package/dist/providers/toast-provider.d.ts +9 -0
  75. package/dist/providers/toast-provider.d.ts.map +1 -0
  76. package/dist/utils/string-util.d.ts +4 -0
  77. package/dist/utils/string-util.d.ts.map +1 -0
  78. package/package.json +94 -0
package/README.md ADDED
@@ -0,0 +1,681 @@
1
+ # Handy FluentUI
2
+
3
+ Opinionated React components built on top of [FluentUI v9](https://react.fluentui.dev/) with responsive, form-friendly behaviours out of the box.
4
+
5
+ - Consistent label / hint / error / info layout via the `withInputField` HOC
6
+ - Automatic mobile adaptation (breakpoint-driven theme switching, bottom-sheet drawers, stacking layouts)
7
+ - Imperative `useToast`, `useSpinner`, `useDialog` APIs
8
+ - i18n-ready label overrides through the provider
9
+
10
+ ---
11
+
12
+ ## Commands
13
+
14
+ ```bash
15
+ yarn dev # Start dev server (Vite)
16
+ yarn build # Production build
17
+ yarn test # Run tests in watch mode (Vitest)
18
+ yarn test:run # Run tests once
19
+ yarn lint # ESLint
20
+ ```
21
+
22
+ ---
23
+
24
+ ## Setup
25
+
26
+ Wrap your application in `HandyFluentUiProvider` once at the root. All components and hooks must be descendants of this provider.
27
+
28
+ ```tsx
29
+ import { webDarkTheme, webLightTheme } from '@fluentui/react-components';
30
+ import { HandyFluentUiProvider } from './providers/handy-fluent-ui-provider';
31
+
32
+ function App() {
33
+ return (
34
+ <HandyFluentUiProvider
35
+ mobileBreakpoint={600}
36
+ supportedTheme={{
37
+ web: { light: webLightTheme, dark: webDarkTheme },
38
+ default: 'light',
39
+ }}
40
+ component={{
41
+ toast: { dismissTimeout: 3000 },
42
+ }}
43
+ >
44
+ {/* your app */}
45
+ </HandyFluentUiProvider>
46
+ );
47
+ }
48
+ ```
49
+
50
+ ### Provider props
51
+
52
+ | Prop | Type | Default | Description |
53
+ |------|------|---------|-------------|
54
+ | `mobileBreakpoint` | `number` | `600` | Viewport width (px) at which mobile layout and theme activate |
55
+ | `supportedTheme` | `SupportedTheme` | built-in themes | Theme objects for web/mobile platforms |
56
+ | `component` | `Component` | — | Spinner and toast configuration |
57
+ | `loggerConfig` | `{ logMessage? }` | `console.log` | Custom logger |
58
+ | `children` | `ReactNode` | — | Required |
59
+
60
+ ### `supportedTheme`
61
+
62
+ ```ts
63
+ type SupportedTheme = {
64
+ web?: { light?: Theme; dark?: Theme; custom?: Theme };
65
+ mobile?: { light?: Theme; dark?: Theme; custom?: Theme };
66
+ default?: 'light' | 'dark' | 'custom'; // defaults to system preference
67
+ };
68
+ ```
69
+
70
+ A `custom` theme object must be supplied to make `'custom'` selectable via `useTheme().switchTheme('custom')`. A single custom theme passed under either `web` or `mobile` is automatically shared with the other platform.
71
+
72
+ ### `component` config
73
+
74
+ ```ts
75
+ type Component = {
76
+ spinner?: SpinnerContextConfig;
77
+ toast?: ToastContextConfig;
78
+ };
79
+ ```
80
+
81
+ Component-specific labels (`FuiTable` pagination text, `FuiImageCarousel` tooltips, `FuiInputMultiLangText` language names) are passed directly as props on each component — see each component's section below.
82
+
83
+ ---
84
+
85
+ ## Hooks
86
+
87
+ | Hook | Returns | Description |
88
+ |------|---------|-------------|
89
+ | `useTheme()` | `{ currentTheme, switchTheme }` | Read and change the active theme |
90
+ | `useIsMobile()` | `boolean` | True when viewport ≤ `mobileBreakpoint` |
91
+ | `useToast()` | `{ success, error, info, warning }` | Show toast notifications |
92
+ | `useSpinner()` | `{ show, hide }` | Show/hide the global overlay spinner |
93
+ | `useDialog()` | `{ openDialog }` | Show an imperative confirmation dialog |
94
+ | `useLogger()` | `(message, level?) => void` | Log via the configured logger |
95
+ | `useTimeZone()` | `{ timeZone, setTimeZone, zonedDate2LocalDate }` | Read, update, and decompose dates in the active time zone |
96
+
97
+ All hooks throw if called outside `HandyFluentUiProvider`.
98
+
99
+ ### `useTimeZone`
100
+
101
+ The provider initialises `timeZone` from `Intl.DateTimeFormat().resolvedOptions().timeZone` (the browser's local time zone). `useTimeZone` lets you read or override it and decompose a `Date` object into its constituent parts within that zone.
102
+
103
+ ```tsx
104
+ const { timeZone, setTimeZone, zonedDate2LocalDate } = useTimeZone();
105
+
106
+ // Read the active time zone
107
+ console.log(timeZone); // e.g. 'Asia/Tokyo'
108
+
109
+ // Switch to a different time zone (validated; invalid values are ignored with a warning)
110
+ setTimeZone('America/New_York');
111
+
112
+ // Extract date parts in the active time zone
113
+ const parts = zonedDate2LocalDate(new Date());
114
+ // { year, month, day, hour, minute, second }
115
+
116
+ // Extract date parts in an explicit time zone (overrides the active one for this call)
117
+ const tokyoParts = zonedDate2LocalDate(new Date(), 'Asia/Tokyo');
118
+ ```
119
+
120
+ **`LocalDate`**
121
+
122
+ ```ts
123
+ type LocalDate = {
124
+ year: number;
125
+ month: number; // 1–12
126
+ day: number; // 1–31
127
+ hour: number; // 0–23
128
+ minute: number; // 0–59
129
+ second: number; // 0–59
130
+ };
131
+ ```
132
+
133
+ | Return value | Type | Description |
134
+ |---|---|---|
135
+ | `timeZone` | `string` | Currently active IANA time zone identifier |
136
+ | `setTimeZone` | `(tz: string) => void` | Update the active time zone. Invalid identifiers are ignored and logged as a warning. |
137
+ | `zonedDate2LocalDate` | `(date: Date, tz?: string) => LocalDate` | Decompose a `Date` into year/month/day/hour/minute/second in the active (or an explicitly supplied) time zone. Falls back to local time and logs a warning if `tz` is invalid. |
138
+
139
+ ---
140
+
141
+ ## Common field props (`FieldLayoutProps`)
142
+
143
+ Every `input-*` component inherits these props from the `withInputField` HOC:
144
+
145
+ | Prop | Type | Default | Description |
146
+ |------|------|---------|-------------|
147
+ | `label` | `string \| null` | — | Field label. `null` suppresses the label container entirely |
148
+ | `required` | `boolean` | `false` | Shows a red asterisk next to the label |
149
+ | `hint` | `string` | — | Supplemental info shown in a popover (info icon appears) |
150
+ | `errorMessage` | `string` | — | Error text shown in red below the input |
151
+ | `infoMessage` | `string` | — | Grey helper text below the input (hidden when `errorMessage` is present) |
152
+ | `noMessage` | `boolean` | `false` | Suppresses the message area and its reserved space |
153
+ | `additionalMessage` | `ReactNode` | — | Extra content on the right of the message row |
154
+ | `clearable` | `boolean` | `true` | Shows an eraser icon that clears the value |
155
+ | `direction` | `'vertical' \| 'horizontal'` | `'vertical'` | Label position: above or to the left of the input |
156
+ | `labelWidth` | `'small' \| 'medium' \| 'large' \| 'none'` | — | Fixed label width when `direction='horizontal'` |
157
+
158
+ Horizontal layout automatically collapses to vertical on mobile.
159
+
160
+ ---
161
+
162
+ ## Components
163
+
164
+ ### `FuiInputText`
165
+
166
+ Text input with optional show/hide toggle for passwords.
167
+
168
+ ```tsx
169
+ <FuiInputText
170
+ label="Full Name"
171
+ value={name}
172
+ onChange={setName}
173
+ required
174
+ hint="Enter your legal name."
175
+ />
176
+
177
+ <FuiInputText
178
+ label="Password"
179
+ value={password}
180
+ onChange={setPassword}
181
+ type="password"
182
+ />
183
+ ```
184
+
185
+ | Prop | Type | Required | Description |
186
+ |------|------|----------|-------------|
187
+ | `value` | `string \| null` | Yes | Current value |
188
+ | `onChange` | `(value: string \| null) => void` | Yes | Change callback |
189
+ | `type` | `'text' \| 'email' \| 'password'` | No | Defaults to `'text'`. Password adds show/hide toggle; email blocks duplicate `@`. |
190
+
191
+ ---
192
+
193
+ ### `FuiInputTextArea`
194
+
195
+ Multi-line text area with optional character counter.
196
+
197
+ ```tsx
198
+ <FuiInputTextArea
199
+ label="Biography"
200
+ value={bio}
201
+ onChange={setBio}
202
+ maxLength={300}
203
+ hint="Max 300 characters."
204
+ />
205
+ ```
206
+
207
+ | Prop | Type | Required | Description |
208
+ |------|------|----------|-------------|
209
+ | `value` | `string \| null` | Yes | Current value |
210
+ | `onChange` | `(value: string \| null) => void` | Yes | Change callback |
211
+ | `maxLength` | `number` | No | Automatically appends a counter (`n / max`) unless `additionalMessage` is set |
212
+
213
+ ---
214
+
215
+ ### `FuiInputNumber`
216
+
217
+ Number input with keystroke filtering and optional SpinButton mode.
218
+
219
+ ```tsx
220
+ {/* Plain number input */}
221
+ <FuiInputNumber
222
+ label="Age"
223
+ value={age}
224
+ onChange={setAge}
225
+ min={0}
226
+ max={120}
227
+ precision={0}
228
+ allowNegative={false}
229
+ />
230
+
231
+ {/* SpinButton mode (set step) */}
232
+ <FuiInputNumber
233
+ label="Salary"
234
+ value={salary}
235
+ onChange={setSalary}
236
+ step={1000}
237
+ min={0}
238
+ formatter={(v) => `$${v.toLocaleString()}`}
239
+ />
240
+ ```
241
+
242
+ | Prop | Type | Required | Description |
243
+ |------|------|----------|-------------|
244
+ | `value` | `number \| null` | Yes | Current value |
245
+ | `onChange` | `(value: number \| null) => void` | Yes | Change callback |
246
+ | `step` | `number` | No | Enables SpinButton mode; direct typing is disabled. On mobile, renders a plain `Input` with horizontally-arranged up/down arrow buttons instead of the native SpinButton. |
247
+ | `precision` | `number` | No | Decimal places allowed. Defaults to `0`. Fixed at `0` in SpinButton mode. |
248
+ | `min` | `number` | No | Minimum value |
249
+ | `max` | `number` | No | Maximum value |
250
+ | `allowNegative` | `boolean` | No | When `false`, blocks the minus key. Defaults to `true`. |
251
+ | `formatter` | `(value: number) => string` | No | Formats the display value when the field is unfocused |
252
+
253
+ ---
254
+
255
+ ### `FuiInputDate`
256
+
257
+ Date picker. Renders a FluentUI `DatePicker` on desktop and a bottom-sheet calendar drawer on mobile.
258
+
259
+ ```tsx
260
+ <FuiInputDate
261
+ label="Date of Birth"
262
+ value={date}
263
+ onChange={(d) => setDate(d ?? null)}
264
+ required
265
+ />
266
+ ```
267
+
268
+ | Prop | Type | Required | Description |
269
+ |------|------|----------|-------------|
270
+ | `value` | `Date \| null` | Yes | Selected date |
271
+ | `onChange` | `(date: Date \| null \| undefined) => void` | Yes | Change callback |
272
+ | `formatter` | `(date: Date \| null) => string` | No | Custom date format function. Defaults to `toLocaleDateString()`. |
273
+ | `readOnly` | `boolean` | No | Suppresses the calendar popup/drawer. Desktop renders a plain read-only `Input`; mobile hides the calendar icon and ignores clicks. |
274
+
275
+ ---
276
+
277
+ ### `FuiInputTime`
278
+
279
+ Time picker with up/down arrow buttons. Clicking an arrow increments or decrements the time segment under the cursor. On mobile the arrows are laid out in a horizontal row with larger icons.
280
+
281
+ ```tsx
282
+ import { FuiInputTime, FuiTime } from './components/input-time';
283
+
284
+ const [shiftStart, setShiftStart] = useState<FuiTime | null>(null);
285
+
286
+ {/* 12-hour format with seconds */}
287
+ <FuiInputTime
288
+ label="Shift Start"
289
+ value={shiftStart}
290
+ onChange={setShiftStart}
291
+ in24HourFormat={false}
292
+ withSeconds
293
+ />
294
+
295
+ {/* 24-hour format, cascade carry enabled */}
296
+ <FuiInputTime
297
+ label="Shift End"
298
+ value={shiftEnd}
299
+ onChange={setShiftEnd}
300
+ cascadeCarry
301
+ />
302
+ ```
303
+
304
+ **`FuiTime`** type:
305
+
306
+ ```ts
307
+ type FuiTime = {
308
+ hour: number; // 0–23
309
+ minute: number; // 0–59
310
+ second: number; // 0–59
311
+ };
312
+ ```
313
+
314
+ | Prop | Type | Required | Description |
315
+ |------|------|----------|-------------|
316
+ | `value` | `FuiTime \| null` | Yes | Current time value |
317
+ | `onChange` | `(time: FuiTime \| null) => void` | Yes | Change callback |
318
+ | `in24HourFormat` | `boolean` | No | When `false`, display uses 12-hour clock and shows an AM/PM toggle. Defaults to `true`. |
319
+ | `withSeconds` | `boolean` | No | When `true`, shows the seconds segment. Defaults to `false`. |
320
+ | `cascadeCarry` | `boolean` | No | When `true`, incrementing past a segment boundary (e.g. 59m → 0m) also advances the next segment. Defaults to `false`. |
321
+ | `readOnly` | `boolean` | No | Hides the up/down arrows; the input becomes non-interactive. |
322
+
323
+ ---
324
+
325
+ ### `FuiInputDropdown`
326
+
327
+ Dropdown with single or multi-select. Renders a bottom-sheet drawer on mobile with the field label (or `placeholder`) as the drawer title.
328
+
329
+ ```tsx
330
+ const options = [
331
+ { value: 'hk', text: 'Hong Kong', group: 'Asia' },
332
+ { value: 'gb', text: 'United Kingdom', group: 'Europe' },
333
+ ];
334
+
335
+ {/* Single select */}
336
+ <FuiInputDropdown
337
+ label="Country"
338
+ value={country}
339
+ onChange={(val) => setCountry(val as string | null)}
340
+ options={options}
341
+ />
342
+
343
+ {/* Multi-select */}
344
+ <FuiInputDropdown
345
+ label="Tags"
346
+ value={tags}
347
+ onChange={(val) => setTags(val as string[])}
348
+ options={options}
349
+ multiselect
350
+ />
351
+
352
+ {/* Constrain dropdown height on desktop */}
353
+ <FuiInputDropdown
354
+ label="Country"
355
+ value={country}
356
+ onChange={(val) => setCountry(val as string | null)}
357
+ options={options}
358
+ listbox={{ style: { maxHeight: '200px', overflowY: 'auto' } }}
359
+ positioning={{ autoSize: false }}
360
+ />
361
+ ```
362
+
363
+ | Prop | Type | Required | Description |
364
+ |------|------|----------|-------------|
365
+ | `value` | `string \| string[] \| null` | Yes | Selected value(s) |
366
+ | `onChange` | `(value: string \| string[] \| null) => void` | Yes | Change callback |
367
+ | `options` | `InputDropdownOption[]` | Yes | Option list |
368
+ | `multiselect` | `boolean` | No | Enable multi-select mode |
369
+ | `readOnly` | `boolean` | No | Silently ignores selection changes |
370
+ | `listbox` | `ListboxProps` | No | Props forwarded to the inner `Listbox`. Use `style.maxHeight` to constrain dropdown height. Must pass `positioning={{ autoSize: false }}` alongside this, otherwise Floating UI overrides inline `max-height`. |
371
+
372
+ **`InputDropdownOption`**
373
+
374
+ | Prop | Type | Required | Description |
375
+ |------|------|----------|-------------|
376
+ | `value` | `string` | Yes | Option value |
377
+ | `text` | `string` | Yes | Display text (used for search/filtering) |
378
+ | `group` | `string` | No | Group label |
379
+ | `render` | `() => ReactNode` | No | Custom option content renderer |
380
+
381
+ ---
382
+
383
+ ### `FuiInputRadio`
384
+
385
+ Radio group with a shared label.
386
+
387
+ ```tsx
388
+ <FuiInputRadio
389
+ label="Gender"
390
+ value={gender}
391
+ onChange={(data) => setGender(data.value)}
392
+ direction="horizontal"
393
+ >
394
+ <Radio label="Male" value="male" />
395
+ <Radio label="Female" value="female" />
396
+ <Radio label="Other" value="other" />
397
+ </FuiInputRadio>
398
+ ```
399
+
400
+ | Prop | Type | Required | Description |
401
+ |------|------|----------|-------------|
402
+ | `onChange` | `(data: RadioGroupOnChangeData) => void` | No | Change callback |
403
+ | `layout` | `'vertical' \| 'horizontal'` | No | Radio button layout direction |
404
+
405
+ `horizontal-stacked` layout is not supported.
406
+
407
+ ---
408
+
409
+ ### `FuiInputCheckbox`
410
+
411
+ Checkbox with optional read-only mode.
412
+
413
+ ```tsx
414
+ <FuiInputCheckbox
415
+ label="I agree to the terms"
416
+ labelPosition="after"
417
+ checked={agreed}
418
+ onChange={(data) => setAgreed(!!data.checked)}
419
+ />
420
+ ```
421
+
422
+ | Prop | Type | Required | Description |
423
+ |------|------|----------|-------------|
424
+ | `onChange` | `(data: CheckboxOnChangeData) => void` | No | Change callback |
425
+ | `readOnly` | `boolean` | No | Visually interactive but ignores changes |
426
+
427
+ ---
428
+
429
+ ### `FuiInputSwitch`
430
+
431
+ Toggle switch. `onChange` delivers a `boolean` directly.
432
+
433
+ ```tsx
434
+ <FuiInputSwitch
435
+ label="Receive notifications"
436
+ checked={notifications}
437
+ onChange={setNotifications}
438
+ />
439
+ ```
440
+
441
+ | Prop | Type | Required | Description |
442
+ |------|------|----------|-------------|
443
+ | `onChange` | `(value: boolean) => void` | Yes | Change callback — receives `boolean` directly |
444
+ | `readOnly` | `boolean` | No | Silently ignores changes |
445
+
446
+ ---
447
+
448
+ ### `FuiInputMultiLangText`
449
+
450
+ Text input for multi-language values. A translate icon opens a drawer with one field per configured language (up to 3). Language names are set via the `inputMultiLang.label.languages` provider config.
451
+
452
+ ```tsx
453
+ <FuiInputMultiLangText
454
+ label="Job Title"
455
+ value={jobTitle}
456
+ onChange={setJobTitle}
457
+ />
458
+ ```
459
+
460
+ `value` / `onChange` use `MultiLangText`:
461
+
462
+ ```ts
463
+ type MultiLangText = {
464
+ valueInLangOne: string | null;
465
+ valueInLangTwo: string | null;
466
+ valueInLangThree: string | null;
467
+ };
468
+ ```
469
+
470
+ | Prop | Type | Required | Description |
471
+ |------|------|----------|-------------|
472
+ | `value` | `MultiLangText \| null` | Yes | Multi-language text value |
473
+ | `onChange` | `(value: MultiLangText \| null) => void` | Yes | Change callback |
474
+ | `label` | `string` | Yes | Field label — also used as the drawer title |
475
+ | `languages` | `string[]` | No | Names of each language slot shown in the drawer (up to 3). When fewer than 2 are provided, the translate icon is hidden. |
476
+ | `textComponent` | `ComponentType<InputTextProps>` | No | Overrides the inner text component. Defaults to `FuiInputText`. |
477
+
478
+ ---
479
+
480
+ ### `FuiInputGroup`
481
+
482
+ Groups multiple inputs under one shared label with weighted distribution. Items stack vertically on mobile.
483
+
484
+ ```tsx
485
+ <FuiInputGroup
486
+ label="City / Zip"
487
+ items={[
488
+ { element: <FuiInputText value={city} onChange={setCity} placeholder="City" />, weight: 2 },
489
+ { element: <FuiInputText value={zip} onChange={setZip} placeholder="Zip" />, weight: 1 },
490
+ ]}
491
+ />
492
+ ```
493
+
494
+ | Prop | Type | Required | Description |
495
+ |------|------|----------|-------------|
496
+ | `label` | `string` | Yes | Shared label for the group |
497
+ | `items` | `{ element: ReactElement; weight?: number }[]` | Yes | Inputs with optional flex-grow weights (default `1`) |
498
+
499
+ Each item's own `label` is hidden; use the group-level `label` instead.
500
+
501
+ ---
502
+
503
+ ### `FuiTable` / `FuiColumn`
504
+
505
+ Data table driven by `FuiColumn` children. Supports sorting and pagination with horizontal scroll.
506
+
507
+ ```tsx
508
+ <FuiTable
509
+ data={records}
510
+ pagination={{
511
+ offset: 0,
512
+ pageSize: 10,
513
+ pageSizeOption: [5, 10, 20],
514
+ totalRecord: records.length,
515
+ position: 'bottom',
516
+ }}
517
+ width={{ minWidth: '560px' }}
518
+ >
519
+ <FuiColumn field="id" header="ID" style={{ width: '10%' }} />
520
+ <FuiColumn field="name" header="Name" sortable style={{ width: '40%' }} />
521
+ <FuiColumn
522
+ field="status"
523
+ header="Status"
524
+ builder={(value) => <Badge>{String(value)}</Badge>}
525
+ style={{ width: '20%' }}
526
+ />
527
+ </FuiTable>
528
+ ```
529
+
530
+ **`FuiTable` props**
531
+
532
+ | Prop | Type | Required | Description |
533
+ |------|------|----------|-------------|
534
+ | `data` | `T[]` | Yes | Array of row objects |
535
+ | `pagination` | `PaginationProps` | No | Pagination configuration |
536
+ | `onPageOrSort` | `(page?, sort?) => void` | No | Called on page change or sort click. Omit to use local sort state. |
537
+ | `width` | `Pick<CSSProperties, 'width' \| 'minWidth' \| 'maxWidth'>` | No | Width constraints on the inner DataGrid. Set `minWidth` to enable horizontal scroll on mobile. |
538
+ | `label` | `FuiTableLabel` | No | Pagination text overrides. `pageRange` and `paginationBar.nextN` / `previousN` support template tokens (`{{from}}`, `{{to}}`, `{{total}}`, `{{n}}`). |
539
+
540
+ **`FuiColumn` props**
541
+
542
+ | Prop | Type | Required | Description |
543
+ |------|------|----------|-------------|
544
+ | `field` | `string` | Yes | Dot-notation path into the row object (e.g. `"address.city"`) |
545
+ | `header` | `string` | Yes | Column header text |
546
+ | `sortable` | `boolean` | No | Makes the column header clickable for sorting |
547
+ | `align` | `'left' \| 'center' \| 'right'` | No | Cell text alignment |
548
+ | `formatter` | `(value, row) => string` | No | Format function for plain text cells |
549
+ | `builder` | `(value, row) => ReactNode` | No | Render function for rich content (mutually exclusive with `formatter`) |
550
+ | `style` | `CSSProperties` | No | Cell styles (use to set column width) |
551
+ | `headerStyle` | `CSSProperties` | No | Header-cell-specific styles |
552
+ | `headerEllipsis` | `boolean` | No | Truncate long header text with ellipsis |
553
+
554
+ **`PaginationProps`**
555
+
556
+ | Prop | Type | Required | Description |
557
+ |------|------|----------|-------------|
558
+ | `offset` | `number` | Yes | Zero-based row offset of the current page |
559
+ | `pageSize` | `number` | Yes | Rows per page |
560
+ | `totalRecord` | `number` | Yes | Total number of records |
561
+ | `pageSizeOption` | `number[]` | Yes | Available page size choices |
562
+ | `position` | `'top' \| 'bottom'` | No | Defaults to `'bottom'` |
563
+ | `fastForwardPage` | `number` | No | Pages to jump on `<<` / `>>`. Defaults to `5`. |
564
+
565
+ ---
566
+
567
+ ### `FuiTabList` / `FuiTab`
568
+
569
+ Tabbed panel. On mobile the tab bar becomes horizontally scrollable. Vertical layout is forced horizontal on mobile.
570
+
571
+ ```tsx
572
+ <FuiTabList<string>
573
+ selectedValue={tab}
574
+ onTabSelect={(data) => setTab(data.value)}
575
+ >
576
+ <FuiTab name="Personal" value="personal">
577
+ <PersonalForm />
578
+ </FuiTab>
579
+ <FuiTab name="Employment" value="employment">
580
+ <EmploymentForm />
581
+ </FuiTab>
582
+ </FuiTabList>
583
+ ```
584
+
585
+ | Prop (`FuiTabList`) | Type | Required | Description |
586
+ |---------------------|------|----------|-------------|
587
+ | `selectedValue` | `T` | No | Currently active tab value |
588
+ | `onTabSelect` | `(data: { value: T }) => void` | No | Selection change callback |
589
+ | `vertical` | `boolean` | No | Side-by-side layout (collapsed on mobile) |
590
+
591
+ | Prop (`FuiTab`) | Type | Required | Description |
592
+ |-----------------|------|----------|-------------|
593
+ | `name` | `string` | Yes | Tab button label |
594
+ | `value` | `T` | No | Tab identifier — defaults to `name` |
595
+ | `children` | `ReactNode` | No | Content panel |
596
+ | `icon` | `ReactNode` | No | Icon shown in the tab button |
597
+
598
+ ---
599
+
600
+ ### `FuiImageCarousel`
601
+
602
+ Circular image carousel with autoplay and navigation controls. Tooltip labels are read from `component.imageCarousell.label` in the provider config.
603
+
604
+ ```tsx
605
+ <FuiImageCarousel
606
+ images={[
607
+ 'https://example.com/photo1.jpg',
608
+ 'https://example.com/photo2.jpg',
609
+ ]}
610
+ />
611
+ ```
612
+
613
+ | Prop | Type | Required | Description |
614
+ |------|------|----------|-------------|
615
+ | `images` | `string[]` | Yes | Image URLs |
616
+ | `label` | `{ autoplay?: string; next?: string; previous?: string }` | No | Tooltip label overrides for the navigation buttons |
617
+
618
+ ---
619
+
620
+ ### `FuiButtonPanel`
621
+
622
+ Flex row of action buttons. Collapses to full-width stacked column on mobile.
623
+
624
+ ```tsx
625
+ <FuiButtonPanel alignItems="right">
626
+ <Button appearance="secondary" onClick={onCancel}>Cancel</Button>
627
+ <Button appearance="primary" onClick={onSave}>Save</Button>
628
+ </FuiButtonPanel>
629
+ ```
630
+
631
+ | Prop | Type | Required | Description |
632
+ |------|------|----------|-------------|
633
+ | `alignItems` | `'left' \| 'right'` | No | Horizontal alignment. Defaults to `'right'`. |
634
+ | `children` | `ReactNode` | Yes | Button elements |
635
+
636
+ ---
637
+
638
+ ## Toast notifications
639
+
640
+ Use the `useToast()` hook to show non-blocking feedback.
641
+
642
+ ```tsx
643
+ const toast = useToast();
644
+
645
+ toast.success('Saved!');
646
+ toast.error('Something went wrong.');
647
+ toast.info('Processing…');
648
+ toast.warning('Check your input.');
649
+ ```
650
+
651
+ Error toasts do not auto-dismiss. All other intents dismiss automatically after `toast.dismissTimeout` ms (configurable in the provider `component.toast` config). A dismiss button appears on error toasts after the timeout.
652
+
653
+ ---
654
+
655
+ ## Spinner
656
+
657
+ Use `useSpinner()` to show a full-screen overlay spinner during async operations.
658
+
659
+ ```tsx
660
+ const spinner = useSpinner();
661
+
662
+ spinner.show();
663
+ await saveData();
664
+ spinner.hide();
665
+ ```
666
+
667
+ ---
668
+
669
+ ## Confirmation dialog
670
+
671
+ Use `useDialog()` for imperative confirmation dialogs.
672
+
673
+ ```tsx
674
+ const dialog = useDialog();
675
+
676
+ dialog.openDialog({
677
+ title: 'Confirm Delete',
678
+ content: 'Are you sure?',
679
+ primaryButton: { label: 'Yes', action: handleDelete },
680
+ });
681
+ ```
@@ -0,0 +1,13 @@
1
+ import { default as React } from 'react';
2
+ /** Props for FuiButtonPanel. */
3
+ type ButtonPanelProps = {
4
+ className?: string;
5
+ /** Horizontal alignment of buttons. Defaults to `'right'`. */
6
+ alignItems?: 'left' | 'right';
7
+ children: React.ReactNode;
8
+ };
9
+ /** Flex row of action buttons that collapses to a full-width column on mobile. */
10
+ declare const FuiButtonPanel: React.FC<ButtonPanelProps>;
11
+ export { FuiButtonPanel };
12
+ export type { ButtonPanelProps };
13
+ //# sourceMappingURL=fui-button-panel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fui-button-panel.d.ts","sourceRoot":"","sources":["../../src/components/fui-button-panel.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,OAAO,CAAC;AAuB1B,gCAAgC;AAChC,KAAK,gBAAgB,GAAG;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8DAA8D;IAC9D,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC9B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B,CAAC;AAEF,kFAAkF;AAClF,QAAA,MAAM,cAAc,EAAE,KAAK,CAAC,EAAE,CAAC,gBAAgB,CAoB9C,CAAC;AAEF,OAAO,EAAE,cAAc,EAAE,CAAC;AAC1B,YAAY,EAAE,gBAAgB,EAAE,CAAC"}