@dowel-ui/react 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -7
- package/dist/components/ai-agent-plan/ai-agent-plan.d.ts +50 -0
- package/dist/components/ai-agent-plan/ai-agent-plan.d.ts.map +1 -0
- package/dist/components/ai-agent-plan/ai-agent-plan.js +156 -0
- package/dist/components/ai-agent-plan/ai-agent-plan.js.map +1 -0
- package/dist/components/ai-agent-plan/index.d.ts +2 -0
- package/dist/components/ai-agent-plan/index.js +2 -0
- package/dist/components/ai-agent-plan/meta.js +17 -0
- package/dist/components/ai-agent-plan/meta.js.map +1 -0
- package/dist/components/ai-agent-status/ai-agent-status.d.ts +1 -1
- package/dist/components/ai-approval-request/ai-approval-request.d.ts +67 -0
- package/dist/components/ai-approval-request/ai-approval-request.d.ts.map +1 -0
- package/dist/components/ai-approval-request/ai-approval-request.js +218 -0
- package/dist/components/ai-approval-request/ai-approval-request.js.map +1 -0
- package/dist/components/ai-approval-request/index.d.ts +2 -0
- package/dist/components/ai-approval-request/index.js +2 -0
- package/dist/components/ai-approval-request/meta.js +17 -0
- package/dist/components/ai-approval-request/meta.js.map +1 -0
- package/dist/components/ai-disclosure/ai-disclosure.d.ts +92 -0
- package/dist/components/ai-disclosure/ai-disclosure.d.ts.map +1 -0
- package/dist/components/ai-disclosure/ai-disclosure.js +122 -0
- package/dist/components/ai-disclosure/ai-disclosure.js.map +1 -0
- package/dist/components/ai-disclosure/index.d.ts +2 -0
- package/dist/components/ai-disclosure/index.js +2 -0
- package/dist/components/ai-disclosure/meta.js +17 -0
- package/dist/components/ai-disclosure/meta.js.map +1 -0
- package/dist/components/ai-response/ai-response.d.ts.map +1 -1
- package/dist/components/ai-response/ai-response.js +1 -0
- package/dist/components/ai-response/ai-response.js.map +1 -1
- package/dist/components/ai-structured-output/ai-structured-output.d.ts.map +1 -1
- package/dist/components/ai-structured-output/ai-structured-output.js +13 -2
- package/dist/components/ai-structured-output/ai-structured-output.js.map +1 -1
- package/dist/components/ai-tool/ai-tool.d.ts +1 -1
- package/dist/components/alert/alert.d.ts +1 -1
- package/dist/components/avatar/avatar.d.ts +1 -1
- package/dist/components/badge/badge.d.ts +1 -1
- package/dist/components/button/button.d.ts +1 -1
- package/dist/components/date-picker/date-picker.js +1 -1
- package/dist/components/diff-viewer/diff-model.d.ts +69 -0
- package/dist/components/diff-viewer/diff-model.d.ts.map +1 -0
- package/dist/components/diff-viewer/diff-model.js +180 -0
- package/dist/components/diff-viewer/diff-model.js.map +1 -0
- package/dist/components/diff-viewer/diff-viewer.d.ts +42 -0
- package/dist/components/diff-viewer/diff-viewer.d.ts.map +1 -0
- package/dist/components/diff-viewer/diff-viewer.js +212 -0
- package/dist/components/diff-viewer/diff-viewer.js.map +1 -0
- package/dist/components/diff-viewer/index.d.ts +3 -0
- package/dist/components/diff-viewer/index.js +3 -0
- package/dist/components/diff-viewer/meta.js +17 -0
- package/dist/components/diff-viewer/meta.js.map +1 -0
- package/dist/components/file-upload/file-upload.d.ts +46 -0
- package/dist/components/file-upload/file-upload.d.ts.map +1 -0
- package/dist/components/file-upload/file-upload.js +204 -0
- package/dist/components/file-upload/file-upload.js.map +1 -0
- package/dist/components/file-upload/index.d.ts +3 -0
- package/dist/components/file-upload/index.js +3 -0
- package/dist/components/file-upload/meta.js +17 -0
- package/dist/components/file-upload/meta.js.map +1 -0
- package/dist/components/file-upload/upload-queue.d.ts +87 -0
- package/dist/components/file-upload/upload-queue.d.ts.map +1 -0
- package/dist/components/file-upload/upload-queue.js +249 -0
- package/dist/components/file-upload/upload-queue.js.map +1 -0
- package/dist/components/log-viewer/index.d.ts +3 -0
- package/dist/components/log-viewer/index.js +3 -0
- package/dist/components/log-viewer/log-stream.d.ts +68 -0
- package/dist/components/log-viewer/log-stream.d.ts.map +1 -0
- package/dist/components/log-viewer/log-stream.js +120 -0
- package/dist/components/log-viewer/log-stream.js.map +1 -0
- package/dist/components/log-viewer/log-viewer.d.ts +51 -0
- package/dist/components/log-viewer/log-viewer.d.ts.map +1 -0
- package/dist/components/log-viewer/log-viewer.js +284 -0
- package/dist/components/log-viewer/log-viewer.js.map +1 -0
- package/dist/components/log-viewer/meta.js +17 -0
- package/dist/components/log-viewer/meta.js.map +1 -0
- package/dist/components/progress/progress.d.ts +1 -1
- package/dist/components/progress/progress.js +1 -0
- package/dist/components/progress/progress.js.map +1 -1
- package/dist/components/spinner/spinner.d.ts +1 -1
- package/dist/components/spinner/spinner.js +1 -0
- package/dist/components/spinner/spinner.js.map +1 -1
- package/dist/components/tags-input/index.d.ts +2 -0
- package/dist/components/tags-input/index.js +2 -0
- package/dist/components/tags-input/meta.js +17 -0
- package/dist/components/tags-input/meta.js.map +1 -0
- package/dist/components/tags-input/tags-input.d.ts +47 -0
- package/dist/components/tags-input/tags-input.d.ts.map +1 -0
- package/dist/components/tags-input/tags-input.js +182 -0
- package/dist/components/tags-input/tags-input.js.map +1 -0
- package/dist/components/time-range-picker/index.d.ts +3 -0
- package/dist/components/time-range-picker/index.js +3 -0
- package/dist/components/time-range-picker/meta.js +22 -0
- package/dist/components/time-range-picker/meta.js.map +1 -0
- package/dist/components/time-range-picker/time-expression.d.ts +88 -0
- package/dist/components/time-range-picker/time-expression.d.ts.map +1 -0
- package/dist/components/time-range-picker/time-expression.js +335 -0
- package/dist/components/time-range-picker/time-expression.js.map +1 -0
- package/dist/components/time-range-picker/time-range-picker.d.ts +65 -0
- package/dist/components/time-range-picker/time-range-picker.d.ts.map +1 -0
- package/dist/components/time-range-picker/time-range-picker.js +377 -0
- package/dist/components/time-range-picker/time-range-picker.js.map +1 -0
- package/dist/components/toast/toast.d.ts +1 -1
- package/dist/index.d.ts +21 -1
- package/dist/index.js +14 -2
- package/dist/registry/components.d.ts.map +1 -1
- package/dist/registry/components.js +66 -50
- package/dist/registry/components.js.map +1 -1
- package/package.json +6 -4
- package/src/components/ai-agent-plan/ai-agent-plan.tsx +0 -0
- package/src/components/ai-agent-plan/index.ts +10 -0
- package/src/components/ai-agent-plan/meta.ts +19 -0
- package/src/components/ai-approval-request/ai-approval-request.tsx +368 -0
- package/src/components/ai-approval-request/index.ts +7 -0
- package/src/components/ai-approval-request/meta.ts +21 -0
- package/src/components/ai-disclosure/ai-disclosure.tsx +244 -0
- package/src/components/ai-disclosure/index.ts +9 -0
- package/src/components/ai-disclosure/meta.ts +23 -0
- package/src/components/ai-response/ai-response.tsx +1 -0
- package/src/components/ai-structured-output/ai-structured-output.tsx +25 -1
- package/src/components/diff-viewer/diff-model.ts +256 -0
- package/src/components/diff-viewer/diff-viewer.tsx +339 -0
- package/src/components/diff-viewer/index.ts +17 -0
- package/src/components/diff-viewer/meta.ts +20 -0
- package/src/components/file-upload/file-upload.tsx +322 -0
- package/src/components/file-upload/index.ts +20 -0
- package/src/components/file-upload/meta.ts +21 -0
- package/src/components/file-upload/upload-queue.ts +337 -0
- package/src/components/log-viewer/index.ts +21 -0
- package/src/components/log-viewer/log-stream.ts +176 -0
- package/src/components/log-viewer/log-viewer.tsx +424 -0
- package/src/components/log-viewer/meta.ts +21 -0
- package/src/components/progress/progress.tsx +3 -0
- package/src/components/spinner/spinner.tsx +5 -0
- package/src/components/tags-input/index.ts +1 -0
- package/src/components/tags-input/meta.ts +23 -0
- package/src/components/tags-input/tags-input.tsx +300 -0
- package/src/components/time-range-picker/index.ts +25 -0
- package/src/components/time-range-picker/meta.ts +22 -0
- package/src/components/time-range-picker/time-expression.ts +491 -0
- package/src/components/time-range-picker/time-range-picker.tsx +522 -0
- package/src/index.ts +8 -0
- package/src/registry/components.ts +16 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"progress.js","names":["Progress","ProgressPrimitive"],"sources":["../../../src/components/progress/progress.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { Progress as ProgressPrimitive } from \"radix-ui\";\nimport type { ComponentPropsWithRef } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst progressVariants = cva(\"relative w-full overflow-hidden rounded-full bg-muted\", {\n variants: {\n size: {\n sm: \"h-1\",\n md: \"h-2\",\n lg: \"h-3\",\n },\n tone: {\n primary: \"[--progress-fill:var(--color-primary)]\",\n success: \"[--progress-fill:var(--color-success)]\",\n warning: \"[--progress-fill:var(--color-warning)]\",\n destructive: \"[--progress-fill:var(--color-destructive)]\",\n },\n },\n defaultVariants: {\n size: \"md\",\n tone: \"primary\",\n },\n});\n\nexport interface ProgressProps\n extends\n ComponentPropsWithRef<typeof ProgressPrimitive.Root>,\n VariantProps<typeof progressVariants> {}\n\n/**\n * Shows how far along a task is.\n *\n * Pass `value={null}` for work whose duration is unknown — that is a genuinely\n * different state from zero percent, and it is announced as such rather than as\n * \"no progress\". For a short wait with no measurable progress, a Spinner says\n * the same thing with less furniture.\n */\nexport function Progress({ className, size, tone, value, ...props }: ProgressProps) {\n const indeterminate = value === null || value === undefined;\n const percent = indeterminate ? 0 : Math.min(100, Math.max(0, value));\n\n return (\n <ProgressPrimitive.Root\n data-slot=\"progress\"\n value={value}\n className={cn(progressVariants({ size, tone }), className)}\n {...props}\n >\n <ProgressPrimitive.Indicator\n data-slot=\"progress-indicator\"\n className={cn(\n \"h-full w-full flex-1 rounded-full bg-[var(--progress-fill)]\",\n \"transition-transform duration-[var(--duration-slow)] ease-[var(--ease-out-quint)]\",\n // With no known value the bar sweeps instead of filling, so it reads\n // as \"working\" rather than as a stalled 0%.\n indeterminate && \"animate-pulse-soft\",\n )}\n style={\n indeterminate ? undefined : { transform: `translateX(-${String(100 - percent)}%)` }\n }\n />\n </ProgressPrimitive.Root>\n );\n}\n\nexport { progressVariants };\n"],"mappings":";;;;;;AAQA,MAAM,mBAAmB,IAAI,yDAAyD;CACpF,UAAU;EACR,MAAM;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;EACN;EACA,MAAM;GACJ,SAAS;GACT,SAAS;GACT,SAAS;GACT,aAAa;EACf;CACF;CACA,iBAAiB;EACf,MAAM;EACN,MAAM;CACR;AACF,CAAC;;;;;;;;;AAeD,SAAgBA,WAAS,EAAE,WAAW,MAAM,MAAM,OAAO,GAAG,SAAwB;CAClF,MAAM,gBAAgB,UAAU,QAAQ,UAAU,KAAA;CAClD,MAAM,UAAU,gBAAgB,IAAI,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,KAAK,CAAC;CAEpE,OACE,oBAACC,SAAkB,MAAnB;EACE,aAAU;EACH;EACP,WAAW,GAAG,iBAAiB;GAAE;GAAM;EAAK,CAAC,GAAG,SAAS;EACzD,GAAI;EAEJ,UAAA,oBAACA,SAAkB,WAAnB;GACE,aAAU;
|
|
1
|
+
{"version":3,"file":"progress.js","names":["Progress","ProgressPrimitive"],"sources":["../../../src/components/progress/progress.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { Progress as ProgressPrimitive } from \"radix-ui\";\nimport type { ComponentPropsWithRef } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst progressVariants = cva(\"relative w-full overflow-hidden rounded-full bg-muted\", {\n variants: {\n size: {\n sm: \"h-1\",\n md: \"h-2\",\n lg: \"h-3\",\n },\n tone: {\n primary: \"[--progress-fill:var(--color-primary)]\",\n success: \"[--progress-fill:var(--color-success)]\",\n warning: \"[--progress-fill:var(--color-warning)]\",\n destructive: \"[--progress-fill:var(--color-destructive)]\",\n },\n },\n defaultVariants: {\n size: \"md\",\n tone: \"primary\",\n },\n});\n\nexport interface ProgressProps\n extends\n ComponentPropsWithRef<typeof ProgressPrimitive.Root>,\n VariantProps<typeof progressVariants> {}\n\n/**\n * Shows how far along a task is.\n *\n * Pass `value={null}` for work whose duration is unknown — that is a genuinely\n * different state from zero percent, and it is announced as such rather than as\n * \"no progress\". For a short wait with no measurable progress, a Spinner says\n * the same thing with less furniture.\n */\nexport function Progress({ className, size, tone, value, ...props }: ProgressProps) {\n const indeterminate = value === null || value === undefined;\n const percent = indeterminate ? 0 : Math.min(100, Math.max(0, value));\n\n return (\n <ProgressPrimitive.Root\n data-slot=\"progress\"\n value={value}\n className={cn(progressVariants({ size, tone }), className)}\n {...props}\n >\n <ProgressPrimitive.Indicator\n data-slot=\"progress-indicator\"\n // Only while indeterminate: the sweep is the signal that work is\n // ongoing, and freezing it reads as a stalled zero percent.\n data-motion={indeterminate ? \"indicator\" : undefined}\n className={cn(\n \"h-full w-full flex-1 rounded-full bg-[var(--progress-fill)]\",\n \"transition-transform duration-[var(--duration-slow)] ease-[var(--ease-out-quint)]\",\n // With no known value the bar sweeps instead of filling, so it reads\n // as \"working\" rather than as a stalled 0%.\n indeterminate && \"animate-pulse-soft\",\n )}\n style={\n indeterminate ? undefined : { transform: `translateX(-${String(100 - percent)}%)` }\n }\n />\n </ProgressPrimitive.Root>\n );\n}\n\nexport { progressVariants };\n"],"mappings":";;;;;;AAQA,MAAM,mBAAmB,IAAI,yDAAyD;CACpF,UAAU;EACR,MAAM;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;EACN;EACA,MAAM;GACJ,SAAS;GACT,SAAS;GACT,SAAS;GACT,aAAa;EACf;CACF;CACA,iBAAiB;EACf,MAAM;EACN,MAAM;CACR;AACF,CAAC;;;;;;;;;AAeD,SAAgBA,WAAS,EAAE,WAAW,MAAM,MAAM,OAAO,GAAG,SAAwB;CAClF,MAAM,gBAAgB,UAAU,QAAQ,UAAU,KAAA;CAClD,MAAM,UAAU,gBAAgB,IAAI,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,KAAK,CAAC;CAEpE,OACE,oBAACC,SAAkB,MAAnB;EACE,aAAU;EACH;EACP,WAAW,GAAG,iBAAiB;GAAE;GAAM;EAAK,CAAC,GAAG,SAAS;EACzD,GAAI;EAEJ,UAAA,oBAACA,SAAkB,WAAnB;GACE,aAAU;GAGV,eAAa,gBAAgB,cAAc,KAAA;GAC3C,WAAW,GACT,+DACA,qFAGA,iBAAiB,oBACnB;GACA,OACE,gBAAgB,KAAA,IAAY,EAAE,WAAW,eAAe,OAAO,MAAM,OAAO,EAAE,IAAI;EAErF,CAAA;CACqB,CAAA;AAE5B"}
|
|
@@ -2,7 +2,7 @@ import { VariantProps } from "class-variance-authority";
|
|
|
2
2
|
import { ComponentPropsWithRef } from "react";
|
|
3
3
|
//#region src/components/spinner/spinner.d.ts
|
|
4
4
|
declare const spinnerVariants: (props?: ({
|
|
5
|
-
size?: "sm" | "md" | "
|
|
5
|
+
size?: "sm" | "md" | "xs" | "lg" | "xl" | null | undefined;
|
|
6
6
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
7
7
|
interface SpinnerProps extends Omit<ComponentPropsWithRef<"svg">, "children">, VariantProps<typeof spinnerVariants> {
|
|
8
8
|
/**
|
|
@@ -18,6 +18,7 @@ function Spinner({ className, size, label, ...props }) {
|
|
|
18
18
|
viewBox: "0 0 24 24",
|
|
19
19
|
fill: "none",
|
|
20
20
|
"aria-hidden": "true",
|
|
21
|
+
"data-motion": "indicator",
|
|
21
22
|
className: cn(spinnerVariants({ size }), className),
|
|
22
23
|
...props,
|
|
23
24
|
children: [/* @__PURE__ */ jsx("circle", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spinner.js","names":[],"sources":["../../../src/components/spinner/spinner.tsx"],"sourcesContent":["import { cva, type VariantProps } from \"class-variance-authority\";\nimport type { ComponentPropsWithRef } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst spinnerVariants = cva(\"animate-spin text-current\", {\n variants: {\n size: {\n xs: \"size-3\",\n sm: \"size-3.5\",\n md: \"size-4\",\n lg: \"size-5\",\n xl: \"size-6\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n});\n\nexport interface SpinnerProps\n extends Omit<ComponentPropsWithRef<\"svg\">, \"children\">, VariantProps<typeof spinnerVariants> {\n /**\n * Announced to assistive technology while the spinner is visible.\n *\n * Omit it when the spinner sits inside a control that already communicates\n * its busy state (a loading Button, for example) — announcing twice is worse\n * than not announcing at all.\n */\n label?: string;\n}\n\n/** Indeterminate loading indicator. */\nexport function Spinner({ className, size, label, ...props }: SpinnerProps) {\n return (\n <>\n <svg\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n aria-hidden=\"true\"\n className={cn(spinnerVariants({ size }), className)}\n {...props}\n >\n <circle\n cx=\"12\"\n cy=\"12\"\n r=\"9.5\"\n stroke=\"currentColor\"\n strokeWidth=\"2.5\"\n opacity=\"0.22\"\n />\n <path\n d=\"M12 2.5a9.5 9.5 0 0 1 9.5 9.5\"\n stroke=\"currentColor\"\n strokeWidth=\"2.5\"\n strokeLinecap=\"round\"\n />\n </svg>\n {label ? (\n <span role=\"status\" className=\"sr-only\">\n {label}\n </span>\n ) : null}\n </>\n );\n}\n\nexport { spinnerVariants };\n"],"mappings":";;;;AAKA,MAAM,kBAAkB,IAAI,6BAA6B;CACvD,UAAU,EACR,MAAM;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;CACN,EACF;CACA,iBAAiB,EACf,MAAM,KACR;AACF,CAAC;;AAeD,SAAgB,QAAQ,EAAE,WAAW,MAAM,OAAO,GAAG,SAAuB;CAC1E,OACE,qBAAA,UAAA,EAAA,UAAA,CACE,qBAAC,OAAD;EACE,SAAQ;EACR,MAAK;EACL,eAAY;EACZ,WAAW,GAAG,gBAAgB,EAAE,KAAK,CAAC,GAAG,SAAS;EAClD,GAAI;
|
|
1
|
+
{"version":3,"file":"spinner.js","names":[],"sources":["../../../src/components/spinner/spinner.tsx"],"sourcesContent":["import { cva, type VariantProps } from \"class-variance-authority\";\nimport type { ComponentPropsWithRef } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst spinnerVariants = cva(\"animate-spin text-current\", {\n variants: {\n size: {\n xs: \"size-3\",\n sm: \"size-3.5\",\n md: \"size-4\",\n lg: \"size-5\",\n xl: \"size-6\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n});\n\nexport interface SpinnerProps\n extends Omit<ComponentPropsWithRef<\"svg\">, \"children\">, VariantProps<typeof spinnerVariants> {\n /**\n * Announced to assistive technology while the spinner is visible.\n *\n * Omit it when the spinner sits inside a control that already communicates\n * its busy state (a loading Button, for example) — announcing twice is worse\n * than not announcing at all.\n */\n label?: string;\n}\n\n/** Indeterminate loading indicator. */\nexport function Spinner({ className, size, label, ...props }: SpinnerProps) {\n return (\n <>\n <svg\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n aria-hidden=\"true\"\n // Exempt from the reduced-motion blanket: this is not decoration. A\n // spinner that stops turning reports that the application has hung,\n // which is a worse experience than the motion it was avoiding. It is\n // slowed instead — see --motion-scale-indicator.\n data-motion=\"indicator\"\n className={cn(spinnerVariants({ size }), className)}\n {...props}\n >\n <circle\n cx=\"12\"\n cy=\"12\"\n r=\"9.5\"\n stroke=\"currentColor\"\n strokeWidth=\"2.5\"\n opacity=\"0.22\"\n />\n <path\n d=\"M12 2.5a9.5 9.5 0 0 1 9.5 9.5\"\n stroke=\"currentColor\"\n strokeWidth=\"2.5\"\n strokeLinecap=\"round\"\n />\n </svg>\n {label ? (\n <span role=\"status\" className=\"sr-only\">\n {label}\n </span>\n ) : null}\n </>\n );\n}\n\nexport { spinnerVariants };\n"],"mappings":";;;;AAKA,MAAM,kBAAkB,IAAI,6BAA6B;CACvD,UAAU,EACR,MAAM;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;CACN,EACF;CACA,iBAAiB,EACf,MAAM,KACR;AACF,CAAC;;AAeD,SAAgB,QAAQ,EAAE,WAAW,MAAM,OAAO,GAAG,SAAuB;CAC1E,OACE,qBAAA,UAAA,EAAA,UAAA,CACE,qBAAC,OAAD;EACE,SAAQ;EACR,MAAK;EACL,eAAY;EAKZ,eAAY;EACZ,WAAW,GAAG,gBAAgB,EAAE,KAAK,CAAC,GAAG,SAAS;EAClD,GAAI;EAVN,UAAA,CAYE,oBAAC,UAAD;GACE,IAAG;GACH,IAAG;GACH,GAAE;GACF,QAAO;GACP,aAAY;GACZ,SAAQ;EACT,CAAA,GACD,oBAAC,QAAD;GACE,GAAE;GACF,QAAO;GACP,aAAY;GACZ,eAAc;EACf,CAAA,CACE;CACJ,CAAA,GAAA,QACC,oBAAC,QAAD;EAAM,MAAK;EAAS,WAAU;EAC3B,UAAA;CACG,CAAA,IACJ,IACJ,EAAA,CAAA;AAEN"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { defineMeta } from "../../registry/schema.js";
|
|
2
|
+
//#region src/components/tags-input/meta.ts
|
|
3
|
+
const meta = defineMeta({
|
|
4
|
+
name: "tags-input",
|
|
5
|
+
title: "Tags Input",
|
|
6
|
+
description: "A list of short values, where invalid entries stay visible instead of vanishing.",
|
|
7
|
+
category: "form",
|
|
8
|
+
status: "stable",
|
|
9
|
+
dependencies: [],
|
|
10
|
+
registryDependencies: [],
|
|
11
|
+
files: ["tags-input.tsx"],
|
|
12
|
+
a11y: "No WAI-ARIA pattern covers a token field, and the near misses misdescribe it — these are committed values, not options being chosen, so calling the container a listbox would promise navigation that does not exist. It is a labelled group holding a list, with a remove button per token carrying the tag in its accessible name. An invalid token's reason is inside the token, so it is announced with it rather than as a detached error. Additions, removals and refusals all reach a polite live region, because a refusal that looks like nothing happening is the failure this component exists to prevent. Remove buttons sit in the natural tab order rather than behind a roving tabindex: it costs a tab stop per token, and buys a pattern every assistive technology already understands."
|
|
13
|
+
});
|
|
14
|
+
//#endregion
|
|
15
|
+
export { meta };
|
|
16
|
+
|
|
17
|
+
//# sourceMappingURL=meta.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meta.js","names":[],"sources":["../../../src/components/tags-input/meta.ts"],"sourcesContent":["import { defineMeta } from \"@/registry/schema\";\n\nexport const meta = defineMeta({\n name: \"tags-input\",\n title: \"Tags Input\",\n description:\n \"A list of short values, where invalid entries stay visible instead of vanishing.\",\n category: \"form\",\n status: \"stable\",\n dependencies: [],\n registryDependencies: [],\n files: [\"tags-input.tsx\"],\n a11y:\n \"No WAI-ARIA pattern covers a token field, and the near misses misdescribe it — these are \" +\n \"committed values, not options being chosen, so calling the container a listbox would promise \" +\n \"navigation that does not exist. It is a labelled group holding a list, with a remove button \" +\n \"per token carrying the tag in its accessible name. An invalid token's reason is inside the \" +\n \"token, so it is announced with it rather than as a detached error. Additions, removals and \" +\n \"refusals all reach a polite live region, because a refusal that looks like nothing happening \" +\n \"is the failure this component exists to prevent. Remove buttons sit in the natural tab order \" +\n \"rather than behind a roving tabindex: it costs a tab stop per token, and buys a pattern every \" +\n \"assistive technology already understands.\",\n});\n"],"mappings":";;AAEA,MAAa,OAAO,WAAW;CAC7B,MAAM;CACN,OAAO;CACP,aACE;CACF,UAAU;CACV,QAAQ;CACR,cAAc,CAAC;CACf,sBAAsB,CAAC;CACvB,OAAO,CAAC,gBAAgB;CACxB,MACE;AASJ,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { ComponentPropsWithRef } from "react";
|
|
2
|
+
//#region src/components/tags-input/tags-input.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* A field whose value is a list of short strings: invited emails, allowed
|
|
5
|
+
* domains, labels, stop sequences.
|
|
6
|
+
*
|
|
7
|
+
* The behaviour that makes this worth shipping is what happens to input that
|
|
8
|
+
* fails validation. Every implementation surveyed either refuses to create the
|
|
9
|
+
* token, or creates it and silently throws it away — and both leave the reader
|
|
10
|
+
* staring at a field that did not do what they asked, with nothing to correct.
|
|
11
|
+
* Here an invalid entry becomes a token like any other, marked invalid and
|
|
12
|
+
* carrying its reason, so it can be seen, read out, and fixed by removing it.
|
|
13
|
+
* `value` holds every token including the invalid ones; the consumer runs the
|
|
14
|
+
* same `validate` to decide whether the field is submittable.
|
|
15
|
+
*
|
|
16
|
+
* Deliberately no suggestion list. A token field with an anchored listbox is a
|
|
17
|
+
* multi-select combobox, and this library already has 544 lines of hand-rolled
|
|
18
|
+
* combobox ARIA. A second copy of that would be the expensive kind of
|
|
19
|
+
* duplication — the kind that drifts. When multi-select is wanted, it belongs in
|
|
20
|
+
* Combobox.
|
|
21
|
+
*/
|
|
22
|
+
/** Returns `true` for a valid tag, or a reason it is not. */
|
|
23
|
+
type TagValidator = (tag: string, existing: string[]) => true | string;
|
|
24
|
+
interface TagsInputProps extends Omit<ComponentPropsWithRef<"div">, "onChange" | "children"> {
|
|
25
|
+
value: string[];
|
|
26
|
+
onValueChange: (tags: string[]) => void;
|
|
27
|
+
/** Names the field. Required — an unlabelled list of tokens is unreadable. */
|
|
28
|
+
label: string;
|
|
29
|
+
/** Characters that commit the token being typed, besides Enter. */
|
|
30
|
+
delimiters?: string[];
|
|
31
|
+
/**
|
|
32
|
+
* Decides whether a tag is acceptable. An invalid tag is still added, marked,
|
|
33
|
+
* and given its reason, rather than being refused or dropped.
|
|
34
|
+
*/
|
|
35
|
+
validate?: TagValidator;
|
|
36
|
+
/** Rejects a tag already present. Rejections are announced, never silent. */
|
|
37
|
+
allowDuplicates?: boolean;
|
|
38
|
+
/** Refuses further tags once reached, and says so. */
|
|
39
|
+
max?: number;
|
|
40
|
+
placeholder?: string;
|
|
41
|
+
disabled?: boolean;
|
|
42
|
+
inputProps?: Omit<ComponentPropsWithRef<"input">, "value" | "onChange" | "disabled">;
|
|
43
|
+
}
|
|
44
|
+
declare function TagsInput({ className, value, onValueChange, label, delimiters, validate, allowDuplicates, max, placeholder, disabled, inputProps, ...props }: TagsInputProps): import("react").JSX.Element;
|
|
45
|
+
//#endregion
|
|
46
|
+
export { TagValidator, TagsInput, TagsInputProps };
|
|
47
|
+
//# sourceMappingURL=tags-input.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tags-input.d.ts","names":[],"sources":["../../../src/components/tags-input/tags-input.tsx"],"mappings":";;;;;;;;;;;;;;;;;;;;;;KAmCY,gBAAgB,aAAa;UAExB,uBAAuB,KACtC;EAGA;EACA,gBAAgB;;EAEhB;;EAEA;;;;;EAKA,WAAW;;EAEX;;EAEA;EACA;EACA;EACA,aAAa,KAAK;;iBAaJ,YACd,WACA,OACA,eACA,OACA,YACA,UACA,iBACA,KACA,aACA,UACA,eACG,SACF,iCAAc,IAAA"}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { disabledStyles, focusRing } from "../../lib/styles.js";
|
|
3
|
+
import { cn } from "../../lib/utils.js";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
import { useId, useRef, useState } from "react";
|
|
6
|
+
//#region src/components/tags-input/tags-input.tsx
|
|
7
|
+
/** Splits pasted or typed text on any delimiter, dropping empty fragments. */
|
|
8
|
+
function splitOn(text, delimiters) {
|
|
9
|
+
if (delimiters.length === 0) return [text];
|
|
10
|
+
const pattern = new RegExp(`[${delimiters.map((d) => `\\${d}`).join("")}]`);
|
|
11
|
+
return text.split(pattern).map((part) => part.trim()).filter((part) => part.length > 0);
|
|
12
|
+
}
|
|
13
|
+
function TagsInput({ className, value, onValueChange, label, delimiters = [",", ";"], validate, allowDuplicates = false, max, placeholder, disabled = false, inputProps, ...props }) {
|
|
14
|
+
const [draft, setDraft] = useState("");
|
|
15
|
+
const [announcement, setAnnouncement] = useState("");
|
|
16
|
+
const inputRef = useRef(null);
|
|
17
|
+
const labelId = useId();
|
|
18
|
+
const inputId = useId();
|
|
19
|
+
const hintId = useId();
|
|
20
|
+
const atLimit = max !== void 0 && value.length >= max;
|
|
21
|
+
function reasonFor(tag) {
|
|
22
|
+
if (!validate) return null;
|
|
23
|
+
const result = validate(tag, value);
|
|
24
|
+
return result === true ? null : result;
|
|
25
|
+
}
|
|
26
|
+
/** Adds tags, reporting anything refused rather than dropping it in silence. */
|
|
27
|
+
function commit(candidates) {
|
|
28
|
+
const accepted = [];
|
|
29
|
+
const refused = [];
|
|
30
|
+
for (const candidate of candidates.flatMap((part) => splitOn(part, delimiters))) {
|
|
31
|
+
const tag = candidate.trim();
|
|
32
|
+
if (tag.length === 0) continue;
|
|
33
|
+
if (!allowDuplicates && [...value, ...accepted].includes(tag)) {
|
|
34
|
+
refused.push(`${tag} is already added`);
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (max !== void 0 && value.length + accepted.length >= max) {
|
|
38
|
+
refused.push(`${tag} not added, limit of ${String(max)} reached`);
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
accepted.push(tag);
|
|
42
|
+
}
|
|
43
|
+
if (accepted.length > 0) onValueChange([...value, ...accepted]);
|
|
44
|
+
const parts = [];
|
|
45
|
+
if (accepted.length > 0) {
|
|
46
|
+
const invalid = accepted.filter((tag) => reasonFor(tag) !== null);
|
|
47
|
+
parts.push(`Added ${accepted.join(", ")}`);
|
|
48
|
+
if (invalid.length > 0) parts.push(`${String(invalid.length)} need attention`);
|
|
49
|
+
}
|
|
50
|
+
parts.push(...refused);
|
|
51
|
+
setAnnouncement(parts.join(". "));
|
|
52
|
+
}
|
|
53
|
+
function removeAt(index) {
|
|
54
|
+
const removed = value[index];
|
|
55
|
+
onValueChange(value.filter((_, position) => position !== index));
|
|
56
|
+
setAnnouncement(removed === void 0 ? "" : `Removed ${removed}`);
|
|
57
|
+
inputRef.current?.focus();
|
|
58
|
+
}
|
|
59
|
+
function handleKeyDown(event) {
|
|
60
|
+
if (event.key === "Enter") {
|
|
61
|
+
event.preventDefault();
|
|
62
|
+
if (draft.trim().length > 0) {
|
|
63
|
+
commit([draft]);
|
|
64
|
+
setDraft("");
|
|
65
|
+
}
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (delimiters.includes(event.key)) {
|
|
69
|
+
event.preventDefault();
|
|
70
|
+
if (draft.trim().length > 0) {
|
|
71
|
+
commit([draft]);
|
|
72
|
+
setDraft("");
|
|
73
|
+
}
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (event.key === "Backspace" && draft.length === 0 && value.length > 0) {
|
|
77
|
+
event.preventDefault();
|
|
78
|
+
removeAt(value.length - 1);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function handlePaste(event) {
|
|
82
|
+
const parts = splitOn(event.clipboardData.getData("text"), delimiters);
|
|
83
|
+
if (parts.length <= 1) return;
|
|
84
|
+
event.preventDefault();
|
|
85
|
+
commit(parts);
|
|
86
|
+
setDraft("");
|
|
87
|
+
}
|
|
88
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
89
|
+
"data-slot": "tags-input",
|
|
90
|
+
className: cn("flex flex-col gap-1.5", className),
|
|
91
|
+
...props,
|
|
92
|
+
children: [
|
|
93
|
+
/* @__PURE__ */ jsx("label", {
|
|
94
|
+
id: labelId,
|
|
95
|
+
htmlFor: inputId,
|
|
96
|
+
className: "w-fit text-sm font-medium",
|
|
97
|
+
children: label
|
|
98
|
+
}),
|
|
99
|
+
/* @__PURE__ */ jsxs("div", {
|
|
100
|
+
role: "group",
|
|
101
|
+
"aria-labelledby": labelId,
|
|
102
|
+
"data-disabled": disabled || void 0,
|
|
103
|
+
className: cn("flex min-h-10 w-full flex-wrap items-center gap-1.5 rounded-md border border-input bg-background px-2 py-1.5", "focus-within:ring-2 focus-within:ring-ring/55", disabled && "pointer-events-none opacity-55"),
|
|
104
|
+
children: [value.length > 0 ? /* @__PURE__ */ jsx("ul", {
|
|
105
|
+
className: "contents",
|
|
106
|
+
children: value.map((tag, index) => {
|
|
107
|
+
const reason = reasonFor(tag);
|
|
108
|
+
return /* @__PURE__ */ jsxs("li", {
|
|
109
|
+
"data-slot": "tags-input-tag",
|
|
110
|
+
"data-invalid": reason !== null || void 0,
|
|
111
|
+
className: cn("inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-xs font-medium", reason === null ? "border-transparent bg-secondary text-secondary-foreground" : "border-destructive/40 bg-destructive/10 text-destructive"),
|
|
112
|
+
children: [
|
|
113
|
+
tag,
|
|
114
|
+
reason === null ? null : /* @__PURE__ */ jsxs("span", {
|
|
115
|
+
className: "sr-only",
|
|
116
|
+
children: [", invalid: ", reason]
|
|
117
|
+
}),
|
|
118
|
+
/* @__PURE__ */ jsx("button", {
|
|
119
|
+
type: "button",
|
|
120
|
+
"data-slot": "tags-input-remove",
|
|
121
|
+
disabled,
|
|
122
|
+
"aria-label": `Remove ${tag}`,
|
|
123
|
+
onClick: () => {
|
|
124
|
+
removeAt(index);
|
|
125
|
+
},
|
|
126
|
+
className: cn("-mr-0.5 grid size-4 place-items-center rounded-full", "transition-colors hover:bg-foreground/10", focusRing, disabledStyles),
|
|
127
|
+
children: /* @__PURE__ */ jsx("svg", {
|
|
128
|
+
viewBox: "0 0 24 24",
|
|
129
|
+
"aria-hidden": "true",
|
|
130
|
+
className: "size-2.5",
|
|
131
|
+
children: /* @__PURE__ */ jsx("path", {
|
|
132
|
+
d: "M6 6l12 12M18 6L6 18",
|
|
133
|
+
stroke: "currentColor",
|
|
134
|
+
strokeWidth: "3",
|
|
135
|
+
strokeLinecap: "round"
|
|
136
|
+
})
|
|
137
|
+
})
|
|
138
|
+
})
|
|
139
|
+
]
|
|
140
|
+
}, `${tag}-${String(index)}`);
|
|
141
|
+
})
|
|
142
|
+
}) : null, /* @__PURE__ */ jsx("input", {
|
|
143
|
+
ref: inputRef,
|
|
144
|
+
type: "text",
|
|
145
|
+
id: inputId,
|
|
146
|
+
"data-slot": "tags-input-field",
|
|
147
|
+
value: draft,
|
|
148
|
+
disabled,
|
|
149
|
+
placeholder: value.length === 0 ? placeholder : void 0,
|
|
150
|
+
"aria-describedby": hintId,
|
|
151
|
+
onChange: (event) => {
|
|
152
|
+
setDraft(event.target.value);
|
|
153
|
+
},
|
|
154
|
+
onKeyDown: handleKeyDown,
|
|
155
|
+
onPaste: handlePaste,
|
|
156
|
+
onBlur: () => {
|
|
157
|
+
if (draft.trim().length > 0) {
|
|
158
|
+
commit([draft]);
|
|
159
|
+
setDraft("");
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
className: cn("min-w-24 flex-1 self-stretch bg-transparent text-sm outline-none", "placeholder:text-muted-foreground"),
|
|
163
|
+
...inputProps
|
|
164
|
+
})]
|
|
165
|
+
}),
|
|
166
|
+
/* @__PURE__ */ jsx("span", {
|
|
167
|
+
id: hintId,
|
|
168
|
+
className: "text-xs text-muted-foreground",
|
|
169
|
+
children: atLimit ? `Limit of ${String(max ?? 0)} reached` : `Press Enter${delimiters.length > 0 ? ` or ${delimiters.join(" ")}` : ""} to add`
|
|
170
|
+
}),
|
|
171
|
+
/* @__PURE__ */ jsx("span", {
|
|
172
|
+
"aria-live": "polite",
|
|
173
|
+
className: "sr-only",
|
|
174
|
+
children: announcement
|
|
175
|
+
})
|
|
176
|
+
]
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
//#endregion
|
|
180
|
+
export { TagsInput };
|
|
181
|
+
|
|
182
|
+
//# sourceMappingURL=tags-input.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tags-input.js","names":[],"sources":["../../../src/components/tags-input/tags-input.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n useId,\n useRef,\n useState,\n type ClipboardEvent,\n type ComponentPropsWithRef,\n type KeyboardEvent,\n} from \"react\";\n\nimport { disabledStyles, focusRing } from \"@/lib/styles\";\nimport { cn } from \"@/lib/utils\";\n\n/**\n * A field whose value is a list of short strings: invited emails, allowed\n * domains, labels, stop sequences.\n *\n * The behaviour that makes this worth shipping is what happens to input that\n * fails validation. Every implementation surveyed either refuses to create the\n * token, or creates it and silently throws it away — and both leave the reader\n * staring at a field that did not do what they asked, with nothing to correct.\n * Here an invalid entry becomes a token like any other, marked invalid and\n * carrying its reason, so it can be seen, read out, and fixed by removing it.\n * `value` holds every token including the invalid ones; the consumer runs the\n * same `validate` to decide whether the field is submittable.\n *\n * Deliberately no suggestion list. A token field with an anchored listbox is a\n * multi-select combobox, and this library already has 544 lines of hand-rolled\n * combobox ARIA. A second copy of that would be the expensive kind of\n * duplication — the kind that drifts. When multi-select is wanted, it belongs in\n * Combobox.\n */\n\n/** Returns `true` for a valid tag, or a reason it is not. */\nexport type TagValidator = (tag: string, existing: string[]) => true | string;\n\nexport interface TagsInputProps extends Omit<\n ComponentPropsWithRef<\"div\">,\n \"onChange\" | \"children\"\n> {\n value: string[];\n onValueChange: (tags: string[]) => void;\n /** Names the field. Required — an unlabelled list of tokens is unreadable. */\n label: string;\n /** Characters that commit the token being typed, besides Enter. */\n delimiters?: string[];\n /**\n * Decides whether a tag is acceptable. An invalid tag is still added, marked,\n * and given its reason, rather than being refused or dropped.\n */\n validate?: TagValidator;\n /** Rejects a tag already present. Rejections are announced, never silent. */\n allowDuplicates?: boolean;\n /** Refuses further tags once reached, and says so. */\n max?: number;\n placeholder?: string;\n disabled?: boolean;\n inputProps?: Omit<ComponentPropsWithRef<\"input\">, \"value\" | \"onChange\" | \"disabled\">;\n}\n\n/** Splits pasted or typed text on any delimiter, dropping empty fragments. */\nfunction splitOn(text: string, delimiters: string[]): string[] {\n if (delimiters.length === 0) return [text];\n const pattern = new RegExp(`[${delimiters.map((d) => `\\\\${d}`).join(\"\")}]`);\n return text\n .split(pattern)\n .map((part) => part.trim())\n .filter((part) => part.length > 0);\n}\n\nexport function TagsInput({\n className,\n value,\n onValueChange,\n label,\n delimiters = [\",\", \";\"],\n validate,\n allowDuplicates = false,\n max,\n placeholder,\n disabled = false,\n inputProps,\n ...props\n}: TagsInputProps) {\n const [draft, setDraft] = useState(\"\");\n const [announcement, setAnnouncement] = useState(\"\");\n const inputRef = useRef<HTMLInputElement | null>(null);\n const labelId = useId();\n const inputId = useId();\n const hintId = useId();\n\n const atLimit = max !== undefined && value.length >= max;\n\n function reasonFor(tag: string): string | null {\n if (!validate) return null;\n const result = validate(tag, value);\n return result === true ? null : result;\n }\n\n /** Adds tags, reporting anything refused rather than dropping it in silence. */\n function commit(candidates: string[]) {\n const accepted: string[] = [];\n const refused: string[] = [];\n\n // Split defensively rather than trusting the caller. A draft can arrive\n // holding a delimiter from autofill, an IME commit, or a programmatic set,\n // and a token with a comma inside it is never what was meant.\n for (const candidate of candidates.flatMap((part) => splitOn(part, delimiters))) {\n const tag = candidate.trim();\n if (tag.length === 0) continue;\n\n if (!allowDuplicates && [...value, ...accepted].includes(tag)) {\n refused.push(`${tag} is already added`);\n continue;\n }\n if (max !== undefined && value.length + accepted.length >= max) {\n refused.push(`${tag} not added, limit of ${String(max)} reached`);\n continue;\n }\n // An invalid tag is still added. It is the reader's text and they need to\n // see it to fix it; refusing silently is how these fields lose input.\n accepted.push(tag);\n }\n\n if (accepted.length > 0) onValueChange([...value, ...accepted]);\n\n const parts: string[] = [];\n if (accepted.length > 0) {\n const invalid = accepted.filter((tag) => reasonFor(tag) !== null);\n parts.push(`Added ${accepted.join(\", \")}`);\n if (invalid.length > 0) parts.push(`${String(invalid.length)} need attention`);\n }\n parts.push(...refused);\n setAnnouncement(parts.join(\". \"));\n }\n\n function removeAt(index: number) {\n const removed = value[index];\n onValueChange(value.filter((_, position) => position !== index));\n setAnnouncement(removed === undefined ? \"\" : `Removed ${removed}`);\n inputRef.current?.focus();\n }\n\n function handleKeyDown(event: KeyboardEvent<HTMLInputElement>) {\n if (event.key === \"Enter\") {\n // Never let a token commit submit the form as well.\n event.preventDefault();\n if (draft.trim().length > 0) {\n commit([draft]);\n setDraft(\"\");\n }\n return;\n }\n\n if (delimiters.includes(event.key)) {\n event.preventDefault();\n if (draft.trim().length > 0) {\n commit([draft]);\n setDraft(\"\");\n }\n return;\n }\n\n // Backspace on an empty field removes the last token — the expected\n // shortcut, and the only way to correct without reaching for the mouse.\n if (event.key === \"Backspace\" && draft.length === 0 && value.length > 0) {\n event.preventDefault();\n removeAt(value.length - 1);\n }\n }\n\n function handlePaste(event: ClipboardEvent<HTMLInputElement>) {\n const text = event.clipboardData.getData(\"text\");\n const parts = splitOn(text, delimiters);\n // Only intercept a paste that actually contains several tags; a single\n // value should behave like an ordinary paste and stay editable.\n if (parts.length <= 1) return;\n\n event.preventDefault();\n commit(parts);\n setDraft(\"\");\n }\n\n return (\n <div data-slot=\"tags-input\" className={cn(\"flex flex-col gap-1.5\", className)} {...props}>\n <label id={labelId} htmlFor={inputId} className=\"w-fit text-sm font-medium\">\n {label}\n </label>\n\n {/* A group rather than a listbox: these are committed values, not options\n being chosen from, and calling it a listbox would promise navigation\n semantics that do not exist here. */}\n <div\n role=\"group\"\n aria-labelledby={labelId}\n data-disabled={disabled || undefined}\n className={cn(\n \"flex min-h-10 w-full flex-wrap items-center gap-1.5 rounded-md border border-input bg-background px-2 py-1.5\",\n \"focus-within:ring-2 focus-within:ring-ring/55\",\n disabled && \"pointer-events-none opacity-55\",\n )}\n >\n {value.length > 0 ? (\n <ul className=\"contents\">\n {value.map((tag, index) => {\n const reason = reasonFor(tag);\n return (\n <li\n key={`${tag}-${String(index)}`}\n data-slot=\"tags-input-tag\"\n data-invalid={reason !== null || undefined}\n className={cn(\n \"inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-xs font-medium\",\n reason === null\n ? \"border-transparent bg-secondary text-secondary-foreground\"\n : \"border-destructive/40 bg-destructive/10 text-destructive\",\n )}\n >\n {tag}\n {/* The reason travels with the token, so it is announced when\n the token is, rather than as a detached error elsewhere. */}\n {reason === null ? null : (\n <span className=\"sr-only\">, invalid: {reason}</span>\n )}\n <button\n type=\"button\"\n data-slot=\"tags-input-remove\"\n disabled={disabled}\n aria-label={`Remove ${tag}`}\n onClick={() => {\n removeAt(index);\n }}\n className={cn(\n \"-mr-0.5 grid size-4 place-items-center rounded-full\",\n \"transition-colors hover:bg-foreground/10\",\n focusRing,\n disabledStyles,\n )}\n >\n <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\" className=\"size-2.5\">\n <path\n d=\"M6 6l12 12M18 6L6 18\"\n stroke=\"currentColor\"\n strokeWidth=\"3\"\n strokeLinecap=\"round\"\n />\n </svg>\n </button>\n </li>\n );\n })}\n </ul>\n ) : null}\n\n <input\n ref={inputRef}\n type=\"text\"\n id={inputId}\n data-slot=\"tags-input-field\"\n value={draft}\n disabled={disabled}\n placeholder={value.length === 0 ? placeholder : undefined}\n aria-describedby={hintId}\n onChange={(event) => {\n setDraft(event.target.value);\n }}\n onKeyDown={handleKeyDown}\n onPaste={handlePaste}\n onBlur={() => {\n // Committing on blur stops a typed-but-uncommitted value being lost\n // when the reader tabs away thinking they are done.\n if (draft.trim().length > 0) {\n commit([draft]);\n setDraft(\"\");\n }\n }}\n className={cn(\n \"min-w-24 flex-1 self-stretch bg-transparent text-sm outline-none\",\n \"placeholder:text-muted-foreground\",\n )}\n {...inputProps}\n />\n </div>\n\n <span id={hintId} className=\"text-xs text-muted-foreground\">\n {atLimit\n ? `Limit of ${String(max ?? 0)} reached`\n : `Press Enter${delimiters.length > 0 ? ` or ${delimiters.join(\" \")}` : \"\"} to add`}\n </span>\n\n {/* Additions, removals and refusals all land here. A refusal that is only\n visible as \"nothing happened\" is the failure this component exists to\n avoid. */}\n <span aria-live=\"polite\" className=\"sr-only\">\n {announcement}\n </span>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { DEFAULT_PRESETS, ResolveOptions, ResolvedRange, TimeExpressionError, TimeRangePreset, TimeUnit, absoluteExpression, describeTimeRange, formatResolvedRange, isValidTimeRange, resolveExpression, resolveTimeRange } from "./time-expression.js";
|
|
2
|
+
import { TimeRange, TimeRangeCalendar, TimeRangeContent, TimeRangeContentProps, TimeRangeExpression, TimeRangePresets, TimeRangeProps, TimeRangeTrigger, TimeRangeTriggerProps } from "./time-range-picker.js";
|
|
3
|
+
export { DEFAULT_PRESETS, type ResolveOptions, type ResolvedRange, TimeExpressionError, TimeRange, TimeRangeCalendar, TimeRangeContent, type TimeRangeContentProps, TimeRangeExpression, type TimeRangePreset, TimeRangePresets, type TimeRangeProps, TimeRangeTrigger, type TimeRangeTriggerProps, type TimeUnit, absoluteExpression, describeTimeRange, formatResolvedRange, isValidTimeRange, resolveExpression, resolveTimeRange };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { DEFAULT_PRESETS, TimeExpressionError, absoluteExpression, describeTimeRange, formatResolvedRange, isValidTimeRange, resolveExpression, resolveTimeRange } from "./time-expression.js";
|
|
2
|
+
import { TimeRange, TimeRangeCalendar, TimeRangeContent, TimeRangeExpression, TimeRangePresets, TimeRangeTrigger } from "./time-range-picker.js";
|
|
3
|
+
export { DEFAULT_PRESETS, TimeExpressionError, TimeRange, TimeRangeCalendar, TimeRangeContent, TimeRangeExpression, TimeRangePresets, TimeRangeTrigger, absoluteExpression, describeTimeRange, formatResolvedRange, isValidTimeRange, resolveExpression, resolveTimeRange };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { defineMeta } from "../../registry/schema.js";
|
|
2
|
+
//#region src/components/time-range-picker/meta.ts
|
|
3
|
+
const meta = defineMeta({
|
|
4
|
+
name: "time-range-picker",
|
|
5
|
+
title: "Time Range Picker",
|
|
6
|
+
description: "A time range whose value is an expression, so it stays relative across a reload.",
|
|
7
|
+
category: "form",
|
|
8
|
+
status: "stable",
|
|
9
|
+
dependencies: ["react-day-picker"],
|
|
10
|
+
registryDependencies: [
|
|
11
|
+
"button",
|
|
12
|
+
"calendar",
|
|
13
|
+
"input",
|
|
14
|
+
"popover"
|
|
15
|
+
],
|
|
16
|
+
files: ["time-expression.ts", "time-range-picker.tsx"],
|
|
17
|
+
a11y: "The trigger is named by the range it holds — \"Last 6 hours\" — and the window that resolves to is read after it, so the relative choice is not replaced by two timestamps. The popover carries role=\"dialog\" and is named. Presets are buttons rather than radios because choosing one both selects and dismisses, with aria-pressed carrying which matches the current expression. The expression field reports why an unparseable entry is invalid through one aria-describedby element that the preview and the error share, so a reader hears the error replace the preview rather than both at once; an invalid expression is never applied, because a chart silently re-scoping itself is worse than one that refuses."
|
|
18
|
+
});
|
|
19
|
+
//#endregion
|
|
20
|
+
export { meta };
|
|
21
|
+
|
|
22
|
+
//# sourceMappingURL=meta.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meta.js","names":[],"sources":["../../../src/components/time-range-picker/meta.ts"],"sourcesContent":["import { defineMeta } from \"@/registry/schema\";\n\nexport const meta = defineMeta({\n name: \"time-range-picker\",\n title: \"Time Range Picker\",\n description:\n \"A time range whose value is an expression, so it stays relative across a reload.\",\n category: \"form\",\n status: \"stable\",\n dependencies: [\"react-day-picker\"],\n registryDependencies: [\"button\", \"calendar\", \"input\", \"popover\"],\n files: [\"time-expression.ts\", \"time-range-picker.tsx\"],\n a11y:\n 'The trigger is named by the range it holds — \"Last 6 hours\" — and the window that ' +\n \"resolves to is read after it, so the relative choice is not replaced by two timestamps. \" +\n 'The popover carries role=\"dialog\" and is named. Presets are buttons rather than radios ' +\n \"because choosing one both selects and dismisses, with aria-pressed carrying which matches \" +\n \"the current expression. The expression field reports why an unparseable entry is invalid \" +\n \"through one aria-describedby element that the preview and the error share, so a reader \" +\n \"hears the error replace the preview rather than both at once; an invalid expression is \" +\n \"never applied, because a chart silently re-scoping itself is worse than one that refuses.\",\n});\n"],"mappings":";;AAEA,MAAa,OAAO,WAAW;CAC7B,MAAM;CACN,OAAO;CACP,aACE;CACF,UAAU;CACV,QAAQ;CACR,cAAc,CAAC,kBAAkB;CACjC,sBAAsB;EAAC;EAAU;EAAY;EAAS;CAAS;CAC/D,OAAO,CAAC,sBAAsB,uBAAuB;CACrD,MACE;AAQJ,CAAC"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
//#region src/components/time-range-picker/time-expression.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Time ranges as expressions, not as two frozen timestamps.
|
|
4
|
+
*
|
|
5
|
+
* `now-6h` is still the last six hours tomorrow; a resolved pair is six hours
|
|
6
|
+
* of last Tuesday forever. Storing the expression is what lets a dashboard URL
|
|
7
|
+
* survive being bookmarked and reloaded.
|
|
8
|
+
*
|
|
9
|
+
* The grammar is a deliberate subset of the one Grafana, Datadog and Kibana
|
|
10
|
+
* converged on, so the strings are already familiar:
|
|
11
|
+
*
|
|
12
|
+
* range := expr ".." expr
|
|
13
|
+
* expr := "now" offset* snap? | ISO-8601
|
|
14
|
+
* offset := ("-" | "+") digits unit
|
|
15
|
+
* snap := "/" unit
|
|
16
|
+
* unit := s | m | h | d | w | M | y
|
|
17
|
+
*
|
|
18
|
+
* now-6h..now the last six hours
|
|
19
|
+
* now-6h/h..now the last six hours, starting on the hour
|
|
20
|
+
* now/d..now/d today, all of it
|
|
21
|
+
* now-1d/d..now-1d/d yesterday
|
|
22
|
+
* 2026-01-01..now since a fixed instant
|
|
23
|
+
*
|
|
24
|
+
* Everything here is pure and takes `now` as an argument: reading the clock
|
|
25
|
+
* during render would disagree between the server and the browser.
|
|
26
|
+
*/
|
|
27
|
+
type TimeUnit = "s" | "m" | "h" | "d" | "w" | "M" | "y";
|
|
28
|
+
interface ResolveOptions {
|
|
29
|
+
/** The instant `now` refers to. Required, so nothing here reads the clock. */
|
|
30
|
+
now: Date;
|
|
31
|
+
/**
|
|
32
|
+
* IANA zone the snapping happens in — "start of day" is a different instant
|
|
33
|
+
* in Auckland than in Denver. Defaults to the runtime zone.
|
|
34
|
+
*/
|
|
35
|
+
timeZone?: string;
|
|
36
|
+
}
|
|
37
|
+
interface ResolvedRange {
|
|
38
|
+
from: Date;
|
|
39
|
+
to: Date;
|
|
40
|
+
}
|
|
41
|
+
/** Why an expression could not be resolved. Shown to the user verbatim. */
|
|
42
|
+
declare class TimeExpressionError extends Error {
|
|
43
|
+
/** The part of the input at fault, for pointing at it. */
|
|
44
|
+
readonly input: string;
|
|
45
|
+
constructor(message: string,
|
|
46
|
+
/** The part of the input at fault, for pointing at it. */
|
|
47
|
+
input: string);
|
|
48
|
+
}
|
|
49
|
+
/** Resolves a single side of a range. Exported for the input's live feedback. */
|
|
50
|
+
declare function resolveExpression(expression: string, edge: "start" | "end", options: ResolveOptions): Date;
|
|
51
|
+
/**
|
|
52
|
+
* Turns a range expression into the two instants a query needs.
|
|
53
|
+
*
|
|
54
|
+
* Throws `TimeExpressionError` rather than returning a fallback range: a chart
|
|
55
|
+
* silently showing the wrong window is worse than one that says it cannot.
|
|
56
|
+
*/
|
|
57
|
+
declare function resolveTimeRange(expression: string, options: ResolveOptions): ResolvedRange;
|
|
58
|
+
/** Whether an expression resolves, without the caller writing a try/catch. */
|
|
59
|
+
declare function isValidTimeRange(expression: string, options: ResolveOptions): boolean;
|
|
60
|
+
interface TimeRangePreset {
|
|
61
|
+
expression: string;
|
|
62
|
+
label: string;
|
|
63
|
+
/** Groups presets under a heading in the picker. */
|
|
64
|
+
group: string;
|
|
65
|
+
}
|
|
66
|
+
/** Relative windows first, for debugging; calendar periods after, for reporting. */
|
|
67
|
+
declare const DEFAULT_PRESETS: TimeRangePreset[];
|
|
68
|
+
/**
|
|
69
|
+
* A sentence for the trigger.
|
|
70
|
+
*
|
|
71
|
+
* An unnamed relative range is described in its own terms — "Last 90 minutes" —
|
|
72
|
+
* because showing it as two timestamps throws away what the user chose. Only an
|
|
73
|
+
* absolute range, which has no relative meaning left, is shown as dates.
|
|
74
|
+
*/
|
|
75
|
+
declare function describeTimeRange(expression: string, options: ResolveOptions & {
|
|
76
|
+
locale?: string;
|
|
77
|
+
presets?: TimeRangePreset[];
|
|
78
|
+
}): string;
|
|
79
|
+
/** The resolved window, spelled out. Pairs with the relative label, not replaces it. */
|
|
80
|
+
declare function formatResolvedRange(range: ResolvedRange, options?: {
|
|
81
|
+
locale?: string;
|
|
82
|
+
timeZone?: string;
|
|
83
|
+
}): string;
|
|
84
|
+
/** Builds the expression for an absolute range chosen on a calendar. */
|
|
85
|
+
declare function absoluteExpression(from: Date, to: Date): string;
|
|
86
|
+
//#endregion
|
|
87
|
+
export { DEFAULT_PRESETS, ResolveOptions, ResolvedRange, TimeExpressionError, TimeRangePreset, TimeUnit, absoluteExpression, describeTimeRange, formatResolvedRange, isValidTimeRange, resolveExpression, resolveTimeRange };
|
|
88
|
+
//# sourceMappingURL=time-expression.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"time-expression.d.ts","names":[],"sources":["../../../src/components/time-range-picker/time-expression.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;KA0BY;UAEK;;EAEf,KAAK;;;;;EAKL;;UAGe;EACf,MAAM;EACN,IAAI;;;cAIO,4BAA4B;;WAI5B;EAFT,YAAA,iBAES;;EAAA;;;iBAwOG,kBACd,oBACA,uBACA,SAAS,iBACR;;;;;;;iBAwFa,iBAAiB,oBAAoB,SAAS,iBAAiB;;iBAoB/D,iBAAiB,oBAAoB,SAAS;UAa7C;EACf;EACA;;EAEA;;;cAIW,iBAAiB;;;;;;;;iBAyBd,kBACd,oBACA,SAAS;EAAmB;EAAiB,UAAU;;;iBA2BzC,oBACd,OAAO,eACP;EAAW;EAAiB;;;iBAWd,mBAAmB,MAAM,MAAM,IAAI"}
|