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.
- package/README.md +681 -0
- package/dist/components/fui-button-panel.d.ts +13 -0
- package/dist/components/fui-button-panel.d.ts.map +1 -0
- package/dist/components/fui-image-carousell.d.ts +17 -0
- package/dist/components/fui-image-carousell.d.ts.map +1 -0
- package/dist/components/fui-tab.d.ts +29 -0
- package/dist/components/fui-tab.d.ts.map +1 -0
- package/dist/components/fui-table.d.ts +89 -0
- package/dist/components/fui-table.d.ts.map +1 -0
- package/dist/components/input-checkbox.d.ts +17 -0
- package/dist/components/input-checkbox.d.ts.map +1 -0
- package/dist/components/input-date.d.ts +27 -0
- package/dist/components/input-date.d.ts.map +1 -0
- package/dist/components/input-dropdown.d.ts +33 -0
- package/dist/components/input-dropdown.d.ts.map +1 -0
- package/dist/components/input-group.d.ts +25 -0
- package/dist/components/input-group.d.ts.map +1 -0
- package/dist/components/input-multi-lang.d.ts +35 -0
- package/dist/components/input-multi-lang.d.ts.map +1 -0
- package/dist/components/input-number.d.ts +39 -0
- package/dist/components/input-number.d.ts.map +1 -0
- package/dist/components/input-radio.d.ts +33 -0
- package/dist/components/input-radio.d.ts.map +1 -0
- package/dist/components/input-switch.d.ts +17 -0
- package/dist/components/input-switch.d.ts.map +1 -0
- package/dist/components/input-text.d.ts +19 -0
- package/dist/components/input-text.d.ts.map +1 -0
- package/dist/components/input-textarea.d.ts +20 -0
- package/dist/components/input-textarea.d.ts.map +1 -0
- package/dist/components/input-time.d.ts +31 -0
- package/dist/components/input-time.d.ts.map +1 -0
- package/dist/components/with-input-field.d.ts +49 -0
- package/dist/components/with-input-field.d.ts.map +1 -0
- package/dist/contexts/breadcrumb-context.d.ts +20 -0
- package/dist/contexts/breadcrumb-context.d.ts.map +1 -0
- package/dist/contexts/dialog-context.d.ts +28 -0
- package/dist/contexts/dialog-context.d.ts.map +1 -0
- package/dist/contexts/handy-fluent-ui-context.d.ts +48 -0
- package/dist/contexts/handy-fluent-ui-context.d.ts.map +1 -0
- package/dist/contexts/spinner-context.d.ts +19 -0
- package/dist/contexts/spinner-context.d.ts.map +1 -0
- package/dist/contexts/toast-context.d.ts +17 -0
- package/dist/contexts/toast-context.d.ts.map +1 -0
- package/dist/hooks/use-breadcrumb.d.ts +19 -0
- package/dist/hooks/use-breadcrumb.d.ts.map +1 -0
- package/dist/hooks/use-dialog.d.ts +6 -0
- package/dist/hooks/use-dialog.d.ts.map +1 -0
- package/dist/hooks/use-logger.d.ts +9 -0
- package/dist/hooks/use-logger.d.ts.map +1 -0
- package/dist/hooks/use-mobile.d.ts +4 -0
- package/dist/hooks/use-mobile.d.ts.map +1 -0
- package/dist/hooks/use-spinner.d.ts +7 -0
- package/dist/hooks/use-spinner.d.ts.map +1 -0
- package/dist/hooks/use-theme.d.ts +8 -0
- package/dist/hooks/use-theme.d.ts.map +1 -0
- package/dist/hooks/use-time-zone.d.ts +17 -0
- package/dist/hooks/use-time-zone.d.ts.map +1 -0
- package/dist/hooks/use-toast.d.ts +9 -0
- package/dist/hooks/use-toast.d.ts.map +1 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1571 -0
- package/dist/index.js.map +1 -0
- package/dist/providers/breadcrumb-provider.d.ts +10 -0
- package/dist/providers/breadcrumb-provider.d.ts.map +1 -0
- package/dist/providers/dialog-provider.d.ts +6 -0
- package/dist/providers/dialog-provider.d.ts.map +1 -0
- package/dist/providers/handy-fluent-ui-provider.d.ts +10 -0
- package/dist/providers/handy-fluent-ui-provider.d.ts.map +1 -0
- package/dist/providers/spinner-provider.d.ts +10 -0
- package/dist/providers/spinner-provider.d.ts.map +1 -0
- package/dist/providers/toast-provider.d.ts +9 -0
- package/dist/providers/toast-provider.d.ts.map +1 -0
- package/dist/utils/string-util.d.ts +4 -0
- package/dist/utils/string-util.d.ts.map +1 -0
- 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"}
|