@morze/ui 0.2.12 → 0.3.1

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
@@ -251,13 +251,13 @@ The full list lives in `dist/morze-ui-tokens.css`.
251
251
 
252
252
  ## Components
253
253
 
254
- | Input and actions | Navigation and output | Overlays |
255
- | --- | --- | --- |
256
- | `Button` `Toggle` `ToggleGroup` | `Tabs` `Accordion` | `Dialog` |
257
- | `Checkbox` `RadioGroup` `Switch` | `Card` `Alert` `Badge` | `DropdownMenu` |
258
- | `Slider` `Input` `Textarea` | `Progress` `Avatar` | `Popover` |
259
- | `Label` `Field` `Select` | `Separator` `Skeleton` `Spinner` | `Tooltip` |
260
- | `DataTable` | `Sidebar` | `Sheet` |
254
+ | Input and actions | Navigation | Output | Overlays |
255
+ | --- | --- | --- | --- |
256
+ | `Button` `Toggle` `ToggleGroup` | `Tabs` `Accordion` | `Card` `Alert` `Badge` | `Dialog` `AlertDialog` |
257
+ | `Checkbox` `RadioGroup` `Switch` | `Sidebar` `Breadcrumb` | `Progress` `Avatar` | `DropdownMenu` `Menubar` |
258
+ | `Slider` `Input` `Textarea` | `Menubar` `NavigationMenu` | `Separator` `Skeleton` `Spinner` | `Popover` `Tooltip` |
259
+ | `Label` `Field` `Select` | `ScrollArea` | `Table` `Chart` | `Sheet` `Toast` |
260
+ | `Calendar` `DataTable` | | | |
261
261
 
262
262
  The API mirrors shadcn/ui: same names, same sub-component composition,
263
263
  `asChild`, a `data-slot` on every part — so examples from the shadcn docs work
@@ -286,6 +286,16 @@ What this kit adds on top:
286
286
  `label={null}` makes it purely decorative.
287
287
  - `Badge` — `variant`: `solid` `soft` `outline`.
288
288
  - `Field` / `FieldHint` / `FieldError` — form scaffolding.
289
+ - `TabsList` — `overflow`: `wrap` (default), `scroll`, or `menu`, which
290
+ collapses whatever does not fit into a trailing menu. See [Tabs that run out
291
+ of room](#tabs-that-run-out-of-room).
292
+ - `AlertDialogAction` / `AlertDialogCancel` — `variant`, `size` and `tone` from
293
+ `Button`, so a destructive confirmation is `tone="danger"`.
294
+ - `Table` — brings its own horizontal scroll container, because a wide table
295
+ otherwise scrolls the page instead of itself; `containerRef` hands that
296
+ element back for a synchronised header or a virtualiser.
297
+ - `ScrollArea` — the kit's scrollbar in place of the platform's, so a pane
298
+ looks scrollable on macOS too. `viewportRef` reaches the scrolling element.
289
299
 
290
300
  ```tsx
291
301
  <ToggleGroup type="single" defaultValue="week" appearance="segmented">
@@ -294,6 +304,149 @@ What this kit adds on top:
294
304
  </ToggleGroup>
295
305
  ```
296
306
 
307
+ ## Tabs that run out of room
308
+
309
+ `overflow="menu"` keeps a tab row on one line and folds the rest into a menu:
310
+
311
+ ```tsx
312
+ <TabsList overflow="menu" overflowLabel="More">
313
+ {sections.map((s) => <TabsTrigger key={s.id} value={s.id}>{s.name}</TabsTrigger>)}
314
+ </TabsList>
315
+ ```
316
+
317
+ Every trigger stays mounted inside the list — Radix wires them into one
318
+ roving-focus group and a trigger outside it throws — so the collapsed ones are
319
+ hidden rather than unmounted, which also takes them out of the tab order. The
320
+ button lights up when the *selected* tab is one of the hidden ones, so the row
321
+ never looks as if nothing is chosen. Widths are measured off a mirror row of
322
+ inert spans: a hidden trigger measures zero, and a second row of real triggers
323
+ would duplicate every `value`.
324
+
325
+ ## Calendar
326
+
327
+ A month grid with no date library behind it. Names come from `Intl`, so a
328
+ locale tag is the whole of the localisation story.
329
+
330
+ ```tsx
331
+ <Calendar mode="single" selected={date} onSelect={setDate} locale="ru-RU" />
332
+
333
+ <Calendar
334
+ mode="range"
335
+ numberOfMonths={2}
336
+ selected={range}
337
+ onSelect={setRange}
338
+ disabled={{ before: new Date() }}
339
+ />
340
+ ```
341
+
342
+ `mode`: `single` (clicking the chosen day clears it unless `required`),
343
+ `multiple`, `range` (the ends are ordered for you, and the span under the
344
+ pointer previews before you commit). `disabled` takes a `Date`, a `Date[]`, a
345
+ `{ from, to }` span, an open `{ before, after }` bound, or a predicate.
346
+ `fromDate` / `toDate` bound both selection and paging; `captionLayout="dropdown"`
347
+ swaps the caption for month and year selects; `showWeekNumbers` adds the ISO
348
+ column; `weekStartsOn` overrides what the locale says.
349
+
350
+ Six week rows are always drawn, so paging never resizes the popover it sits in.
351
+ Arrows move a day, `Home`/`End` a week, `PageUp`/`PageDown` a month, and exactly
352
+ one day is ever in the tab order.
353
+
354
+ ## Toast
355
+
356
+ `Toaster` mounts once; `toast()` is a plain function, callable from a fetch
357
+ handler or a store — the places a failure actually happens are rarely places
358
+ that can call a hook.
359
+
360
+ ```tsx
361
+ // once, at the root
362
+ <Toaster position="bottom-right" />
363
+
364
+ // anywhere
365
+ toast({ title: 'Saved', tone: 'success' })
366
+
367
+ const t = toast({ title: 'Uploading…', duration: Infinity })
368
+ t.update({ title: 'Uploaded', tone: 'success', duration: 4000 })
369
+ t.dismiss()
370
+ ```
371
+
372
+ Three live at once; beyond that the oldest is dropped, because a taller stack
373
+ is a log and nobody reads a log that is covering the page. `useToast()` returns
374
+ the same `{ toasts, toast, dismiss }` for the rare case that needs to render
375
+ the queue itself.
376
+
377
+ ## Chart
378
+
379
+ The kit does not draw charts — it dresses them. `ChartContainer` publishes each
380
+ series colour as a CSS custom property scoped to itself, so any library that
381
+ takes a CSS colour can consume it, and the tooltip and legend read like the
382
+ rest of the interface.
383
+
384
+ ```tsx
385
+ const config = {
386
+ shipped: { label: 'Shipped', color: 'var(--mz-primary)' },
387
+ returned: { label: 'Returned', theme: { light: '#c2410c', dark: '#fb923c' } },
388
+ } satisfies ChartConfig
389
+
390
+ <ChartContainer config={config} className="h-64">
391
+ <ResponsiveContainer>
392
+ <BarChart data={rows}>
393
+ <Bar dataKey="shipped" fill="var(--color-shipped)" />
394
+ <Tooltip content={<ChartTooltipContent />} />
395
+ </BarChart>
396
+ </ResponsiveContainer>
397
+ </ChartContainer>
398
+ ```
399
+
400
+ The responsive wrapper stays yours: sizing belongs to the charting library, and
401
+ a container that owned it would only ever work with one of them. Config values
402
+ are validated before they are interpolated into the stylesheet — a config is
403
+ data, and data ends up coming from a server.
404
+
405
+ `ChartTooltipContent` and `ChartLegendContent` take `nameKey` (and the tooltip
406
+ also `labelKey`) for the charts whose series key lives *inside* the datum
407
+ rather than in `dataKey` — a pie, where every slice comes from one series:
408
+
409
+ ```tsx
410
+ <Tooltip content={<ChartTooltipContent nameKey="material" hideLabel />} />
411
+ ```
412
+
413
+ `ChartTooltip` and `ChartLegend` are not exported: they were only ever
414
+ recharts' own `Tooltip` and `Legend`, so import those from recharts directly.
415
+
416
+ ## Forms
417
+
418
+ React Hook Form bindings live behind their own entry point, because
419
+ `react-hook-form` is an *optional* peer dependency — an application that does
420
+ not use it should not carry it:
421
+
422
+ ```tsx
423
+ import { Form, FormField, FormItem, FormLabel, FormControl, FormMessage }
424
+ from '@morze/ui/form'
425
+
426
+ <Form {...form}>
427
+ <FormField
428
+ control={form.control}
429
+ name="email"
430
+ render={({ field }) => (
431
+ <FormItem>
432
+ <FormLabel>Email</FormLabel>
433
+ <FormControl><Input {...field} /></FormControl>
434
+ <FormMessage />
435
+ </FormItem>
436
+ )}
437
+ />
438
+ </Form>
439
+ ```
440
+
441
+ What it adds over `Field` is the wiring nobody enjoys writing: one generated id
442
+ per field, `htmlFor` on the label, `aria-describedby` pointing at both the
443
+ description and the message, and `aria-invalid` flipped by the field's own
444
+ state. `FormMessage` renders nothing at all when there is no error, so a form
445
+ does not reserve a blank line under every input.
446
+
447
+ Without React Hook Form, `Field` / `FieldHint` / `FieldError` from the main
448
+ entry are the same layout with none of the binding.
449
+
297
450
  ## Sidebar
298
451
 
299
452
  A navigation column: it collapses into an icon rail, turns into a sliding sheet
@@ -506,8 +659,13 @@ column pinned, it does not reflow into cards.
506
659
  ## Localisation
507
660
 
508
661
  Component strings default to English and every one of them can be replaced.
509
- `Dialog`/`Sheet` take `closeLabel`, `Spinner` takes `label`, `Sidebar` takes
510
- `mobileTitle` / `mobileDescription` and `SidebarTrigger` takes `label`.
662
+ `Dialog`/`Sheet`/`Toaster` take `closeLabel`, `Spinner` takes `label`, `Sidebar`
663
+ takes `mobileTitle` / `mobileDescription`, `SidebarTrigger` takes `label`,
664
+ `BreadcrumbEllipsis` takes `label` and `TabsList` takes `overflowLabel`.
665
+
666
+ `Calendar` reads its month, weekday and day names from `Intl` — a `locale` tag
667
+ is enough — and takes a partial `labels` object for the four strings `Intl`
668
+ cannot supply (the nav buttons, the caption selects, the week-number column).
511
669
 
512
670
  `DataTable` has more strings than the rest, so it takes a partial `labels`
513
671
  object — anything omitted falls back to the English default:
@@ -537,14 +695,16 @@ does not hand-carry ~50 strings — and does not silently drift as the kit adds
537
695
  them:
538
696
 
539
697
  ```tsx
540
- import { dataTable as ru, common } from '@morze/ui/locales/ru'
698
+ import { dataTable as ru, calendar, common } from '@morze/ui/locales/ru'
541
699
 
542
700
  <DataTable labels={ru} locale="ru-RU" … />
701
+ <Calendar labels={calendar} locale="ru-RU" … />
543
702
  <Dialog><DialogContent closeLabel={common.close}>…</DialogContent></Dialog>
544
703
  ```
545
704
 
546
705
  `common` carries the strings the rest of the kit takes as individual props
547
- (`close`, `loading`, `sidebarNavigation`, `sidebarSections`, `toggleSidebar`).
706
+ (`close`, `loading`, `more`, `sidebarNavigation`, `sidebarSections`,
707
+ `toggleSidebar`), and `calendar` the four the month grid needs.
548
708
  A bundle is plain data — no React, no styles — so it is safe to import from a
549
709
  server component. `@morze/ui/locales/en` is the same shape for English, and
550
710
  `MorzeLocale` types a language of your own.
@@ -0,0 +1,128 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ var React2 = require('react');
5
+ var reactHookForm = require('react-hook-form');
6
+ var radixUi = require('radix-ui');
7
+ var clsx = require('clsx');
8
+ var jsxRuntime = require('react/jsx-runtime');
9
+
10
+ function _interopNamespace(e) {
11
+ if (e && e.__esModule) return e;
12
+ var n = Object.create(null);
13
+ if (e) {
14
+ Object.keys(e).forEach(function (k) {
15
+ if (k !== 'default') {
16
+ var d = Object.getOwnPropertyDescriptor(e, k);
17
+ Object.defineProperty(n, k, d.get ? d : {
18
+ enumerable: true,
19
+ get: function () { return e[k]; }
20
+ });
21
+ }
22
+ });
23
+ }
24
+ n.default = e;
25
+ return Object.freeze(n);
26
+ }
27
+
28
+ var React2__namespace = /*#__PURE__*/_interopNamespace(React2);
29
+
30
+ function cn(...inputs) {
31
+ return clsx.clsx(inputs);
32
+ }
33
+ function Label({ className, ...props }) {
34
+ return /* @__PURE__ */ jsxRuntime.jsx(radixUi.Label.Root, { "data-slot": "label", className: cn("mz-label", className), ...props });
35
+ }
36
+ var Form = reactHookForm.FormProvider;
37
+ var FormFieldContext = React2__namespace.createContext(null);
38
+ var FormItemContext = React2__namespace.createContext(null);
39
+ function FormField({ ...props }) {
40
+ return /* @__PURE__ */ jsxRuntime.jsx(FormFieldContext.Provider, { value: { name: props.name }, children: /* @__PURE__ */ jsxRuntime.jsx(reactHookForm.Controller, { ...props }) });
41
+ }
42
+ function useFormField() {
43
+ const fieldContext = React2__namespace.useContext(FormFieldContext);
44
+ const itemContext = React2__namespace.useContext(FormItemContext);
45
+ const { getFieldState } = reactHookForm.useFormContext();
46
+ if (!fieldContext) throw new Error("useFormField must be used inside a <FormField>");
47
+ if (!itemContext) throw new Error("useFormField must be used inside a <FormItem>");
48
+ const formState = reactHookForm.useFormState({ name: fieldContext.name });
49
+ const fieldState = getFieldState(fieldContext.name, formState);
50
+ const { id } = itemContext;
51
+ return {
52
+ id,
53
+ name: fieldContext.name,
54
+ formItemId: `${id}-form-item`,
55
+ formDescriptionId: `${id}-form-item-description`,
56
+ formMessageId: `${id}-form-item-message`,
57
+ ...fieldState
58
+ };
59
+ }
60
+ function FormItem({ className, ...props }) {
61
+ const id = React2__namespace.useId();
62
+ return /* @__PURE__ */ jsxRuntime.jsx(FormItemContext.Provider, { value: { id }, children: /* @__PURE__ */ jsxRuntime.jsx("div", { "data-slot": "form-item", className: cn("mz-field", className), ...props }) });
63
+ }
64
+ function FormLabel({ className, ...props }) {
65
+ const { error, formItemId } = useFormField();
66
+ return /* @__PURE__ */ jsxRuntime.jsx(
67
+ Label,
68
+ {
69
+ "data-slot": "form-label",
70
+ "data-error": !!error,
71
+ className: cn(error && "mz-label--error", className),
72
+ htmlFor: formItemId,
73
+ ...props
74
+ }
75
+ );
76
+ }
77
+ function FormControl({ ...props }) {
78
+ const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
79
+ return /* @__PURE__ */ jsxRuntime.jsx(
80
+ radixUi.Slot.Root,
81
+ {
82
+ "data-slot": "form-control",
83
+ id: formItemId,
84
+ "aria-describedby": error ? `${formDescriptionId} ${formMessageId}` : formDescriptionId,
85
+ "aria-invalid": !!error,
86
+ ...props
87
+ }
88
+ );
89
+ }
90
+ function FormDescription({ className, ...props }) {
91
+ const { formDescriptionId } = useFormField();
92
+ return /* @__PURE__ */ jsxRuntime.jsx(
93
+ "p",
94
+ {
95
+ "data-slot": "form-description",
96
+ id: formDescriptionId,
97
+ className: cn("mz-field__hint", className),
98
+ ...props
99
+ }
100
+ );
101
+ }
102
+ function FormMessage({ className, children, ...props }) {
103
+ const { error, formMessageId } = useFormField();
104
+ const body = error ? String(error.message ?? "") : children;
105
+ if (!body) return null;
106
+ return /* @__PURE__ */ jsxRuntime.jsx(
107
+ "p",
108
+ {
109
+ "data-slot": "form-message",
110
+ id: formMessageId,
111
+ role: "alert",
112
+ className: cn("mz-field__error", className),
113
+ ...props,
114
+ children: body
115
+ }
116
+ );
117
+ }
118
+
119
+ exports.Form = Form;
120
+ exports.FormControl = FormControl;
121
+ exports.FormDescription = FormDescription;
122
+ exports.FormField = FormField;
123
+ exports.FormItem = FormItem;
124
+ exports.FormLabel = FormLabel;
125
+ exports.FormMessage = FormMessage;
126
+ exports.useFormField = useFormField;
127
+ //# sourceMappingURL=index.cjs.map
128
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/lib/utils.ts","../../src/components/label.tsx","../../src/form/index.tsx"],"names":["clsx","jsx","LabelPrimitive","FormProvider","React2","Controller","useFormContext","useFormState","Slot"],"mappings":"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","file":"index.cjs","sourcesContent":["import { clsx, type ClassValue } from 'clsx'\n\n/**\n * Class name joiner. Morze UI ships plain, prefixed CSS rather than utility\n * classes, so there is nothing to merge — clsx is enough.\n */\nexport function cn(...inputs: ClassValue[]) {\n return clsx(inputs)\n}\n\nexport type Tone = 'primary' | 'accent' | 'danger' | 'warning' | 'info' | 'success'\n","'use client'\n\nimport * as React from 'react'\nimport { Label as LabelPrimitive } from 'radix-ui'\n\nimport { cn } from '../lib/utils'\n\nfunction Label({ className, ...props }: React.ComponentProps<typeof LabelPrimitive.Root>) {\n return <LabelPrimitive.Root data-slot=\"label\" className={cn('mz-label', className)} {...props} />\n}\n\nexport { Label }\n","'use client'\n\nimport * as React from 'react'\nimport {\n Controller,\n FormProvider,\n useFormContext,\n useFormState,\n type ControllerProps,\n type FieldPath,\n type FieldValues,\n} from 'react-hook-form'\nimport { Slot } from 'radix-ui'\n\nimport { cn } from '../lib/utils'\nimport { Label } from '../components/label'\n\n/**\n * React Hook Form bindings.\n *\n * import { Form, FormField, FormItem, FormLabel, FormControl, FormMessage }\n * from '@morze/ui/form'\n *\n * They live behind their own entry point because `react-hook-form` is an\n * optional peer dependency: an application that does not use it should not\n * carry it, and the main bundle stays free of every form library.\n *\n * What this adds over `Field` is the wiring nobody enjoys writing by hand —\n * one generated id per field, `htmlFor` on the label, `aria-describedby`\n * pointing at the description and the message, and `aria-invalid` flipped by\n * the field's own validation state.\n */\nconst Form = FormProvider\n\ntype FormFieldContextValue<\n TFieldValues extends FieldValues = FieldValues,\n TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,\n> = { name: TName }\n\nconst FormFieldContext = React.createContext<FormFieldContextValue | null>(null)\nconst FormItemContext = React.createContext<{ id: string } | null>(null)\n\nfunction FormField<\n TFieldValues extends FieldValues = FieldValues,\n TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,\n>({ ...props }: ControllerProps<TFieldValues, TName>) {\n return (\n <FormFieldContext.Provider value={{ name: props.name }}>\n <Controller {...props} />\n </FormFieldContext.Provider>\n )\n}\n\n/**\n * The ids and validation state of the field this is called inside.\n *\n * The state is read through `useFormState` scoped to this one name, so a\n * keystroke in one field does not re-render every other field's label.\n */\nfunction useFormField() {\n const fieldContext = React.useContext(FormFieldContext)\n const itemContext = React.useContext(FormItemContext)\n const { getFieldState } = useFormContext()\n\n if (!fieldContext) throw new Error('useFormField must be used inside a <FormField>')\n if (!itemContext) throw new Error('useFormField must be used inside a <FormItem>')\n\n const formState = useFormState({ name: fieldContext.name })\n const fieldState = getFieldState(fieldContext.name, formState)\n const { id } = itemContext\n\n return {\n id,\n name: fieldContext.name,\n formItemId: `${id}-form-item`,\n formDescriptionId: `${id}-form-item-description`,\n formMessageId: `${id}-form-item-message`,\n ...fieldState,\n }\n}\n\nfunction FormItem({ className, ...props }: React.ComponentProps<'div'>) {\n const id = React.useId()\n\n return (\n <FormItemContext.Provider value={{ id }}>\n <div data-slot=\"form-item\" className={cn('mz-field', className)} {...props} />\n </FormItemContext.Provider>\n )\n}\n\nfunction FormLabel({ className, ...props }: React.ComponentProps<typeof Label>) {\n const { error, formItemId } = useFormField()\n\n return (\n <Label\n data-slot=\"form-label\"\n data-error={!!error}\n className={cn(error && 'mz-label--error', className)}\n htmlFor={formItemId}\n {...props}\n />\n )\n}\n\n/**\n * Wraps the control itself — an `Input`, a `SelectTrigger`, anything. It\n * renders nothing of its own; it only stamps the id and the aria wiring onto\n * whatever child it is given.\n */\nfunction FormControl({ ...props }: React.ComponentProps<typeof Slot.Root>) {\n const { error, formItemId, formDescriptionId, formMessageId } = useFormField()\n\n return (\n <Slot.Root\n data-slot=\"form-control\"\n id={formItemId}\n aria-describedby={error ? `${formDescriptionId} ${formMessageId}` : formDescriptionId}\n aria-invalid={!!error}\n {...props}\n />\n )\n}\n\nfunction FormDescription({ className, ...props }: React.ComponentProps<'p'>) {\n const { formDescriptionId } = useFormField()\n\n return (\n <p\n data-slot=\"form-description\"\n id={formDescriptionId}\n className={cn('mz-field__hint', className)}\n {...props}\n />\n )\n}\n\n/**\n * The validation message. It renders the field's error when there is one and\n * `children` otherwise, and disappears entirely when there is neither — so a\n * form does not reserve a blank line under every input.\n */\nfunction FormMessage({ className, children, ...props }: React.ComponentProps<'p'>) {\n const { error, formMessageId } = useFormField()\n const body = error ? String(error.message ?? '') : children\n\n if (!body) return null\n\n return (\n <p\n data-slot=\"form-message\"\n id={formMessageId}\n role=\"alert\"\n className={cn('mz-field__error', className)}\n {...props}\n >\n {body}\n </p>\n )\n}\n\nexport {\n Form,\n FormField,\n FormItem,\n FormLabel,\n FormControl,\n FormDescription,\n FormMessage,\n useFormField,\n}\n"]}
@@ -0,0 +1,58 @@
1
+ import * as react_hook_form from 'react-hook-form';
2
+ import { FieldValues, FieldPath, ControllerProps } from 'react-hook-form';
3
+ import * as React from 'react';
4
+ import { Slot } from 'radix-ui';
5
+ import { L as Label } from '../label-Bf37KoRc.cjs';
6
+
7
+ /**
8
+ * React Hook Form bindings.
9
+ *
10
+ * import { Form, FormField, FormItem, FormLabel, FormControl, FormMessage }
11
+ * from '@morze/ui/form'
12
+ *
13
+ * They live behind their own entry point because `react-hook-form` is an
14
+ * optional peer dependency: an application that does not use it should not
15
+ * carry it, and the main bundle stays free of every form library.
16
+ *
17
+ * What this adds over `Field` is the wiring nobody enjoys writing by hand —
18
+ * one generated id per field, `htmlFor` on the label, `aria-describedby`
19
+ * pointing at the description and the message, and `aria-invalid` flipped by
20
+ * the field's own validation state.
21
+ */
22
+ declare const Form: <TFieldValues extends FieldValues, TContext = any, TTransformedValues = TFieldValues>({ children, watch, getValues, getErrors, getFieldState, setError, clearErrors, setValue, setValues, trigger, formState, resetField, reset, resetDefaultValues, handleSubmit, unregister, control, register, setFocus, subscribe, }: react_hook_form.FormProviderProps<TFieldValues, TContext, TTransformedValues>) => React.JSX.Element;
23
+ declare function FormField<TFieldValues extends FieldValues = FieldValues, TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>>({ ...props }: ControllerProps<TFieldValues, TName>): React.JSX.Element;
24
+ /**
25
+ * The ids and validation state of the field this is called inside.
26
+ *
27
+ * The state is read through `useFormState` scoped to this one name, so a
28
+ * keystroke in one field does not re-render every other field's label.
29
+ */
30
+ declare function useFormField(): {
31
+ invalid: boolean;
32
+ isDirty: boolean;
33
+ isTouched: boolean;
34
+ isValidating: boolean;
35
+ error?: react_hook_form.FieldError | undefined;
36
+ id: string;
37
+ name: string;
38
+ formItemId: string;
39
+ formDescriptionId: string;
40
+ formMessageId: string;
41
+ };
42
+ declare function FormItem({ className, ...props }: React.ComponentProps<'div'>): React.JSX.Element;
43
+ declare function FormLabel({ className, ...props }: React.ComponentProps<typeof Label>): React.JSX.Element;
44
+ /**
45
+ * Wraps the control itself — an `Input`, a `SelectTrigger`, anything. It
46
+ * renders nothing of its own; it only stamps the id and the aria wiring onto
47
+ * whatever child it is given.
48
+ */
49
+ declare function FormControl({ ...props }: React.ComponentProps<typeof Slot.Root>): React.JSX.Element;
50
+ declare function FormDescription({ className, ...props }: React.ComponentProps<'p'>): React.JSX.Element;
51
+ /**
52
+ * The validation message. It renders the field's error when there is one and
53
+ * `children` otherwise, and disappears entirely when there is neither — so a
54
+ * form does not reserve a blank line under every input.
55
+ */
56
+ declare function FormMessage({ className, children, ...props }: React.ComponentProps<'p'>): React.JSX.Element | null;
57
+
58
+ export { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, useFormField };
@@ -0,0 +1,58 @@
1
+ import * as react_hook_form from 'react-hook-form';
2
+ import { FieldValues, FieldPath, ControllerProps } from 'react-hook-form';
3
+ import * as React from 'react';
4
+ import { Slot } from 'radix-ui';
5
+ import { L as Label } from '../label-Bf37KoRc.js';
6
+
7
+ /**
8
+ * React Hook Form bindings.
9
+ *
10
+ * import { Form, FormField, FormItem, FormLabel, FormControl, FormMessage }
11
+ * from '@morze/ui/form'
12
+ *
13
+ * They live behind their own entry point because `react-hook-form` is an
14
+ * optional peer dependency: an application that does not use it should not
15
+ * carry it, and the main bundle stays free of every form library.
16
+ *
17
+ * What this adds over `Field` is the wiring nobody enjoys writing by hand —
18
+ * one generated id per field, `htmlFor` on the label, `aria-describedby`
19
+ * pointing at the description and the message, and `aria-invalid` flipped by
20
+ * the field's own validation state.
21
+ */
22
+ declare const Form: <TFieldValues extends FieldValues, TContext = any, TTransformedValues = TFieldValues>({ children, watch, getValues, getErrors, getFieldState, setError, clearErrors, setValue, setValues, trigger, formState, resetField, reset, resetDefaultValues, handleSubmit, unregister, control, register, setFocus, subscribe, }: react_hook_form.FormProviderProps<TFieldValues, TContext, TTransformedValues>) => React.JSX.Element;
23
+ declare function FormField<TFieldValues extends FieldValues = FieldValues, TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>>({ ...props }: ControllerProps<TFieldValues, TName>): React.JSX.Element;
24
+ /**
25
+ * The ids and validation state of the field this is called inside.
26
+ *
27
+ * The state is read through `useFormState` scoped to this one name, so a
28
+ * keystroke in one field does not re-render every other field's label.
29
+ */
30
+ declare function useFormField(): {
31
+ invalid: boolean;
32
+ isDirty: boolean;
33
+ isTouched: boolean;
34
+ isValidating: boolean;
35
+ error?: react_hook_form.FieldError | undefined;
36
+ id: string;
37
+ name: string;
38
+ formItemId: string;
39
+ formDescriptionId: string;
40
+ formMessageId: string;
41
+ };
42
+ declare function FormItem({ className, ...props }: React.ComponentProps<'div'>): React.JSX.Element;
43
+ declare function FormLabel({ className, ...props }: React.ComponentProps<typeof Label>): React.JSX.Element;
44
+ /**
45
+ * Wraps the control itself — an `Input`, a `SelectTrigger`, anything. It
46
+ * renders nothing of its own; it only stamps the id and the aria wiring onto
47
+ * whatever child it is given.
48
+ */
49
+ declare function FormControl({ ...props }: React.ComponentProps<typeof Slot.Root>): React.JSX.Element;
50
+ declare function FormDescription({ className, ...props }: React.ComponentProps<'p'>): React.JSX.Element;
51
+ /**
52
+ * The validation message. It renders the field's error when there is one and
53
+ * `children` otherwise, and disappears entirely when there is neither — so a
54
+ * form does not reserve a blank line under every input.
55
+ */
56
+ declare function FormMessage({ className, children, ...props }: React.ComponentProps<'p'>): React.JSX.Element | null;
57
+
58
+ export { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, useFormField };
@@ -0,0 +1,99 @@
1
+ "use client";
2
+ import * as React2 from 'react';
3
+ import { FormProvider, Controller, useFormContext, useFormState } from 'react-hook-form';
4
+ import { Slot, Label as Label$1 } from 'radix-ui';
5
+ import { clsx } from 'clsx';
6
+ import { jsx } from 'react/jsx-runtime';
7
+
8
+ function cn(...inputs) {
9
+ return clsx(inputs);
10
+ }
11
+ function Label({ className, ...props }) {
12
+ return /* @__PURE__ */ jsx(Label$1.Root, { "data-slot": "label", className: cn("mz-label", className), ...props });
13
+ }
14
+ var Form = FormProvider;
15
+ var FormFieldContext = React2.createContext(null);
16
+ var FormItemContext = React2.createContext(null);
17
+ function FormField({ ...props }) {
18
+ return /* @__PURE__ */ jsx(FormFieldContext.Provider, { value: { name: props.name }, children: /* @__PURE__ */ jsx(Controller, { ...props }) });
19
+ }
20
+ function useFormField() {
21
+ const fieldContext = React2.useContext(FormFieldContext);
22
+ const itemContext = React2.useContext(FormItemContext);
23
+ const { getFieldState } = useFormContext();
24
+ if (!fieldContext) throw new Error("useFormField must be used inside a <FormField>");
25
+ if (!itemContext) throw new Error("useFormField must be used inside a <FormItem>");
26
+ const formState = useFormState({ name: fieldContext.name });
27
+ const fieldState = getFieldState(fieldContext.name, formState);
28
+ const { id } = itemContext;
29
+ return {
30
+ id,
31
+ name: fieldContext.name,
32
+ formItemId: `${id}-form-item`,
33
+ formDescriptionId: `${id}-form-item-description`,
34
+ formMessageId: `${id}-form-item-message`,
35
+ ...fieldState
36
+ };
37
+ }
38
+ function FormItem({ className, ...props }) {
39
+ const id = React2.useId();
40
+ return /* @__PURE__ */ jsx(FormItemContext.Provider, { value: { id }, children: /* @__PURE__ */ jsx("div", { "data-slot": "form-item", className: cn("mz-field", className), ...props }) });
41
+ }
42
+ function FormLabel({ className, ...props }) {
43
+ const { error, formItemId } = useFormField();
44
+ return /* @__PURE__ */ jsx(
45
+ Label,
46
+ {
47
+ "data-slot": "form-label",
48
+ "data-error": !!error,
49
+ className: cn(error && "mz-label--error", className),
50
+ htmlFor: formItemId,
51
+ ...props
52
+ }
53
+ );
54
+ }
55
+ function FormControl({ ...props }) {
56
+ const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
57
+ return /* @__PURE__ */ jsx(
58
+ Slot.Root,
59
+ {
60
+ "data-slot": "form-control",
61
+ id: formItemId,
62
+ "aria-describedby": error ? `${formDescriptionId} ${formMessageId}` : formDescriptionId,
63
+ "aria-invalid": !!error,
64
+ ...props
65
+ }
66
+ );
67
+ }
68
+ function FormDescription({ className, ...props }) {
69
+ const { formDescriptionId } = useFormField();
70
+ return /* @__PURE__ */ jsx(
71
+ "p",
72
+ {
73
+ "data-slot": "form-description",
74
+ id: formDescriptionId,
75
+ className: cn("mz-field__hint", className),
76
+ ...props
77
+ }
78
+ );
79
+ }
80
+ function FormMessage({ className, children, ...props }) {
81
+ const { error, formMessageId } = useFormField();
82
+ const body = error ? String(error.message ?? "") : children;
83
+ if (!body) return null;
84
+ return /* @__PURE__ */ jsx(
85
+ "p",
86
+ {
87
+ "data-slot": "form-message",
88
+ id: formMessageId,
89
+ role: "alert",
90
+ className: cn("mz-field__error", className),
91
+ ...props,
92
+ children: body
93
+ }
94
+ );
95
+ }
96
+
97
+ export { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, useFormField };
98
+ //# sourceMappingURL=index.js.map
99
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/lib/utils.ts","../../src/components/label.tsx","../../src/form/index.tsx"],"names":["LabelPrimitive","jsx"],"mappings":"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","file":"index.js","sourcesContent":["import { clsx, type ClassValue } from 'clsx'\n\n/**\n * Class name joiner. Morze UI ships plain, prefixed CSS rather than utility\n * classes, so there is nothing to merge — clsx is enough.\n */\nexport function cn(...inputs: ClassValue[]) {\n return clsx(inputs)\n}\n\nexport type Tone = 'primary' | 'accent' | 'danger' | 'warning' | 'info' | 'success'\n","'use client'\n\nimport * as React from 'react'\nimport { Label as LabelPrimitive } from 'radix-ui'\n\nimport { cn } from '../lib/utils'\n\nfunction Label({ className, ...props }: React.ComponentProps<typeof LabelPrimitive.Root>) {\n return <LabelPrimitive.Root data-slot=\"label\" className={cn('mz-label', className)} {...props} />\n}\n\nexport { Label }\n","'use client'\n\nimport * as React from 'react'\nimport {\n Controller,\n FormProvider,\n useFormContext,\n useFormState,\n type ControllerProps,\n type FieldPath,\n type FieldValues,\n} from 'react-hook-form'\nimport { Slot } from 'radix-ui'\n\nimport { cn } from '../lib/utils'\nimport { Label } from '../components/label'\n\n/**\n * React Hook Form bindings.\n *\n * import { Form, FormField, FormItem, FormLabel, FormControl, FormMessage }\n * from '@morze/ui/form'\n *\n * They live behind their own entry point because `react-hook-form` is an\n * optional peer dependency: an application that does not use it should not\n * carry it, and the main bundle stays free of every form library.\n *\n * What this adds over `Field` is the wiring nobody enjoys writing by hand —\n * one generated id per field, `htmlFor` on the label, `aria-describedby`\n * pointing at the description and the message, and `aria-invalid` flipped by\n * the field's own validation state.\n */\nconst Form = FormProvider\n\ntype FormFieldContextValue<\n TFieldValues extends FieldValues = FieldValues,\n TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,\n> = { name: TName }\n\nconst FormFieldContext = React.createContext<FormFieldContextValue | null>(null)\nconst FormItemContext = React.createContext<{ id: string } | null>(null)\n\nfunction FormField<\n TFieldValues extends FieldValues = FieldValues,\n TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,\n>({ ...props }: ControllerProps<TFieldValues, TName>) {\n return (\n <FormFieldContext.Provider value={{ name: props.name }}>\n <Controller {...props} />\n </FormFieldContext.Provider>\n )\n}\n\n/**\n * The ids and validation state of the field this is called inside.\n *\n * The state is read through `useFormState` scoped to this one name, so a\n * keystroke in one field does not re-render every other field's label.\n */\nfunction useFormField() {\n const fieldContext = React.useContext(FormFieldContext)\n const itemContext = React.useContext(FormItemContext)\n const { getFieldState } = useFormContext()\n\n if (!fieldContext) throw new Error('useFormField must be used inside a <FormField>')\n if (!itemContext) throw new Error('useFormField must be used inside a <FormItem>')\n\n const formState = useFormState({ name: fieldContext.name })\n const fieldState = getFieldState(fieldContext.name, formState)\n const { id } = itemContext\n\n return {\n id,\n name: fieldContext.name,\n formItemId: `${id}-form-item`,\n formDescriptionId: `${id}-form-item-description`,\n formMessageId: `${id}-form-item-message`,\n ...fieldState,\n }\n}\n\nfunction FormItem({ className, ...props }: React.ComponentProps<'div'>) {\n const id = React.useId()\n\n return (\n <FormItemContext.Provider value={{ id }}>\n <div data-slot=\"form-item\" className={cn('mz-field', className)} {...props} />\n </FormItemContext.Provider>\n )\n}\n\nfunction FormLabel({ className, ...props }: React.ComponentProps<typeof Label>) {\n const { error, formItemId } = useFormField()\n\n return (\n <Label\n data-slot=\"form-label\"\n data-error={!!error}\n className={cn(error && 'mz-label--error', className)}\n htmlFor={formItemId}\n {...props}\n />\n )\n}\n\n/**\n * Wraps the control itself — an `Input`, a `SelectTrigger`, anything. It\n * renders nothing of its own; it only stamps the id and the aria wiring onto\n * whatever child it is given.\n */\nfunction FormControl({ ...props }: React.ComponentProps<typeof Slot.Root>) {\n const { error, formItemId, formDescriptionId, formMessageId } = useFormField()\n\n return (\n <Slot.Root\n data-slot=\"form-control\"\n id={formItemId}\n aria-describedby={error ? `${formDescriptionId} ${formMessageId}` : formDescriptionId}\n aria-invalid={!!error}\n {...props}\n />\n )\n}\n\nfunction FormDescription({ className, ...props }: React.ComponentProps<'p'>) {\n const { formDescriptionId } = useFormField()\n\n return (\n <p\n data-slot=\"form-description\"\n id={formDescriptionId}\n className={cn('mz-field__hint', className)}\n {...props}\n />\n )\n}\n\n/**\n * The validation message. It renders the field's error when there is one and\n * `children` otherwise, and disappears entirely when there is neither — so a\n * form does not reserve a blank line under every input.\n */\nfunction FormMessage({ className, children, ...props }: React.ComponentProps<'p'>) {\n const { error, formMessageId } = useFormField()\n const body = error ? String(error.message ?? '') : children\n\n if (!body) return null\n\n return (\n <p\n data-slot=\"form-message\"\n id={formMessageId}\n role=\"alert\"\n className={cn('mz-field__error', className)}\n {...props}\n >\n {body}\n </p>\n )\n}\n\nexport {\n Form,\n FormField,\n FormItem,\n FormLabel,\n FormControl,\n FormDescription,\n FormMessage,\n useFormField,\n}\n"]}