@kodffe/ui 0.0.0-stage → 0.10.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/dist/_virtual/_rolldown/runtime.js +11 -0
- package/dist/accordion/accordion.d.ts +44 -0
- package/dist/accordion/accordion.js +84 -0
- package/dist/accordion/accordion.js.map +1 -0
- package/dist/alert/alert.d.ts +17 -0
- package/dist/alert/alert.js +59 -0
- package/dist/alert/alert.js.map +1 -0
- package/dist/app-shell/app-footer.d.ts +29 -0
- package/dist/app-shell/app-footer.js +39 -0
- package/dist/app-shell/app-footer.js.map +1 -0
- package/dist/app-shell/app-shell-context.d.ts +10 -0
- package/dist/app-shell/app-shell-context.js +12 -0
- package/dist/app-shell/app-shell-context.js.map +1 -0
- package/dist/app-shell/app-shell.d.ts +42 -0
- package/dist/app-shell/app-shell.js +191 -0
- package/dist/app-shell/app-shell.js.map +1 -0
- package/dist/app-shell/floating-actions.d.ts +19 -0
- package/dist/app-shell/floating-actions.js +59 -0
- package/dist/app-shell/floating-actions.js.map +1 -0
- package/dist/app-shell/workspace-switcher.d.ts +25 -0
- package/dist/app-shell/workspace-switcher.js +82 -0
- package/dist/app-shell/workspace-switcher.js.map +1 -0
- package/dist/badge/badge.d.ts +19 -0
- package/dist/badge/badge.js +59 -0
- package/dist/badge/badge.js.map +1 -0
- package/dist/banner/banner.d.ts +22 -0
- package/dist/banner/banner.js +80 -0
- package/dist/banner/banner.js.map +1 -0
- package/dist/breadcrumbs/breadcrumbs.d.ts +18 -0
- package/dist/breadcrumbs/breadcrumbs.js +45 -0
- package/dist/breadcrumbs/breadcrumbs.js.map +1 -0
- package/dist/button/button.d.ts +38 -0
- package/dist/button/button.js +83 -0
- package/dist/button/button.js.map +1 -0
- package/dist/card/card.d.ts +159 -0
- package/dist/card/card.js +390 -0
- package/dist/card/card.js.map +1 -0
- package/dist/card/insight-card.d.ts +19 -0
- package/dist/card/insight-card.js +51 -0
- package/dist/card/insight-card.js.map +1 -0
- package/dist/card/integration-card.d.ts +19 -0
- package/dist/card/integration-card.js +70 -0
- package/dist/card/integration-card.js.map +1 -0
- package/dist/charts/charts.d.ts +75 -0
- package/dist/charts/charts.js +453 -0
- package/dist/charts/charts.js.map +1 -0
- package/dist/choice/choice.d.ts +39 -0
- package/dist/choice/choice.js +143 -0
- package/dist/choice/choice.js.map +1 -0
- package/dist/combobox/combobox.d.ts +46 -0
- package/dist/combobox/combobox.js +196 -0
- package/dist/combobox/combobox.js.map +1 -0
- package/dist/command-palette/command-palette.d.ts +35 -0
- package/dist/command-palette/command-palette.js +164 -0
- package/dist/command-palette/command-palette.js.map +1 -0
- package/dist/confirm-dialog/confirm-dialog.d.ts +37 -0
- package/dist/confirm-dialog/confirm-dialog.js +106 -0
- package/dist/confirm-dialog/confirm-dialog.js.map +1 -0
- package/dist/content/assistant.d.ts +86 -0
- package/dist/content/assistant.js +213 -0
- package/dist/content/assistant.js.map +1 -0
- package/dist/content/content.d.ts +45 -0
- package/dist/content/content.js +136 -0
- package/dist/content/content.js.map +1 -0
- package/dist/data-display/avatar-sizes.d.ts +8 -0
- package/dist/data-display/avatar-sizes.js +12 -0
- package/dist/data-display/avatar-sizes.js.map +1 -0
- package/dist/data-display/avatar.d.ts +21 -0
- package/dist/data-display/avatar.js +53 -0
- package/dist/data-display/avatar.js.map +1 -0
- package/dist/data-display/chip.d.ts +20 -0
- package/dist/data-display/chip.js +64 -0
- package/dist/data-display/chip.js.map +1 -0
- package/dist/data-display/code-block.d.ts +14 -0
- package/dist/data-display/code-block.js +50 -0
- package/dist/data-display/code-block.js.map +1 -0
- package/dist/data-display/data-display.d.ts +73 -0
- package/dist/data-display/data-display.js +227 -0
- package/dist/data-display/data-display.js.map +1 -0
- package/dist/data-display/insights.d.ts +120 -0
- package/dist/data-display/insights.js +220 -0
- package/dist/data-display/insights.js.map +1 -0
- package/dist/data-table/cells.d.ts +86 -0
- package/dist/data-table/cells.js +163 -0
- package/dist/data-table/cells.js.map +1 -0
- package/dist/data-table/data-table.d.ts +112 -0
- package/dist/data-table/data-table.js +558 -0
- package/dist/data-table/data-table.js.map +1 -0
- package/dist/date-picker/date-picker.d.ts +61 -0
- package/dist/date-picker/date-picker.js +309 -0
- package/dist/date-picker/date-picker.js.map +1 -0
- package/dist/drawer/drawer.d.ts +32 -0
- package/dist/drawer/drawer.js +72 -0
- package/dist/drawer/drawer.js.map +1 -0
- package/dist/empty-state/empty-state.d.ts +25 -0
- package/dist/empty-state/empty-state.js +78 -0
- package/dist/empty-state/empty-state.js.map +1 -0
- package/dist/error-state/error-state.d.ts +19 -0
- package/dist/error-state/error-state.js +56 -0
- package/dist/error-state/error-state.js.map +1 -0
- package/dist/event-calendar/calendar-logic.test.d.ts +1 -0
- package/dist/event-calendar/dates.d.ts +35 -0
- package/dist/event-calendar/dates.js +55 -0
- package/dist/event-calendar/dates.js.map +1 -0
- package/dist/event-calendar/event-calendar.d.ts +125 -0
- package/dist/event-calendar/event-calendar.js +795 -0
- package/dist/event-calendar/event-calendar.js.map +1 -0
- package/dist/event-calendar/event-details.d.ts +50 -0
- package/dist/event-calendar/event-details.js +124 -0
- package/dist/event-calendar/event-details.js.map +1 -0
- package/dist/event-calendar/layout.d.ts +48 -0
- package/dist/event-calendar/layout.js +55 -0
- package/dist/event-calendar/layout.js.map +1 -0
- package/dist/file-upload/file-upload.d.ts +70 -0
- package/dist/file-upload/file-upload.js +307 -0
- package/dist/file-upload/file-upload.js.map +1 -0
- package/dist/filters/filters.d.ts +48 -0
- package/dist/filters/filters.js +103 -0
- package/dist/filters/filters.js.map +1 -0
- package/dist/form-field/form-field.d.ts +39 -0
- package/dist/form-field/form-field.js +88 -0
- package/dist/form-field/form-field.js.map +1 -0
- package/dist/hover-card/hover-card.d.ts +15 -0
- package/dist/hover-card/hover-card.js +27 -0
- package/dist/hover-card/hover-card.js.map +1 -0
- package/dist/hover-card/person-hover-card.d.ts +26 -0
- package/dist/hover-card/person-hover-card.js +85 -0
- package/dist/hover-card/person-hover-card.js.map +1 -0
- package/dist/icon/icon-tile.d.ts +20 -0
- package/dist/icon/icon-tile.js +55 -0
- package/dist/icon/icon-tile.js.map +1 -0
- package/dist/icon/icon.d.ts +14 -0
- package/dist/icon/icon.js +28 -0
- package/dist/icon/icon.js.map +1 -0
- package/dist/icon/icons.generated.d.ts +358 -0
- package/dist/icon/icons.generated.js +362 -0
- package/dist/icon/icons.generated.js.map +1 -0
- package/dist/index.d.ts +90 -0
- package/dist/index.js +88 -0
- package/dist/input/extra-inputs.d.ts +122 -0
- package/dist/input/extra-inputs.js +351 -0
- package/dist/input/extra-inputs.js.map +1 -0
- package/dist/input/field-styles.d.ts +5 -0
- package/dist/input/field-styles.js +14 -0
- package/dist/input/field-styles.js.map +1 -0
- package/dist/input/input.d.ts +50 -0
- package/dist/input/input.js +107 -0
- package/dist/input/input.js.map +1 -0
- package/dist/layout/layout.d.ts +41 -0
- package/dist/layout/layout.js +90 -0
- package/dist/layout/layout.js.map +1 -0
- package/dist/lib/control-styles.d.ts +9 -0
- package/dist/lib/control-styles.js +16 -0
- package/dist/lib/control-styles.js.map +1 -0
- package/dist/lib/cx.d.ts +2 -0
- package/dist/lib/cx.js +8 -0
- package/dist/lib/cx.js.map +1 -0
- package/dist/lib/glyphs.d.ts +13 -0
- package/dist/lib/glyphs.js +67 -0
- package/dist/lib/glyphs.js.map +1 -0
- package/dist/lib/segmented-styles.d.ts +8 -0
- package/dist/lib/segmented-styles.js +12 -0
- package/dist/lib/segmented-styles.js.map +1 -0
- package/dist/lib/select-on-arrow.d.ts +7 -0
- package/dist/lib/select-on-arrow.js +21 -0
- package/dist/lib/select-on-arrow.js.map +1 -0
- package/dist/lib/skip-link.d.ts +5 -0
- package/dist/lib/skip-link.js +13 -0
- package/dist/lib/skip-link.js.map +1 -0
- package/dist/lib/use-media-query.d.ts +4 -0
- package/dist/lib/use-media-query.js +15 -0
- package/dist/lib/use-media-query.js.map +1 -0
- package/dist/lists/lists.d.ts +76 -0
- package/dist/lists/lists.js +133 -0
- package/dist/lists/lists.js.map +1 -0
- package/dist/locale-switch/locale-switch.d.ts +29 -0
- package/dist/locale-switch/locale-switch.js +62 -0
- package/dist/locale-switch/locale-switch.js.map +1 -0
- package/dist/logo/logo.d.ts +31 -0
- package/dist/logo/logo.js +157 -0
- package/dist/logo/logo.js.map +1 -0
- package/dist/logo/product-logo.d.ts +19 -0
- package/dist/logo/product-logo.js +46 -0
- package/dist/logo/product-logo.js.map +1 -0
- package/dist/marketing/marketing.d.ts +176 -0
- package/dist/marketing/marketing.js +451 -0
- package/dist/marketing/marketing.js.map +1 -0
- package/dist/marketing/newsletter-form.d.ts +21 -0
- package/dist/marketing/newsletter-form.js +100 -0
- package/dist/marketing/newsletter-form.js.map +1 -0
- package/dist/mascot/mascot.d.ts +28 -0
- package/dist/mascot/mascot.js +28 -0
- package/dist/mascot/mascot.js.map +1 -0
- package/dist/mascot/mascots.generated.d.ts +284 -0
- package/dist/mascot/mascots.generated.js +287 -0
- package/dist/mascot/mascots.generated.js.map +1 -0
- package/dist/master-detail/master-detail.d.ts +30 -0
- package/dist/master-detail/master-detail.js +71 -0
- package/dist/master-detail/master-detail.js.map +1 -0
- package/dist/menu/account-menu.d.ts +44 -0
- package/dist/menu/account-menu.js +78 -0
- package/dist/menu/account-menu.js.map +1 -0
- package/dist/menu/context-menu.d.ts +31 -0
- package/dist/menu/context-menu.js +111 -0
- package/dist/menu/context-menu.js.map +1 -0
- package/dist/menu/dropdown-menu.d.ts +49 -0
- package/dist/menu/dropdown-menu.js +152 -0
- package/dist/menu/dropdown-menu.js.map +1 -0
- package/dist/menu/menu-styles.d.ts +8 -0
- package/dist/menu/menu-styles.js +6 -0
- package/dist/menu/menu-styles.js.map +1 -0
- package/dist/menu/menubar.d.ts +31 -0
- package/dist/menu/menubar.js +125 -0
- package/dist/menu/menubar.js.map +1 -0
- package/dist/modal/modal.d.ts +34 -0
- package/dist/modal/modal.js +58 -0
- package/dist/modal/modal.js.map +1 -0
- package/dist/notification-center/notification-center.d.ts +51 -0
- package/dist/notification-center/notification-center.js +244 -0
- package/dist/notification-center/notification-center.js.map +1 -0
- package/dist/otp-input/otp-input.d.ts +23 -0
- package/dist/otp-input/otp-input.js +61 -0
- package/dist/otp-input/otp-input.js.map +1 -0
- package/dist/overlay/overlay.d.ts +53 -0
- package/dist/overlay/overlay.js +103 -0
- package/dist/overlay/overlay.js.map +1 -0
- package/dist/page-header/page-header.d.ts +55 -0
- package/dist/page-header/page-header.js +114 -0
- package/dist/page-header/page-header.js.map +1 -0
- package/dist/page-header/record-headers.d.ts +51 -0
- package/dist/page-header/record-headers.js +134 -0
- package/dist/page-header/record-headers.js.map +1 -0
- package/dist/page-header/section-header.d.ts +21 -0
- package/dist/page-header/section-header.js +44 -0
- package/dist/page-header/section-header.js.map +1 -0
- package/dist/page-navigation/page-navigation.d.ts +59 -0
- package/dist/page-navigation/page-navigation.js +136 -0
- package/dist/page-navigation/page-navigation.js.map +1 -0
- package/dist/pagination/pagination.d.ts +28 -0
- package/dist/pagination/pagination.js +102 -0
- package/dist/pagination/pagination.js.map +1 -0
- package/dist/phone-input/phone-input.d.ts +34 -0
- package/dist/phone-input/phone-input.js +171 -0
- package/dist/phone-input/phone-input.js.map +1 -0
- package/dist/popover/popover.d.ts +23 -0
- package/dist/popover/popover.js +40 -0
- package/dist/popover/popover.js.map +1 -0
- package/dist/progress/progress.d.ts +21 -0
- package/dist/progress/progress.js +49 -0
- package/dist/progress/progress.js.map +1 -0
- package/dist/segmented-control/segmented-control.d.ts +32 -0
- package/dist/segmented-control/segmented-control.js +29 -0
- package/dist/segmented-control/segmented-control.js.map +1 -0
- package/dist/select/select.d.ts +48 -0
- package/dist/select/select.js +136 -0
- package/dist/select/select.js.map +1 -0
- package/dist/settings/permission-matrix.d.ts +29 -0
- package/dist/settings/permission-matrix.js +101 -0
- package/dist/settings/permission-matrix.js.map +1 -0
- package/dist/settings/settings-row.d.ts +32 -0
- package/dist/settings/settings-row.js +63 -0
- package/dist/settings/settings-row.js.map +1 -0
- package/dist/side-nav/side-nav.d.ts +36 -0
- package/dist/side-nav/side-nav.js +123 -0
- package/dist/side-nav/side-nav.js.map +1 -0
- package/dist/site-header/site-header.d.ts +32 -0
- package/dist/site-header/site-header.js +99 -0
- package/dist/site-header/site-header.js.map +1 -0
- package/dist/skeleton/skeleton.d.ts +16 -0
- package/dist/skeleton/skeleton.js +19 -0
- package/dist/skeleton/skeleton.js.map +1 -0
- package/dist/slider/slider.d.ts +15 -0
- package/dist/slider/slider.js +48 -0
- package/dist/slider/slider.js.map +1 -0
- package/dist/spinner/spinner.d.ts +15 -0
- package/dist/spinner/spinner.js +42 -0
- package/dist/spinner/spinner.js.map +1 -0
- package/dist/stepper/step-dots.d.ts +16 -0
- package/dist/stepper/step-dots.js +26 -0
- package/dist/stepper/step-dots.js.map +1 -0
- package/dist/stepper/stepper.d.ts +26 -0
- package/dist/stepper/stepper.js +113 -0
- package/dist/stepper/stepper.js.map +1 -0
- package/dist/tabs/tabs.d.ts +31 -0
- package/dist/tabs/tabs.js +66 -0
- package/dist/tabs/tabs.js.map +1 -0
- package/dist/templates/app-templates.d.ts +126 -0
- package/dist/templates/app-templates.js +153 -0
- package/dist/templates/app-templates.js.map +1 -0
- package/dist/templates/site-templates.d.ts +49 -0
- package/dist/templates/site-templates.js +110 -0
- package/dist/templates/site-templates.js.map +1 -0
- package/dist/toast/toast-store.d.ts +54 -0
- package/dist/toast/toast-store.js +49 -0
- package/dist/toast/toast-store.js.map +1 -0
- package/dist/toast/toaster.d.ts +15 -0
- package/dist/toast/toaster.js +124 -0
- package/dist/toast/toaster.js.map +1 -0
- package/dist/tooltip/tooltip.d.ts +29 -0
- package/dist/tooltip/tooltip.js +54 -0
- package/dist/tooltip/tooltip.js.map +1 -0
- package/package.json +88 -3
- package/styles.css +521 -0
- package/README.md +0 -3
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cx as e } from "../lib/cx.js";
|
|
3
|
+
import { Icon as t } from "../icon/icon.js";
|
|
4
|
+
import { Button as n, IconButton as r } from "../button/button.js";
|
|
5
|
+
import { Progress as i } from "../progress/progress.js";
|
|
6
|
+
import { Glyph as a } from "../lib/glyphs.js";
|
|
7
|
+
import { useFieldProps as o } from "../input/input.js";
|
|
8
|
+
import { jsx as s, jsxs as c } from "react/jsx-runtime";
|
|
9
|
+
import { useEffect as l, useId as u, useRef as d, useState as f } from "react";
|
|
10
|
+
//#region src/file-upload/file-upload.tsx
|
|
11
|
+
var p = {
|
|
12
|
+
title: "Drag files here or browse",
|
|
13
|
+
browse: "browse",
|
|
14
|
+
constraints: "{types} up to {size}",
|
|
15
|
+
remove: "Remove {name}",
|
|
16
|
+
tooBig: "Larger than {size}",
|
|
17
|
+
wrongType: "File type not allowed",
|
|
18
|
+
tooMany: "Only {max} files allowed",
|
|
19
|
+
uploading: "Uploading",
|
|
20
|
+
done: "Uploaded",
|
|
21
|
+
chooseFile: "Choose file",
|
|
22
|
+
uploadImage: "Upload image",
|
|
23
|
+
changeImage: "Change image",
|
|
24
|
+
removeImage: "Remove",
|
|
25
|
+
imagePreview: "Current image",
|
|
26
|
+
images: "Images"
|
|
27
|
+
};
|
|
28
|
+
function m(e) {
|
|
29
|
+
if (e < 1024) return `${e} B`;
|
|
30
|
+
let t = [
|
|
31
|
+
"KB",
|
|
32
|
+
"MB",
|
|
33
|
+
"GB"
|
|
34
|
+
], n = e / 1024, r = 0;
|
|
35
|
+
for (; n >= 1024 && r < t.length - 1;) n /= 1024, r++;
|
|
36
|
+
return `${n < 10 ? n.toFixed(1) : Math.round(n)} ${t[r]}`;
|
|
37
|
+
}
|
|
38
|
+
function h(e, t) {
|
|
39
|
+
if (!t) return !0;
|
|
40
|
+
let n = e.name.toLowerCase();
|
|
41
|
+
return t.split(",").some((t) => {
|
|
42
|
+
let r = t.trim().toLowerCase();
|
|
43
|
+
return r.startsWith(".") ? n.endsWith(r) : r.endsWith("/*") ? e.type.startsWith(r.slice(0, -1)) : e.type === r;
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
function g(e) {
|
|
47
|
+
let [t, n] = f();
|
|
48
|
+
return l(() => {
|
|
49
|
+
if (!e || !e.type.startsWith("image/")) return n(void 0);
|
|
50
|
+
let t = URL.createObjectURL(e);
|
|
51
|
+
return n(t), () => URL.revokeObjectURL(t);
|
|
52
|
+
}, [e]), t;
|
|
53
|
+
}
|
|
54
|
+
function _({ file: e }) {
|
|
55
|
+
let t = g(e);
|
|
56
|
+
return t ? /* @__PURE__ */ s("img", {
|
|
57
|
+
src: t,
|
|
58
|
+
alt: "",
|
|
59
|
+
className: "size-10 shrink-0 rounded-sm object-cover"
|
|
60
|
+
}) : /* @__PURE__ */ s("span", {
|
|
61
|
+
className: "grid size-10 shrink-0 place-items-center rounded-sm bg-surface-muted font-mono text-[10px] font-bold text-fg-muted uppercase",
|
|
62
|
+
children: e.name.split(".").pop()?.slice(0, 4)
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
function v({ items: r, onAdd: i, onRemove: a, onReject: l, accept: g, maxSize: _, maxFiles: v, multiple: b = !0, disabled: S, variant: C = "dropzone", src: w, onClear: T, imageShape: E = "square", fallbackText: D, labels: O, className: k, ...A }) {
|
|
66
|
+
let j = C !== "image" && b, M = {
|
|
67
|
+
...p,
|
|
68
|
+
...O
|
|
69
|
+
}, N = o({
|
|
70
|
+
...A,
|
|
71
|
+
disabled: S
|
|
72
|
+
}), P = d(null), F = u(), [I, L] = f(!1), R = (e) => {
|
|
73
|
+
if (!e || N.disabled) return;
|
|
74
|
+
let t = Array.from(e), n = [], o = [], s = C === "image" ? 1 : v === void 0 ? Infinity : v - r.length;
|
|
75
|
+
for (let e of t) h(e, g) ? _ !== void 0 && e.size > _ ? o.push({
|
|
76
|
+
file: e,
|
|
77
|
+
reason: M.tooBig.replace("{size}", m(_))
|
|
78
|
+
}) : s <= 0 ? o.push({
|
|
79
|
+
file: e,
|
|
80
|
+
reason: M.tooMany.replace("{max}", String(C === "image" ? 1 : v))
|
|
81
|
+
}) : (n.push(e), s--) : o.push({
|
|
82
|
+
file: e,
|
|
83
|
+
reason: M.wrongType
|
|
84
|
+
});
|
|
85
|
+
n.length && (C === "image" && r.forEach((e) => a(e.id)), i(j ? n : n.slice(0, 1))), o.length && l?.(o), P.current && (P.current.value = "");
|
|
86
|
+
}, z = (e) => {
|
|
87
|
+
e.preventDefault(), L(!1), R(e.dataTransfer.files);
|
|
88
|
+
}, B = g?.split(",").map((e) => {
|
|
89
|
+
let t = e.trim().toLowerCase();
|
|
90
|
+
return t === "image/*" ? M.images : t.endsWith("/*") ? t.slice(0, -2).toUpperCase() : (t.includes("/") ? t.split("/")[1].replace("+xml", "") : t.replace(/^\./, "")).toUpperCase();
|
|
91
|
+
}).join(", "), V = _ ? m(_) : void 0, H = B && V ? M.constraints.replace("{types}", B).replace("{size}", V) : B ?? V, U = [N["aria-describedby"], H ? F : void 0].filter(Boolean).join(" ") || void 0, W = /* @__PURE__ */ s("input", {
|
|
92
|
+
ref: P,
|
|
93
|
+
type: "file",
|
|
94
|
+
id: N.id,
|
|
95
|
+
accept: g,
|
|
96
|
+
multiple: j,
|
|
97
|
+
disabled: N.disabled,
|
|
98
|
+
"aria-invalid": N["aria-invalid"],
|
|
99
|
+
"aria-describedby": U,
|
|
100
|
+
onChange: (e) => R(e.target.files),
|
|
101
|
+
tabIndex: C === "dropzone" ? void 0 : -1,
|
|
102
|
+
className: "sr-only"
|
|
103
|
+
}), G = u(), K = (e) => N.id ? `${N.id}-label ${e}` : void 0, q = (e) => H || e ? /* @__PURE__ */ s("span", {
|
|
104
|
+
id: F,
|
|
105
|
+
className: "text-sm text-fg-muted",
|
|
106
|
+
children: [e, H].filter(Boolean).join(" · ")
|
|
107
|
+
}) : null, J = () => P.current?.click();
|
|
108
|
+
if (C === "image") return /* @__PURE__ */ s(x, {
|
|
109
|
+
item: r[0],
|
|
110
|
+
src: w,
|
|
111
|
+
shape: E,
|
|
112
|
+
input: W,
|
|
113
|
+
hint: q(D && !r[0] && !w ? D : void 0),
|
|
114
|
+
labels: M,
|
|
115
|
+
disabled: N.disabled,
|
|
116
|
+
invalid: N.invalid,
|
|
117
|
+
onBrowse: J,
|
|
118
|
+
onRemove: () => r[0] ? a(r[0].id) : T?.(),
|
|
119
|
+
btnId: G,
|
|
120
|
+
labelledBy: K(G),
|
|
121
|
+
describedBy: U,
|
|
122
|
+
className: k
|
|
123
|
+
});
|
|
124
|
+
let Y = r.length > 0 && /* @__PURE__ */ s(y, {
|
|
125
|
+
items: r,
|
|
126
|
+
labels: M,
|
|
127
|
+
onRemove: a
|
|
128
|
+
});
|
|
129
|
+
return C === "button" ? /* @__PURE__ */ c("div", {
|
|
130
|
+
className: e("grid gap-3 font-sans", k),
|
|
131
|
+
children: [/* @__PURE__ */ c("div", {
|
|
132
|
+
className: "flex flex-wrap items-center gap-x-3 gap-y-1",
|
|
133
|
+
children: [
|
|
134
|
+
/* @__PURE__ */ s(n, {
|
|
135
|
+
type: "button",
|
|
136
|
+
id: G,
|
|
137
|
+
variant: "secondary",
|
|
138
|
+
iconStart: "upload",
|
|
139
|
+
disabled: N.disabled,
|
|
140
|
+
onClick: J,
|
|
141
|
+
"aria-labelledby": K(G),
|
|
142
|
+
"aria-describedby": U,
|
|
143
|
+
children: M.chooseFile
|
|
144
|
+
}),
|
|
145
|
+
q(),
|
|
146
|
+
W
|
|
147
|
+
]
|
|
148
|
+
}), Y]
|
|
149
|
+
}) : /* @__PURE__ */ c("div", {
|
|
150
|
+
className: e("grid gap-3 font-sans", k),
|
|
151
|
+
children: [/* @__PURE__ */ c("label", {
|
|
152
|
+
onDragOver: (e) => {
|
|
153
|
+
e.preventDefault(), N.disabled || L(!0);
|
|
154
|
+
},
|
|
155
|
+
onDragLeave: () => L(!1),
|
|
156
|
+
onDrop: z,
|
|
157
|
+
className: e("grid cursor-pointer justify-items-center gap-2 rounded-lg border-2 border-dashed px-6 py-8 text-center transition-colors", I ? "border-primary bg-info-surface/60" : "border-border hover:border-fg-muted/60 hover:bg-surface-muted/50", N.invalid && "border-danger-border", N.disabled && "cursor-not-allowed opacity-60", "has-[:focus-visible]:outline-3 has-[:focus-visible]:outline-offset-2 has-[:focus-visible]:outline-focus"),
|
|
158
|
+
children: [
|
|
159
|
+
/* @__PURE__ */ s(t, {
|
|
160
|
+
name: "upload",
|
|
161
|
+
size: 36,
|
|
162
|
+
className: "text-primary"
|
|
163
|
+
}),
|
|
164
|
+
/* @__PURE__ */ s("span", {
|
|
165
|
+
className: "text-[15px] font-semibold text-fg",
|
|
166
|
+
children: M.title
|
|
167
|
+
}),
|
|
168
|
+
q(),
|
|
169
|
+
W
|
|
170
|
+
]
|
|
171
|
+
}), Y]
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
function y({ items: e, labels: t, onRemove: n }) {
|
|
175
|
+
return /* @__PURE__ */ s("ul", {
|
|
176
|
+
className: "m-0 grid list-none gap-2 p-0",
|
|
177
|
+
children: e.map((e) => /* @__PURE__ */ c("li", {
|
|
178
|
+
className: "flex items-center gap-3 rounded-md border border-border bg-surface p-2.5 pr-1.5",
|
|
179
|
+
children: [
|
|
180
|
+
/* @__PURE__ */ s(_, { file: e.file }),
|
|
181
|
+
/* @__PURE__ */ c("div", {
|
|
182
|
+
className: "grid min-w-0 flex-1 gap-1",
|
|
183
|
+
children: [
|
|
184
|
+
/* @__PURE__ */ c("div", {
|
|
185
|
+
className: "flex items-baseline gap-2",
|
|
186
|
+
children: [/* @__PURE__ */ s("span", {
|
|
187
|
+
className: "min-w-0 flex-1 truncate text-sm font-medium",
|
|
188
|
+
children: e.file.name
|
|
189
|
+
}), /* @__PURE__ */ s("span", {
|
|
190
|
+
className: "shrink-0 text-xs text-fg-muted tabular-nums",
|
|
191
|
+
children: m(e.file.size)
|
|
192
|
+
})]
|
|
193
|
+
}),
|
|
194
|
+
e.status === "uploading" && /* @__PURE__ */ s(i, {
|
|
195
|
+
value: e.progress ?? null,
|
|
196
|
+
size: "sm",
|
|
197
|
+
"aria-label": `${t.uploading} ${e.file.name}`,
|
|
198
|
+
valueText: !1
|
|
199
|
+
}),
|
|
200
|
+
e.status === "error" && /* @__PURE__ */ s("span", {
|
|
201
|
+
className: "text-xs font-medium text-danger-fg",
|
|
202
|
+
children: e.error
|
|
203
|
+
}),
|
|
204
|
+
e.status === "done" && /* @__PURE__ */ c("span", {
|
|
205
|
+
className: "inline-flex items-center gap-1 text-xs font-medium text-success-fg",
|
|
206
|
+
children: [
|
|
207
|
+
/* @__PURE__ */ s(a, {
|
|
208
|
+
name: "check",
|
|
209
|
+
size: 12
|
|
210
|
+
}),
|
|
211
|
+
" ",
|
|
212
|
+
t.done
|
|
213
|
+
]
|
|
214
|
+
})
|
|
215
|
+
]
|
|
216
|
+
}),
|
|
217
|
+
/* @__PURE__ */ s("button", {
|
|
218
|
+
type: "button",
|
|
219
|
+
"aria-label": t.remove.replace("{name}", e.file.name),
|
|
220
|
+
onClick: () => n(e.id),
|
|
221
|
+
className: "grid size-9 shrink-0 cursor-pointer place-items-center rounded-full border-0 bg-transparent text-fg-muted hover:bg-surface-muted hover:text-fg focus-visible:outline-3 focus-visible:outline-focus",
|
|
222
|
+
children: /* @__PURE__ */ s(a, {
|
|
223
|
+
name: "close",
|
|
224
|
+
size: 16
|
|
225
|
+
})
|
|
226
|
+
})
|
|
227
|
+
]
|
|
228
|
+
}, e.id))
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
var b = {
|
|
232
|
+
square: "size-24 rounded-md",
|
|
233
|
+
wide: "aspect-video w-48 rounded-md",
|
|
234
|
+
avatar: "size-24 rounded-full"
|
|
235
|
+
};
|
|
236
|
+
function x({ item: a, src: o, shape: l, input: u, hint: d, labels: f, disabled: p, invalid: m, onBrowse: h, onRemove: _, btnId: v, labelledBy: y, describedBy: x, className: S }) {
|
|
237
|
+
let C = g(a?.file) ?? (a ? void 0 : o), w = !!(a || o);
|
|
238
|
+
return /* @__PURE__ */ c("div", {
|
|
239
|
+
className: e("flex flex-wrap items-center gap-4 font-sans", S),
|
|
240
|
+
children: [/* @__PURE__ */ c("div", {
|
|
241
|
+
className: "relative shrink-0",
|
|
242
|
+
children: [/* @__PURE__ */ s("div", {
|
|
243
|
+
className: e("grid place-items-center overflow-hidden border bg-surface-muted text-fg-muted", m ? "border-danger-border" : "border-border", b[l]),
|
|
244
|
+
children: C ? /* @__PURE__ */ s("img", {
|
|
245
|
+
src: C,
|
|
246
|
+
alt: f.imagePreview,
|
|
247
|
+
className: "size-full object-cover"
|
|
248
|
+
}) : /* @__PURE__ */ s(t, {
|
|
249
|
+
name: l === "avatar" ? "user" : "upload",
|
|
250
|
+
size: 32
|
|
251
|
+
})
|
|
252
|
+
}), l === "avatar" && /* @__PURE__ */ s(r, {
|
|
253
|
+
icon: "edit",
|
|
254
|
+
label: w ? f.changeImage : f.uploadImage,
|
|
255
|
+
variant: "primary",
|
|
256
|
+
size: "sm",
|
|
257
|
+
disabled: p,
|
|
258
|
+
onClick: h,
|
|
259
|
+
"aria-describedby": x,
|
|
260
|
+
className: "absolute -right-1 -bottom-1 ring-3 ring-bg"
|
|
261
|
+
})]
|
|
262
|
+
}), /* @__PURE__ */ c("div", {
|
|
263
|
+
className: "grid min-w-0 flex-1 gap-2",
|
|
264
|
+
children: [
|
|
265
|
+
/* @__PURE__ */ c("div", {
|
|
266
|
+
className: "flex flex-wrap gap-2",
|
|
267
|
+
children: [l !== "avatar" && /* @__PURE__ */ s(n, {
|
|
268
|
+
type: "button",
|
|
269
|
+
id: v,
|
|
270
|
+
variant: "secondary",
|
|
271
|
+
size: "sm",
|
|
272
|
+
iconStart: "upload",
|
|
273
|
+
disabled: p,
|
|
274
|
+
onClick: h,
|
|
275
|
+
"aria-labelledby": y,
|
|
276
|
+
"aria-describedby": x,
|
|
277
|
+
children: w ? f.changeImage : f.uploadImage
|
|
278
|
+
}), w && /* @__PURE__ */ s(n, {
|
|
279
|
+
type: "button",
|
|
280
|
+
variant: "ghost",
|
|
281
|
+
size: "sm",
|
|
282
|
+
iconStart: "trash",
|
|
283
|
+
disabled: p,
|
|
284
|
+
onClick: _,
|
|
285
|
+
children: f.removeImage
|
|
286
|
+
})]
|
|
287
|
+
}),
|
|
288
|
+
a?.status === "uploading" && /* @__PURE__ */ s(i, {
|
|
289
|
+
value: a.progress ?? null,
|
|
290
|
+
size: "sm",
|
|
291
|
+
"aria-label": `${f.uploading} ${a.file.name}`,
|
|
292
|
+
valueText: !1
|
|
293
|
+
}),
|
|
294
|
+
a?.status === "error" && /* @__PURE__ */ s("span", {
|
|
295
|
+
className: "text-xs font-medium text-danger-fg",
|
|
296
|
+
children: a.error
|
|
297
|
+
}),
|
|
298
|
+
d,
|
|
299
|
+
u
|
|
300
|
+
]
|
|
301
|
+
})]
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
//#endregion
|
|
305
|
+
export { v as FileUpload, m as formatBytes };
|
|
306
|
+
|
|
307
|
+
//# sourceMappingURL=file-upload.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-upload.js","names":["LABELS","formatBytes","bytes","units","v","i","matchesAccept","accept","name","file","rule","useObjectUrl","url","setUrl","useState","useEffect","u","Preview","FileUpload","multipleProp","overrides","rest","multiple","variant","labels","field","useFieldProps","inputRef","useRef","hintId","useId","dragging","setDragging","add","list","files","ok","rejected","room","maxFiles","items","maxSize","onRemove","onAdd","onReject","onDrop","types","t","size","constraintText","describedBy","input","btnId","labelledBy","own","hint","extra","browse","ImageUpload","imageShape","fallbackText","src","onClear","FileList","cx","className","Button","Icon","item","Progress","Glyph","SHAPES","has","invalid","shape","IconButton","onBrowse"],"sources":["../../src/file-upload/file-upload.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect, useId, useRef, useState, type DragEvent } from 'react';\nimport { cx } from '../lib/cx';\nimport { Glyph } from '../lib/glyphs';\nimport { Icon } from '../icon/icon';\nimport { Button, IconButton } from '../button/button';\nimport { Progress } from '../progress/progress';\nimport { useFieldProps } from '../input/input';\n\nexport type UploadStatus = 'queued' | 'uploading' | 'done' | 'error';\n\nexport interface UploadItem {\n id: string;\n file: File;\n status: UploadStatus;\n /** 0–100 while uploading. */\n progress?: number;\n error?: string;\n}\n\nexport interface FileUploadProps {\n /** Files already added (controlled). Upload them yourself and update `status`/`progress`. */\n items: UploadItem[];\n /** New valid files (already checked against `accept`, `maxSize`, `maxFiles`). */\n onAdd: (files: File[]) => void;\n onRemove: (id: string) => void;\n /** Files rejected by validation, with the reason. */\n onReject?: (rejections: { file: File; reason: string }[]) => void;\n /** Same syntax as `<input accept>`: \".pdf,.csv,image/*\". */\n accept?: string;\n /** Bytes. */\n maxSize?: number;\n maxFiles?: number;\n multiple?: boolean;\n disabled?: boolean;\n id?: string;\n /**\n * `dropzone` (default): drag-and-drop area plus a file list. `button`: one \"Choose file\" button and a hint\n * (forms with little room). `image`: a single image with a preview, \"Change\" and \"Remove\" (logos, banners, photos).\n */\n variant?: FileUploadVariant;\n /** `image` only: the image already saved, shown until a new file is added. */\n src?: string;\n /** `image` only: removes the saved `src` (the new file, if any, is removed with `onRemove`). */\n onClear?: () => void;\n /** `image` only: `square` (logo), `wide` (16:9 banner) or `avatar` (round, with an edit button on the photo). */\n imageShape?: 'square' | 'wide' | 'avatar';\n /** `image` only: what happens without an image (\"Without a logo, the project uses its client's logo\"). */\n fallbackText?: string;\n labels?: Partial<typeof LABELS>;\n className?: string;\n}\n\nexport type FileUploadVariant = 'dropzone' | 'button' | 'image';\n\nconst LABELS = {\n title: 'Drag files here or browse',\n browse: 'browse',\n /** `{types}` and `{size}` are replaced. */\n constraints: '{types} up to {size}',\n remove: 'Remove {name}',\n tooBig: 'Larger than {size}',\n wrongType: 'File type not allowed',\n tooMany: 'Only {max} files allowed',\n uploading: 'Uploading',\n done: 'Uploaded',\n chooseFile: 'Choose file',\n uploadImage: 'Upload image',\n changeImage: 'Change image',\n removeImage: 'Remove',\n imagePreview: 'Current image',\n images: 'Images',\n};\n\nexport function formatBytes(bytes: number) {\n if (bytes < 1024) return `${bytes} B`;\n const units = ['KB', 'MB', 'GB'];\n let v = bytes / 1024;\n let i = 0;\n while (v >= 1024 && i < units.length - 1) {\n v /= 1024;\n i++;\n }\n return `${v < 10 ? v.toFixed(1) : Math.round(v)} ${units[i]}`;\n}\n\nfunction matchesAccept(file: File, accept?: string) {\n if (!accept) return true;\n const name = file.name.toLowerCase();\n return accept.split(',').some((rule) => {\n const r = rule.trim().toLowerCase();\n if (r.startsWith('.')) return name.endsWith(r);\n if (r.endsWith('/*')) return file.type.startsWith(r.slice(0, -1));\n return file.type === r;\n });\n}\n\nfunction useObjectUrl(file?: File) {\n const [url, setUrl] = useState<string>();\n useEffect(() => {\n if (!file || !file.type.startsWith('image/')) return setUrl(undefined);\n const u = URL.createObjectURL(file);\n setUrl(u);\n return () => URL.revokeObjectURL(u);\n }, [file]);\n return url;\n}\n\nfunction Preview({ file }: { file: File }) {\n const url = useObjectUrl(file);\n return url ? (\n <img src={url} alt=\"\" className=\"size-10 shrink-0 rounded-sm object-cover\" />\n ) : (\n <span className=\"grid size-10 shrink-0 place-items-center rounded-sm bg-surface-muted font-mono text-[10px] font-bold text-fg-muted uppercase\">\n {file.name.split('.').pop()?.slice(0, 4)}\n </span>\n );\n}\n\n/**\n * Drag-and-drop (or tap to browse) file input with validation, image previews and per-file\n * progress. The component validates and lists; your app uploads and reports progress.\n */\nexport function FileUpload({\n items,\n onAdd,\n onRemove,\n onReject,\n accept,\n maxSize,\n maxFiles,\n multiple: multipleProp = true,\n disabled,\n variant = 'dropzone',\n src,\n onClear,\n imageShape = 'square',\n fallbackText,\n labels: overrides,\n className,\n ...rest\n}: FileUploadProps) {\n const multiple = variant === 'image' ? false : multipleProp;\n const labels = { ...LABELS, ...overrides };\n const field = useFieldProps({ ...rest, disabled });\n const inputRef = useRef<HTMLInputElement>(null);\n const hintId = useId();\n const [dragging, setDragging] = useState(false);\n\n const add = (list: FileList | null) => {\n if (!list || field.disabled) return;\n const files = Array.from(list);\n const ok: File[] = [];\n const rejected: { file: File; reason: string }[] = [];\n // An image upload holds one file: a new one replaces the current one.\n let room = variant === 'image' ? 1 : maxFiles !== undefined ? maxFiles - items.length : Infinity;\n for (const file of files) {\n if (!matchesAccept(file, accept)) rejected.push({ file, reason: labels.wrongType });\n else if (maxSize !== undefined && file.size > maxSize)\n rejected.push({ file, reason: labels.tooBig.replace('{size}', formatBytes(maxSize)) });\n else if (room <= 0)\n rejected.push({ file, reason: labels.tooMany.replace('{max}', String(variant === 'image' ? 1 : maxFiles)) });\n else {\n ok.push(file);\n room--;\n }\n }\n if (ok.length) {\n if (variant === 'image') items.forEach((i) => onRemove(i.id));\n onAdd(multiple ? ok : ok.slice(0, 1));\n }\n if (rejected.length) onReject?.(rejected);\n if (inputRef.current) inputRef.current.value = '';\n };\n\n const onDrop = (e: DragEvent) => {\n e.preventDefault();\n setDragging(false);\n add(e.dataTransfer.files);\n };\n\n // \".pdf\" → PDF, \"image/png\" → PNG, \"image/svg+xml\" → SVG, \"image/*\" → Images.\n const types = accept\n ?.split(',')\n .map((t) => {\n const v = t.trim().toLowerCase();\n if (v === 'image/*') return labels.images;\n if (v.endsWith('/*')) return v.slice(0, -2).toUpperCase();\n return (v.includes('/') ? v.split('/')[1]!.replace('+xml', '') : v.replace(/^\\./, '')).toUpperCase();\n })\n .join(', ');\n const size = maxSize ? formatBytes(maxSize) : undefined;\n const constraintText =\n types && size ? labels.constraints.replace('{types}', types).replace('{size}', size) : (types ?? size);\n\n const describedBy =\n [field['aria-describedby'], constraintText ? hintId : undefined].filter(Boolean).join(' ') || undefined;\n const input = (\n <input\n ref={inputRef}\n type=\"file\"\n id={field.id}\n accept={accept}\n multiple={multiple}\n disabled={field.disabled}\n aria-invalid={field['aria-invalid']}\n aria-describedby={describedBy}\n onChange={(e) => add(e.target.files)}\n // In `button` and `image` the visible buttons are the tab stops; the input stays for the label click and forms.\n tabIndex={variant === 'dropzone' ? undefined : -1}\n className=\"sr-only\"\n />\n );\n const btnId = useId();\n // The visible button's name: the field label (FormField renders `${id}-label`) plus the button's own text.\n const labelledBy = (own: string) => (field.id ? `${field.id}-label ${own}` : undefined);\n const hint = (extra?: string) =>\n constraintText || extra ? (\n <span id={hintId} className=\"text-sm text-fg-muted\">\n {[extra, constraintText].filter(Boolean).join(' · ')}\n </span>\n ) : null;\n const browse = () => inputRef.current?.click();\n\n if (variant === 'image') {\n return (\n <ImageUpload\n item={items[0]}\n src={src}\n shape={imageShape}\n input={input}\n hint={hint(fallbackText && !items[0] && !src ? fallbackText : undefined)}\n labels={labels}\n disabled={field.disabled}\n invalid={field.invalid}\n onBrowse={browse}\n onRemove={() => (items[0] ? onRemove(items[0].id) : onClear?.())}\n btnId={btnId}\n labelledBy={labelledBy(btnId)}\n describedBy={describedBy}\n className={className}\n />\n );\n }\n\n const list = items.length > 0 && <FileList items={items} labels={labels} onRemove={onRemove} />;\n\n if (variant === 'button') {\n return (\n <div className={cx('grid gap-3 font-sans', className)}>\n <div className=\"flex flex-wrap items-center gap-x-3 gap-y-1\">\n {/* The visible button opens the input; the input keeps the field's label, hint and error. */}\n <Button\n type=\"button\"\n id={btnId}\n variant=\"secondary\"\n iconStart=\"upload\"\n disabled={field.disabled}\n onClick={browse}\n aria-labelledby={labelledBy(btnId)}\n aria-describedby={describedBy}\n >\n {labels.chooseFile}\n </Button>\n {hint()}\n {input}\n </div>\n {list}\n </div>\n );\n }\n\n return (\n <div className={cx('grid gap-3 font-sans', className)}>\n <label\n onDragOver={(e) => {\n e.preventDefault();\n if (!field.disabled) setDragging(true);\n }}\n onDragLeave={() => setDragging(false)}\n onDrop={onDrop}\n className={cx(\n 'grid cursor-pointer justify-items-center gap-2 rounded-lg border-2 border-dashed px-6 py-8 text-center transition-colors',\n dragging\n ? 'border-primary bg-info-surface/60'\n : 'border-border hover:border-fg-muted/60 hover:bg-surface-muted/50',\n field.invalid && 'border-danger-border',\n field.disabled && 'cursor-not-allowed opacity-60',\n 'has-[:focus-visible]:outline-3 has-[:focus-visible]:outline-offset-2 has-[:focus-visible]:outline-focus',\n )}\n >\n <Icon name=\"upload\" size={36} className=\"text-primary\" />\n <span className=\"text-[15px] font-semibold text-fg\">{labels.title}</span>\n {hint()}\n {input}\n </label>\n {list}\n </div>\n );\n}\n\ntype Labels = typeof LABELS;\n\nfunction FileList({ items, labels, onRemove }: { items: UploadItem[]; labels: Labels; onRemove: (id: string) => void }) {\n return (\n <ul className=\"m-0 grid list-none gap-2 p-0\">\n {items.map((item) => (\n <li\n key={item.id}\n className=\"flex items-center gap-3 rounded-md border border-border bg-surface p-2.5 pr-1.5\"\n >\n <Preview file={item.file} />\n <div className=\"grid min-w-0 flex-1 gap-1\">\n <div className=\"flex items-baseline gap-2\">\n <span className=\"min-w-0 flex-1 truncate text-sm font-medium\">{item.file.name}</span>\n <span className=\"shrink-0 text-xs text-fg-muted tabular-nums\">{formatBytes(item.file.size)}</span>\n </div>\n {item.status === 'uploading' && (\n <Progress\n value={item.progress ?? null}\n size=\"sm\"\n aria-label={`${labels.uploading} ${item.file.name}`}\n valueText={false}\n />\n )}\n {item.status === 'error' && <span className=\"text-xs font-medium text-danger-fg\">{item.error}</span>}\n {item.status === 'done' && (\n <span className=\"inline-flex items-center gap-1 text-xs font-medium text-success-fg\">\n <Glyph name=\"check\" size={12} /> {labels.done}\n </span>\n )}\n </div>\n <button\n type=\"button\"\n aria-label={labels.remove.replace('{name}', item.file.name)}\n onClick={() => onRemove(item.id)}\n className=\"grid size-9 shrink-0 cursor-pointer place-items-center rounded-full border-0 bg-transparent text-fg-muted hover:bg-surface-muted hover:text-fg focus-visible:outline-3 focus-visible:outline-focus\"\n >\n <Glyph name=\"close\" size={16} />\n </button>\n </li>\n ))}\n </ul>\n );\n}\n\nconst SHAPES = {\n square: 'size-24 rounded-md',\n wide: 'aspect-video w-48 rounded-md',\n avatar: 'size-24 rounded-full',\n};\n\nfunction ImageUpload({\n item,\n src,\n shape,\n input,\n hint,\n labels,\n disabled,\n invalid,\n onBrowse,\n onRemove,\n btnId,\n labelledBy,\n describedBy,\n className,\n}: {\n btnId: string;\n labelledBy?: string;\n describedBy?: string;\n item?: UploadItem;\n src?: string;\n shape: 'square' | 'wide' | 'avatar';\n input: React.ReactNode;\n hint: React.ReactNode;\n labels: Labels;\n disabled?: boolean;\n invalid?: boolean;\n onBrowse: () => void;\n onRemove: () => void;\n className?: string;\n}) {\n const fileUrl = useObjectUrl(item?.file);\n const url = fileUrl ?? (item ? undefined : src);\n const has = Boolean(item || src);\n return (\n <div className={cx('flex flex-wrap items-center gap-4 font-sans', className)}>\n <div className=\"relative shrink-0\">\n <div\n className={cx(\n 'grid place-items-center overflow-hidden border bg-surface-muted text-fg-muted',\n invalid ? 'border-danger-border' : 'border-border',\n SHAPES[shape],\n )}\n >\n {url ? (\n <img src={url} alt={labels.imagePreview} className=\"size-full object-cover\" />\n ) : (\n <Icon name={shape === 'avatar' ? 'user' : 'upload'} size={32} />\n )}\n </div>\n {shape === 'avatar' && (\n <IconButton\n icon=\"edit\"\n label={has ? labels.changeImage : labels.uploadImage}\n variant=\"primary\"\n size=\"sm\"\n disabled={disabled}\n onClick={onBrowse}\n aria-describedby={describedBy}\n className=\"absolute -right-1 -bottom-1 ring-3 ring-bg\"\n />\n )}\n </div>\n <div className=\"grid min-w-0 flex-1 gap-2\">\n <div className=\"flex flex-wrap gap-2\">\n {shape !== 'avatar' && (\n <Button\n type=\"button\"\n id={btnId}\n variant=\"secondary\"\n size=\"sm\"\n iconStart=\"upload\"\n disabled={disabled}\n onClick={onBrowse}\n aria-labelledby={labelledBy}\n aria-describedby={describedBy}\n >\n {has ? labels.changeImage : labels.uploadImage}\n </Button>\n )}\n {has && (\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" iconStart=\"trash\" disabled={disabled} onClick={onRemove}>\n {labels.removeImage}\n </Button>\n )}\n </div>\n {item?.status === 'uploading' && (\n <Progress value={item.progress ?? null} size=\"sm\" aria-label={`${labels.uploading} ${item.file.name}`} valueText={false} />\n )}\n {item?.status === 'error' && <span className=\"text-xs font-medium text-danger-fg\">{item.error}</span>}\n {hint}\n {input}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
export interface FilterOption {
|
|
3
|
+
value: string;
|
|
4
|
+
label: string;
|
|
5
|
+
/** Number of matching records, shown on the right. */
|
|
6
|
+
count?: number;
|
|
7
|
+
/** A color dot before the label (status or category), any CSS color. */
|
|
8
|
+
color?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface FilterMenuProps {
|
|
11
|
+
/** The facet's name, shown on the button ("Status"). */
|
|
12
|
+
label: string;
|
|
13
|
+
options: FilterOption[];
|
|
14
|
+
/** Selected values (controlled). Empty means "all". */
|
|
15
|
+
value: string[];
|
|
16
|
+
onValueChange: (value: string[]) => void;
|
|
17
|
+
labels?: {
|
|
18
|
+
clear?: string;
|
|
19
|
+
};
|
|
20
|
+
className?: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* A multi-select facet for table toolbars: an outline button ("Status ▾") that opens a checklist and shows how
|
|
24
|
+
* many values are active ("Status · 2"). The menu stays open while ticking options.
|
|
25
|
+
*/
|
|
26
|
+
export declare function FilterMenu({ label, options, value, onValueChange, labels, className }: FilterMenuProps): import("react").JSX.Element;
|
|
27
|
+
export interface FilterBarProps {
|
|
28
|
+
/** Period presets on top, usually a `SegmentedControl`. */
|
|
29
|
+
period?: ReactNode;
|
|
30
|
+
/** Labelled fields (`FormField` with a `DateRangePicker`, `Select`, `FilterMenu`…), laid out in a grid. */
|
|
31
|
+
children: ReactNode;
|
|
32
|
+
/** Shows "Reset" when set. */
|
|
33
|
+
onReset?: () => void;
|
|
34
|
+
/** Number of active filters: on phones the fields fold behind a "Filters (n)" button. */
|
|
35
|
+
activeCount?: number;
|
|
36
|
+
/** Grid columns from 1024px (default 4). */
|
|
37
|
+
columns?: 2 | 3 | 4;
|
|
38
|
+
labels?: {
|
|
39
|
+
reset?: string;
|
|
40
|
+
filters?: string;
|
|
41
|
+
};
|
|
42
|
+
className?: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* One card for a report's filters: period presets, labelled fields in a grid and "Reset". Below 640px the fields
|
|
46
|
+
* fold behind a "Filters" button so the results stay in view.
|
|
47
|
+
*/
|
|
48
|
+
export declare function FilterBar({ period, children, onReset, activeCount, columns, labels, className, }: FilterBarProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cx as e } from "../lib/cx.js";
|
|
3
|
+
import { Button as t } from "../button/button.js";
|
|
4
|
+
import { Glyph as n } from "../lib/glyphs.js";
|
|
5
|
+
import { DropdownMenu as r, DropdownMenuCheckboxItem as i, DropdownMenuContent as a, DropdownMenuItem as o, DropdownMenuSeparator as s, DropdownMenuTrigger as c } from "../menu/dropdown-menu.js";
|
|
6
|
+
import { Fragment as l, jsx as u, jsxs as d } from "react/jsx-runtime";
|
|
7
|
+
import { useId as f, useState as p } from "react";
|
|
8
|
+
//#region src/filters/filters.tsx
|
|
9
|
+
function m({ label: t, options: f, value: p, onValueChange: m, labels: h = {}, className: g }) {
|
|
10
|
+
let _ = p.length, v = (e, t) => m(t ? [...p, e] : p.filter((t) => t !== e));
|
|
11
|
+
return /* @__PURE__ */ d(r, { children: [/* @__PURE__ */ u(c, {
|
|
12
|
+
asChild: !0,
|
|
13
|
+
children: /* @__PURE__ */ d("button", {
|
|
14
|
+
type: "button",
|
|
15
|
+
className: e("inline-flex h-9 cursor-pointer items-center gap-1.5 rounded-full border px-3.5 font-sans text-sm font-semibold transition-colors pointer-coarse:h-11", "focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-focus", _ ? "border-primary bg-info-surface text-info-fg" : "border-border bg-surface text-fg hover:border-fg-muted/50", g),
|
|
16
|
+
children: [
|
|
17
|
+
t,
|
|
18
|
+
_ > 0 && /* @__PURE__ */ u("span", {
|
|
19
|
+
className: "grid h-5 min-w-5 place-items-center rounded-full bg-primary px-1.5 text-xs text-on-primary tabular-nums",
|
|
20
|
+
children: _
|
|
21
|
+
}),
|
|
22
|
+
/* @__PURE__ */ u(n, {
|
|
23
|
+
name: "chevron-down",
|
|
24
|
+
size: 14
|
|
25
|
+
})
|
|
26
|
+
]
|
|
27
|
+
})
|
|
28
|
+
}), /* @__PURE__ */ d(a, {
|
|
29
|
+
className: "w-64",
|
|
30
|
+
children: [f.map((e) => /* @__PURE__ */ u(i, {
|
|
31
|
+
checked: p.includes(e.value),
|
|
32
|
+
onCheckedChange: (t) => v(e.value, t === !0),
|
|
33
|
+
onSelect: (e) => e.preventDefault(),
|
|
34
|
+
children: /* @__PURE__ */ d("span", {
|
|
35
|
+
className: "flex min-w-0 flex-1 items-center gap-2",
|
|
36
|
+
children: [
|
|
37
|
+
e.color && /* @__PURE__ */ u("span", {
|
|
38
|
+
"aria-hidden": "true",
|
|
39
|
+
className: "size-2.5 shrink-0 rounded-full",
|
|
40
|
+
style: { background: e.color }
|
|
41
|
+
}),
|
|
42
|
+
/* @__PURE__ */ u("span", {
|
|
43
|
+
className: "truncate",
|
|
44
|
+
children: e.label
|
|
45
|
+
}),
|
|
46
|
+
e.count !== void 0 && /* @__PURE__ */ u("span", {
|
|
47
|
+
className: "ml-auto pl-3 text-xs text-fg-muted tabular-nums",
|
|
48
|
+
children: e.count
|
|
49
|
+
})
|
|
50
|
+
]
|
|
51
|
+
})
|
|
52
|
+
}, e.value)), _ > 0 && /* @__PURE__ */ d(l, { children: [/* @__PURE__ */ u(s, {}), /* @__PURE__ */ u(o, {
|
|
53
|
+
onSelect: () => m([]),
|
|
54
|
+
children: h.clear ?? "Clear filter"
|
|
55
|
+
})] })]
|
|
56
|
+
})] });
|
|
57
|
+
}
|
|
58
|
+
var h = {
|
|
59
|
+
2: "lg:grid-cols-2",
|
|
60
|
+
3: "lg:grid-cols-3",
|
|
61
|
+
4: "lg:grid-cols-4"
|
|
62
|
+
};
|
|
63
|
+
function g({ period: n, children: r, onReset: i, activeCount: a = 0, columns: o = 4, labels: s = {}, className: c }) {
|
|
64
|
+
let [l, m] = p(!1), g = f(), _ = s.filters ?? "Filters";
|
|
65
|
+
return /* @__PURE__ */ d("section", {
|
|
66
|
+
"aria-label": _,
|
|
67
|
+
className: e("grid grid-cols-[minmax(0,1fr)] gap-4 rounded-lg border border-border bg-surface p-card font-sans", c),
|
|
68
|
+
children: [/* @__PURE__ */ d("div", {
|
|
69
|
+
className: "flex flex-wrap items-center justify-between gap-3",
|
|
70
|
+
children: [n ?? /* @__PURE__ */ u("span", {}), /* @__PURE__ */ d("div", {
|
|
71
|
+
className: "flex items-center gap-2",
|
|
72
|
+
children: [/* @__PURE__ */ u("span", {
|
|
73
|
+
className: "sm:hidden",
|
|
74
|
+
children: /* @__PURE__ */ u(t, {
|
|
75
|
+
type: "button",
|
|
76
|
+
size: "sm",
|
|
77
|
+
variant: "secondary",
|
|
78
|
+
iconStart: "filter",
|
|
79
|
+
"aria-expanded": l,
|
|
80
|
+
"aria-controls": g,
|
|
81
|
+
onClick: () => m((e) => !e),
|
|
82
|
+
children: a > 0 ? `${_} (${a})` : _
|
|
83
|
+
})
|
|
84
|
+
}), i && /* @__PURE__ */ u(t, {
|
|
85
|
+
type: "button",
|
|
86
|
+
size: "sm",
|
|
87
|
+
variant: "ghost",
|
|
88
|
+
iconStart: "sync",
|
|
89
|
+
onClick: i,
|
|
90
|
+
children: s.reset ?? "Reset"
|
|
91
|
+
})]
|
|
92
|
+
})]
|
|
93
|
+
}), /* @__PURE__ */ u("div", {
|
|
94
|
+
id: g,
|
|
95
|
+
className: e("grid-cols-[minmax(0,1fr)] gap-4 sm:grid sm:grid-cols-2", h[o], l ? "grid" : "hidden"),
|
|
96
|
+
children: r
|
|
97
|
+
})]
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
//#endregion
|
|
101
|
+
export { g as FilterBar, m as FilterMenu };
|
|
102
|
+
|
|
103
|
+
//# sourceMappingURL=filters.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filters.js","names":["_Fragment","FilterMenu","active","value","toggle","onValueChange","on","v","x","DropdownMenu","DropdownMenuTrigger","cx","className","label","Glyph","DropdownMenuContent","options","DropdownMenuCheckboxItem","o","Fragment","DropdownMenuSeparator","DropdownMenuItem","labels","COLS","FilterBar","open","setOpen","useState","fieldsId","useId","filtersLabel","period","Button","activeCount","onReset","columns"],"sources":["../../src/filters/filters.tsx"],"sourcesContent":["'use client';\n\nimport { useId, useState, type ReactNode } from 'react';\nimport { cx } from '../lib/cx';\nimport { Glyph } from '../lib/glyphs';\nimport { Button } from '../button/button';\nimport {\n DropdownMenu,\n DropdownMenuCheckboxItem,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from '../menu/dropdown-menu';\n\nexport interface FilterOption {\n value: string;\n label: string;\n /** Number of matching records, shown on the right. */\n count?: number;\n /** A color dot before the label (status or category), any CSS color. */\n color?: string;\n}\n\nexport interface FilterMenuProps {\n /** The facet's name, shown on the button (\"Status\"). */\n label: string;\n options: FilterOption[];\n /** Selected values (controlled). Empty means \"all\". */\n value: string[];\n onValueChange: (value: string[]) => void;\n labels?: { clear?: string };\n className?: string;\n}\n\n/**\n * A multi-select facet for table toolbars: an outline button (\"Status ▾\") that opens a checklist and shows how\n * many values are active (\"Status · 2\"). The menu stays open while ticking options.\n */\nexport function FilterMenu({ label, options, value, onValueChange, labels = {}, className }: FilterMenuProps) {\n const active = value.length;\n const toggle = (v: string, on: boolean) => onValueChange(on ? [...value, v] : value.filter((x) => x !== v));\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <button\n type=\"button\"\n className={cx(\n 'inline-flex h-9 cursor-pointer items-center gap-1.5 rounded-full border px-3.5 font-sans text-sm font-semibold transition-colors pointer-coarse:h-11',\n 'focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-focus',\n active\n ? 'border-primary bg-info-surface text-info-fg'\n : 'border-border bg-surface text-fg hover:border-fg-muted/50',\n className,\n )}\n >\n {label}\n {active > 0 && (\n <span className=\"grid h-5 min-w-5 place-items-center rounded-full bg-primary px-1.5 text-xs text-on-primary tabular-nums\">\n {active}\n </span>\n )}\n <Glyph name=\"chevron-down\" size={14} />\n </button>\n </DropdownMenuTrigger>\n {/* A fixed width (not a min-width, which would conflict with the menu's own min-width). */}\n <DropdownMenuContent className=\"w-64\">\n {options.map((o) => (\n <DropdownMenuCheckboxItem\n key={o.value}\n checked={value.includes(o.value)}\n onCheckedChange={(on) => toggle(o.value, on === true)}\n // Keep the menu open to tick several options.\n onSelect={(e) => e.preventDefault()}\n >\n <span className=\"flex min-w-0 flex-1 items-center gap-2\">\n {o.color && (\n <span aria-hidden=\"true\" className=\"size-2.5 shrink-0 rounded-full\" style={{ background: o.color }} />\n )}\n <span className=\"truncate\">{o.label}</span>\n {o.count !== undefined && (\n <span className=\"ml-auto pl-3 text-xs text-fg-muted tabular-nums\">{o.count}</span>\n )}\n </span>\n </DropdownMenuCheckboxItem>\n ))}\n {active > 0 && (\n <>\n <DropdownMenuSeparator />\n <DropdownMenuItem onSelect={() => onValueChange([])}>{labels.clear ?? 'Clear filter'}</DropdownMenuItem>\n </>\n )}\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n\nexport interface FilterBarProps {\n /** Period presets on top, usually a `SegmentedControl`. */\n period?: ReactNode;\n /** Labelled fields (`FormField` with a `DateRangePicker`, `Select`, `FilterMenu`…), laid out in a grid. */\n children: ReactNode;\n /** Shows \"Reset\" when set. */\n onReset?: () => void;\n /** Number of active filters: on phones the fields fold behind a \"Filters (n)\" button. */\n activeCount?: number;\n /** Grid columns from 1024px (default 4). */\n columns?: 2 | 3 | 4;\n labels?: { reset?: string; filters?: string };\n className?: string;\n}\n\nconst COLS = { 2: 'lg:grid-cols-2', 3: 'lg:grid-cols-3', 4: 'lg:grid-cols-4' };\n\n/**\n * One card for a report's filters: period presets, labelled fields in a grid and \"Reset\". Below 640px the fields\n * fold behind a \"Filters\" button so the results stay in view.\n */\nexport function FilterBar({\n period,\n children,\n onReset,\n activeCount = 0,\n columns = 4,\n labels = {},\n className,\n}: FilterBarProps) {\n const [open, setOpen] = useState(false);\n const fieldsId = useId();\n const filtersLabel = labels.filters ?? 'Filters';\n return (\n <section\n aria-label={filtersLabel}\n // minmax(0, 1fr): the card never grows past its container, wide children scroll or wrap inside it.\n className={cx(\n 'grid grid-cols-[minmax(0,1fr)] gap-4 rounded-lg border border-border bg-surface p-card font-sans',\n className,\n )}\n >\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n {period ?? <span />}\n <div className=\"flex items-center gap-2\">\n {/* A wrapper hides it from 640px: Button sets its own display. */}\n <span className=\"sm:hidden\">\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"secondary\"\n iconStart=\"filter\"\n aria-expanded={open}\n aria-controls={fieldsId}\n onClick={() => setOpen((o) => !o)}\n >\n {activeCount > 0 ? `${filtersLabel} (${activeCount})` : filtersLabel}\n </Button>\n </span>\n {onReset && (\n <Button type=\"button\" size=\"sm\" variant=\"ghost\" iconStart=\"sync\" onClick={onReset}>\n {labels.reset ?? 'Reset'}\n </Button>\n )}\n </div>\n </div>\n <div\n id={fieldsId}\n className={cx(\n 'grid-cols-[minmax(0,1fr)] gap-4 sm:grid sm:grid-cols-2',\n COLS[columns],\n open ? 'grid' : 'hidden',\n )}\n >\n {children}\n </div>\n </section>\n );\n}\n"],"mappings":";;;;;;;;AAuCA,SAAgBC,EAAW,EAAE,UAAO,YAAS,UAAO,kBAAe,YAAS,CAAC,GAAG,gBAA8B;CAC5G,IAAMC,IAASC,EAAM,QACfC,KAAU,GAAW,MAAgBC,EAAcC,IAAK,CAAC,GAAGH,GAAOI,CAAC,IAAIJ,EAAM,QAAQ,MAAMK,MAAMD,CAAC,CAAC;CAC1G,OACE,kBAACE,GAAD,EAAA,UAAA,CACE,kBAACC,GAAD;EAAqB,SAAA;EACnB,UAAA,kBAAC,UAAD;GACE,MAAK;GACL,WAAWC,EACT,wJACA,sFACAT,IACI,gDACA,6DACJU,CACF;GATF,UAAA;IAWGC;IACAX,IAAS,KACR,kBAAC,QAAD;KAAM,WAAU;KACb,UAAAA;IACG,CAAA;IAER,kBAACY,GAAD;KAAO,MAAK;KAAe,MAAM;IAAK,CAAA;GAChC;;CACW,CAAA,GAErB,kBAACC,GAAD;EAAqB,WAAU;EAA/B,UAAA,CACGC,EAAQ,KAAK,MACZ,kBAACC,GAAD;GAEE,SAASd,EAAM,SAASe,EAAE,KAAK;GAC/B,kBAAkB,MAAOd,EAAOc,EAAE,OAAOZ,MAAO,EAAI;GAEpD,WAAW,MAAM,EAAE,eAAe;GAElC,UAAA,kBAAC,QAAD;IAAM,WAAU;IAAhB,UAAA;KACGY,EAAE,SACD,kBAAC,QAAD;MAAM,eAAY;MAAO,WAAU;MAAiC,OAAO,EAAE,YAAYA,EAAE,MAAM;KAAI,CAAA;KAEvG,kBAAC,QAAD;MAAM,WAAU;MAAY,UAAAA,EAAE;KAAY,CAAA;KACzCA,EAAE,UAAU,KAAA,KACX,kBAAC,QAAD;MAAM,WAAU;MAAmD,UAAAA,EAAE;KAAY,CAAA;IAE/E;;EACkB,GAfnBA,EAAE,KAeiB,CAC3B,GACAhB,IAAS,KACR,kBAAAF,GAAA,EAAA,UAAA,CACE,kBAACoB,GAAD,CAAwB,CAAA,GACxB,kBAACC,GAAD;GAAkB,gBAAgBhB,EAAc,CAAC,CAAC;GAAI,UAAAiB,EAAO,SAAS;EAAiC,CAAA,CACvG,EAAA,CAAA,CAEe;CACT,CAAA,CAAA,EAAA,CAAA;AAElB;AAiBA,IAAMC,IAAO;CAAE,GAAG;CAAkB,GAAG;CAAkB,GAAG;AAAiB;AAM7E,SAAgBC,EAAU,EACxB,WACA,aACA,YACA,iBAAc,GACd,aAAU,GACV,YAAS,CAAC,GACV,gBACiB;CACjB,IAAM,CAACC,GAAMC,KAAWC,EAAS,EAAK,GAChCC,IAAWC,EAAM,GACjBC,IAAeR,EAAO,WAAW;CACvC,OACE,kBAAC,WAAD;EACE,cAAYQ;EAEZ,WAAWnB,EACT,oGACAC,CACF;EANF,UAAA,CAQE,kBAAC,OAAD;GAAK,WAAU;GAAf,UAAA,CACGmB,KAAU,kBAAC,QAAD,CAAO,CAAA,GAClB,kBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CAEE,kBAAC,QAAD;KAAM,WAAU;KACd,UAAA,kBAACC,GAAD;MACE,MAAK;MACL,MAAK;MACL,SAAQ;MACR,WAAU;MACV,iBAAeP;MACf,iBAAeG;MACf,eAAeF,GAAS,MAAM,CAACR,CAAC;MAE/B,UAAAe,IAAc,IAAI,GAAGH,EAAa,IAAIG,EAAY,KAAKH;KAClD,CAAA;IACJ,CAAA,GACLI,KACC,kBAACF,GAAD;KAAQ,MAAK;KAAS,MAAK;KAAK,SAAQ;KAAQ,WAAU;KAAO,SAASE;KACvE,UAAAZ,EAAO,SAAS;IACX,CAAA,CAEP;GACF,CAAA,CAAA;EACL,CAAA,GAAA,kBAAC,OAAD;GACE,IAAIM;GACJ,WAAWjB,EACT,0DACAY,EAAKY,IACLV,IAAO,SAAS,QAClB;GAEC;EACE,CAAA,CACE;;AAEb"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type HTMLAttributes, type ReactNode } from 'react';
|
|
2
|
+
export interface FormFieldState {
|
|
3
|
+
id: string;
|
|
4
|
+
/** ids for `aria-describedby` (hint and error). */
|
|
5
|
+
describedBy?: string;
|
|
6
|
+
invalid: boolean;
|
|
7
|
+
required: boolean;
|
|
8
|
+
disabled: boolean;
|
|
9
|
+
}
|
|
10
|
+
/** Read the surrounding FormField (inputs use it to wire id, aria-describedby, aria-invalid). */
|
|
11
|
+
export declare const useFormField: () => FormFieldState | null;
|
|
12
|
+
export interface FormFieldProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
|
|
13
|
+
label: ReactNode;
|
|
14
|
+
/** Help text under the label. */
|
|
15
|
+
hint?: ReactNode;
|
|
16
|
+
/** Error message; marks the control `aria-invalid`. Say how to fix it ("Enter a work email"). */
|
|
17
|
+
error?: ReactNode;
|
|
18
|
+
required?: boolean;
|
|
19
|
+
disabled?: boolean;
|
|
20
|
+
/** Shows "(optional)" after the label instead of marking required fields. */
|
|
21
|
+
optionalLabel?: string;
|
|
22
|
+
/** Visually hide the label (it stays for screen readers). */
|
|
23
|
+
hideLabel?: boolean;
|
|
24
|
+
/** Extra content on the label row (e.g. "Forgot password?" link). */
|
|
25
|
+
labelAction?: ReactNode;
|
|
26
|
+
/** Override the generated id. */
|
|
27
|
+
id?: string;
|
|
28
|
+
children: ReactNode;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Wraps a single control with its label, help and error. Every Kodffe input reads the field
|
|
32
|
+
* context, so ids and ARIA are wired automatically.
|
|
33
|
+
*/
|
|
34
|
+
export declare function FormField({ label, hint, error, required, disabled, optionalLabel, hideLabel, labelAction, id: idProp, className, children, ...rest }: FormFieldProps): import("react").JSX.Element;
|
|
35
|
+
/** Groups related fields (address, billing) with a legend. */
|
|
36
|
+
export declare function Fieldset({ legend, description, className, children, ...rest }: HTMLAttributes<HTMLFieldSetElement> & {
|
|
37
|
+
legend: ReactNode;
|
|
38
|
+
description?: ReactNode;
|
|
39
|
+
}): import("react").JSX.Element;
|