atlas-react-kit 0.4.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/COMPONENTS.md +135 -41
  2. package/DESIGN_RULES.md +79 -20
  3. package/README.md +60 -11
  4. package/dist/accordion.d.ts +8 -0
  5. package/dist/accordion.js +20 -0
  6. package/dist/alert.d.ts +19 -0
  7. package/dist/alert.js +37 -0
  8. package/dist/avatar.d.ts +9 -0
  9. package/dist/avatar.js +16 -0
  10. package/dist/badge.d.ts +2 -2
  11. package/dist/badge.js +1 -1
  12. package/dist/breadcrumb.d.ts +16 -0
  13. package/dist/breadcrumb.js +33 -0
  14. package/dist/button.d.ts +6 -5
  15. package/dist/button.js +7 -6
  16. package/dist/calendar.d.ts +1 -1
  17. package/dist/calendar.js +20 -2
  18. package/dist/checkbox.js +1 -1
  19. package/dist/color-legend.d.ts +22 -0
  20. package/dist/color-legend.js +11 -0
  21. package/dist/combobox.d.ts +4 -1
  22. package/dist/combobox.js +11 -5
  23. package/dist/date-picker.js +14 -3
  24. package/dist/date-time-picker.d.ts +36 -0
  25. package/dist/date-time-picker.js +105 -0
  26. package/dist/dropdown-menu.d.ts +26 -0
  27. package/dist/dropdown-menu.js +56 -0
  28. package/dist/error-boundary.d.ts +1 -1
  29. package/dist/field.js +1 -1
  30. package/dist/form/date-time-picker-field.d.ts +16 -0
  31. package/dist/form/date-time-picker-field.js +12 -0
  32. package/dist/form/markdown-field.d.ts +17 -0
  33. package/dist/form/markdown-field.js +26 -0
  34. package/dist/form/text-field.d.ts +7 -1
  35. package/dist/form/text-field.js +3 -3
  36. package/dist/info-tip.d.ts +27 -0
  37. package/dist/info-tip.js +63 -0
  38. package/dist/input.js +1 -1
  39. package/dist/internal/form-control.d.ts +1 -1
  40. package/dist/internal/form-control.js +1 -1
  41. package/dist/internal/tabs-styles.js +3 -3
  42. package/dist/markdown.d.ts +18 -0
  43. package/dist/markdown.js +62 -0
  44. package/dist/meter.d.ts +22 -0
  45. package/dist/meter.js +17 -0
  46. package/dist/pagination.d.ts +18 -0
  47. package/dist/pagination.js +35 -0
  48. package/dist/progress.d.ts +8 -0
  49. package/dist/progress.js +13 -0
  50. package/dist/radio-group.d.ts +13 -0
  51. package/dist/radio-group.js +20 -0
  52. package/dist/search-picker.d.ts +30 -0
  53. package/dist/search-picker.js +100 -0
  54. package/dist/select.js +2 -2
  55. package/dist/slider.d.ts +20 -0
  56. package/dist/slider.js +20 -0
  57. package/dist/spinner.d.ts +8 -0
  58. package/dist/spinner.js +13 -0
  59. package/dist/stat-tile.d.ts +21 -0
  60. package/dist/stat-tile.js +17 -0
  61. package/dist/switch.d.ts +9 -0
  62. package/dist/switch.js +14 -0
  63. package/dist/table.d.ts +2 -0
  64. package/dist/table.js +26 -2
  65. package/dist/tabs.d.ts +6 -3
  66. package/dist/tabs.js +5 -2
  67. package/dist/textarea.js +1 -1
  68. package/dist/theme-provider.d.ts +2 -1
  69. package/dist/theme-provider.js +28 -4
  70. package/dist/toggle-group.js +2 -2
  71. package/dist/toggle.d.ts +11 -1
  72. package/dist/toggle.js +13 -3
  73. package/dist/tooltip.d.ts +15 -0
  74. package/dist/tooltip.js +26 -0
  75. package/package.json +92 -5
  76. package/skills/atlas-react-kit/SKILL.md +148 -0
  77. package/theme.css +28 -14
package/dist/badge.js CHANGED
@@ -3,7 +3,7 @@ import * as React from "react";
3
3
  import { cva } from "class-variance-authority";
4
4
  import { Slot } from "radix-ui";
5
5
  import { cn } from "./utils.js";
6
- const badgeVariants = cva("group/badge inline-flex w-fit shrink-0 items-center justify-center gap-1.5 overflow-hidden rounded-none border-0 bg-transparent px-0 py-0 text-[0.625rem] font-semibold tracking-widest whitespace-nowrap uppercase transition-colors focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-0 has-data-[icon=inline-start]:pl-0 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!", {
6
+ const badgeVariants = cva("group/badge inline-flex w-fit shrink-0 items-center justify-center gap-1.5 overflow-hidden rounded-none border-0 bg-transparent px-0 py-0 text-[0.625rem] font-semibold tracking-widest whitespace-nowrap uppercase transition-colors focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring has-data-[icon=inline-end]:pr-0 has-data-[icon=inline-start]:pl-0 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!", {
7
7
  variants: {
8
8
  variant: {
9
9
  default: "text-foreground [a]:hover:text-foreground/70",
@@ -0,0 +1,16 @@
1
+ import * as React from "react";
2
+ /**
3
+ * Where the current page sits in a hierarchy. The last item is
4
+ * `BreadcrumbPage` (plain text with `aria-current="page"`), not a link.
5
+ */
6
+ declare function Breadcrumb({ ...props }: React.ComponentProps<"nav">): React.JSX.Element;
7
+ declare function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">): React.JSX.Element;
8
+ declare function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">): React.JSX.Element;
9
+ /** Pass `asChild` to render a router link (e.g. Next `Link`) with these styles. */
10
+ declare function BreadcrumbLink({ asChild, className, ...props }: React.ComponentProps<"a"> & {
11
+ asChild?: boolean;
12
+ }): React.JSX.Element;
13
+ declare function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">): React.JSX.Element;
14
+ declare function BreadcrumbSeparator({ children, className, ...props }: React.ComponentProps<"li">): React.JSX.Element;
15
+ declare function BreadcrumbEllipsis({ className, ...props }: React.ComponentProps<"span">): React.JSX.Element;
16
+ export { Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, };
@@ -0,0 +1,33 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import * as React from "react";
3
+ import { Slot } from "radix-ui";
4
+ import { ChevronRightIcon, MoreHorizontalIcon } from "lucide-react";
5
+ import { cn } from "./utils.js";
6
+ /**
7
+ * Where the current page sits in a hierarchy. The last item is
8
+ * `BreadcrumbPage` (plain text with `aria-current="page"`), not a link.
9
+ */
10
+ function Breadcrumb({ ...props }) {
11
+ return _jsx("nav", { "aria-label": "Breadcrumb", "data-slot": "breadcrumb", ...props });
12
+ }
13
+ function BreadcrumbList({ className, ...props }) {
14
+ return (_jsx("ol", { "data-slot": "breadcrumb-list", className: cn("flex flex-wrap items-center gap-1.5 text-sm break-words text-muted-foreground sm:gap-2", className), ...props }));
15
+ }
16
+ function BreadcrumbItem({ className, ...props }) {
17
+ return (_jsx("li", { "data-slot": "breadcrumb-item", className: cn("inline-flex items-center gap-1.5", className), ...props }));
18
+ }
19
+ /** Pass `asChild` to render a router link (e.g. Next `Link`) with these styles. */
20
+ function BreadcrumbLink({ asChild, className, ...props }) {
21
+ const Comp = asChild ? Slot.Root : "a";
22
+ return (_jsx(Comp, { "data-slot": "breadcrumb-link", className: cn("transition-colors outline-none hover:text-selected-foreground focus-visible:text-selected-foreground focus-visible:underline focus-visible:underline-offset-4", className), ...props }));
23
+ }
24
+ function BreadcrumbPage({ className, ...props }) {
25
+ return (_jsx("span", { "data-slot": "breadcrumb-page", "aria-current": "page", className: cn("font-medium text-foreground", className), ...props }));
26
+ }
27
+ function BreadcrumbSeparator({ children, className, ...props }) {
28
+ return (_jsx("li", { "data-slot": "breadcrumb-separator", role: "presentation", "aria-hidden": "true", className: cn("[&>svg]:size-3.5", className), ...props, children: children ?? _jsx(ChevronRightIcon, {}) }));
29
+ }
30
+ function BreadcrumbEllipsis({ className, ...props }) {
31
+ return (_jsxs("span", { "data-slot": "breadcrumb-ellipsis", role: "presentation", "aria-hidden": "true", className: cn("flex size-6 items-center justify-center", className), ...props, children: [_jsx(MoreHorizontalIcon, { className: "size-4" }), _jsx("span", { className: "sr-only", children: "More" })] }));
32
+ }
33
+ export { Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, };
package/dist/button.d.ts CHANGED
@@ -1,14 +1,15 @@
1
1
  import * as React from "react";
2
2
  import { type VariantProps } from "class-variance-authority";
3
3
  /**
4
- * Outline and ghost hover/focus: brand purple border and text, optional
5
- * selected wash, no muted grey fill. Used by quiet actions such as
4
+ * Outline and ghost hover/focus: selected-foreground border, selected wash, and
5
+ * `selected-foreground` text (primary in light mode, a lighter purple that
6
+ * stays readable on dark), no muted grey fill. Used by quiet actions such as
6
7
  * "Clear selection" sitting on a selected row.
7
8
  */
8
- declare const quietButtonInteraction = "hover:border-primary hover:bg-selected hover:text-primary focus-visible:border-primary focus-visible:text-primary focus-visible:ring-primary/30 aria-expanded:border-primary aria-expanded:bg-selected aria-expanded:text-primary";
9
+ declare const quietButtonInteraction = "hover:border-selected-foreground hover:bg-selected hover:text-selected-foreground focus-visible:text-selected-foreground aria-expanded:border-selected-foreground aria-expanded:bg-selected aria-expanded:text-selected-foreground";
9
10
  declare const buttonVariants: (props?: ({
10
- variant?: "default" | "secondary" | "destructive" | "outline" | "ghost" | "link" | null | undefined;
11
- size?: "default" | "xs" | "sm" | "lg" | "icon" | "icon-xs" | "icon-sm" | "icon-lg" | null | undefined;
11
+ variant?: "default" | "link" | "destructive" | "secondary" | "outline" | "ghost" | null | undefined;
12
+ size?: "default" | "sm" | "lg" | "xs" | "icon" | "icon-xs" | "icon-sm" | "icon-lg" | null | undefined;
12
13
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
13
14
  declare function Button({ className, variant, size, asChild, ...props }: React.ComponentProps<"button"> & VariantProps<typeof buttonVariants> & {
14
15
  asChild?: boolean;
package/dist/button.js CHANGED
@@ -4,20 +4,21 @@ import { cva } from "class-variance-authority";
4
4
  import { Slot } from "radix-ui";
5
5
  import { cn } from "./utils.js";
6
6
  /**
7
- * Outline and ghost hover/focus: brand purple border and text, optional
8
- * selected wash, no muted grey fill. Used by quiet actions such as
7
+ * Outline and ghost hover/focus: selected-foreground border, selected wash, and
8
+ * `selected-foreground` text (primary in light mode, a lighter purple that
9
+ * stays readable on dark), no muted grey fill. Used by quiet actions such as
9
10
  * "Clear selection" sitting on a selected row.
10
11
  */
11
- const quietButtonInteraction = "hover:border-primary hover:bg-selected hover:text-primary focus-visible:border-primary focus-visible:text-primary focus-visible:ring-primary/30 aria-expanded:border-primary aria-expanded:bg-selected aria-expanded:text-primary";
12
- const buttonVariants = cva("group/button inline-flex shrink-0 items-center justify-center rounded-none border border-transparent bg-clip-padding text-xs font-semibold tracking-widest whitespace-nowrap uppercase transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/30 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-2 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5", {
12
+ const quietButtonInteraction = "hover:border-selected-foreground hover:bg-selected hover:text-selected-foreground focus-visible:text-selected-foreground aria-expanded:border-selected-foreground aria-expanded:bg-selected aria-expanded:text-selected-foreground";
13
+ const buttonVariants = cva("group/button inline-flex shrink-0 items-center justify-center rounded-none border border-transparent bg-clip-padding text-xs font-semibold tracking-widest whitespace-nowrap uppercase transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-2 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5", {
13
14
  variants: {
14
15
  variant: {
15
16
  default: "bg-primary text-primary-foreground hover:bg-primary/80",
16
17
  outline: `border-border bg-transparent ${quietButtonInteraction}`,
17
18
  secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
18
19
  ghost: quietButtonInteraction,
19
- destructive: "bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
20
- link: "text-primary underline underline-offset-4 hover:underline",
20
+ destructive: "bg-destructive/10 text-destructive hover:bg-destructive/15 dark:bg-destructive/20 dark:hover:bg-destructive/30",
21
+ link: "text-selected-foreground underline underline-offset-4 hover:underline",
21
22
  },
22
23
  size: {
23
24
  default: "h-10 gap-1.5 px-6 has-data-[icon=inline-end]:pr-4 has-data-[icon=inline-start]:pl-4",
@@ -1,7 +1,7 @@
1
1
  import * as React from "react";
2
2
  import { DayPicker, type DayButton, type Locale } from "react-day-picker";
3
3
  import { Button } from "./button.js";
4
- declare function Calendar({ className, classNames, showOutsideDays, captionLayout, buttonVariant, locale, formatters, components, ...props }: React.ComponentProps<typeof DayPicker> & {
4
+ declare function Calendar({ className, classNames, showOutsideDays, captionLayout, buttonVariant, locale, formatters, labels, components, ...props }: React.ComponentProps<typeof DayPicker> & {
5
5
  buttonVariant?: React.ComponentProps<typeof Button>["variant"];
6
6
  }): React.JSX.Element;
7
7
  declare function CalendarDayButton({ className, day, modifiers, locale, ...props }: React.ComponentProps<typeof DayButton> & {
package/dist/calendar.js CHANGED
@@ -5,11 +5,29 @@ import { DayPicker, getDefaultClassNames, } from "react-day-picker";
5
5
  import { cn } from "./utils.js";
6
6
  import { Button, buttonVariants } from "./button.js";
7
7
  import { ChevronLeftIcon, ChevronRightIcon, ChevronDownIcon, } from "lucide-react";
8
- function Calendar({ className, classNames, showOutsideDays = true, captionLayout = "label", buttonVariant = "ghost", locale, formatters, components, ...props }) {
8
+ function Calendar({ className, classNames, showOutsideDays = true, captionLayout = "label", buttonVariant = "ghost", locale, formatters, labels, components, ...props }) {
9
9
  const defaultClassNames = getDefaultClassNames();
10
10
  return (_jsx(DayPicker, { showOutsideDays: showOutsideDays, className: cn("group/calendar bg-background p-3 [--cell-radius:0] [--cell-size:--spacing(8)] in-data-[slot=card-content]:bg-transparent in-data-[slot=popover-content]:bg-transparent", String.raw `rtl:**:[.rdp-button\_next>svg]:rotate-180`, String.raw `rtl:**:[.rdp-button\_previous>svg]:rotate-180`, className), captionLayout: captionLayout, locale: locale, formatters: {
11
11
  formatMonthDropdown: (date) => date.toLocaleString(locale?.code, { month: "short" }),
12
12
  ...formatters,
13
+ }, labels: {
14
+ // Day numbers without ordinals ("September 27", not "27th") so the
15
+ // accessible name contains the visible "27" (WCAG 2.5.3 Label in
16
+ // Name); speech users can then say "click 27".
17
+ labelDayButton: (date, modifiers) => {
18
+ let label = date.toLocaleDateString(locale?.code, {
19
+ weekday: "long",
20
+ year: "numeric",
21
+ month: "long",
22
+ day: "numeric",
23
+ });
24
+ if (modifiers.today)
25
+ label = `Today, ${label}`;
26
+ if (modifiers.selected)
27
+ label = `${label}, selected`;
28
+ return label;
29
+ },
30
+ ...labels,
13
31
  }, classNames: {
14
32
  root: cn("w-fit", defaultClassNames.root),
15
33
  months: cn("relative flex flex-col gap-4 md:flex-row", defaultClassNames.months),
@@ -71,6 +89,6 @@ function CalendarDayButton({ className, day, modifiers, locale, ...props }) {
71
89
  return (_jsx(Button, { ref: ref, variant: "ghost", size: "icon", "data-day": day.date.toLocaleDateString(locale?.code), "data-selected-single": modifiers.selected &&
72
90
  !modifiers.range_start &&
73
91
  !modifiers.range_end &&
74
- !modifiers.range_middle, "data-range-start": modifiers.range_start, "data-range-end": modifiers.range_end, "data-range-middle": modifiers.range_middle, className: cn("relative isolate z-10 flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 border-0 leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-[3px] group-data-[focused=true]/day:ring-ring/50 data-[range-end=true]:rounded-(--cell-radius) data-[range-end=true]:rounded-r-(--cell-radius) data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground data-[range-middle=true]:rounded-none data-[range-middle=true]:bg-selected data-[range-middle=true]:text-selected-foreground data-[range-start=true]:rounded-(--cell-radius) data-[range-start=true]:rounded-l-(--cell-radius) data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground dark:hover:text-foreground [&>span]:text-xs [&>span]:opacity-70", defaultClassNames.day, className), ...props }));
92
+ !modifiers.range_middle, "data-range-start": modifiers.range_start, "data-range-end": modifiers.range_end, "data-range-middle": modifiers.range_middle, className: cn("relative isolate z-10 flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 border-0 leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-2 group-data-[focused=true]/day:ring-ring data-[range-end=true]:rounded-(--cell-radius) data-[range-end=true]:rounded-r-(--cell-radius) data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground data-[range-middle=true]:rounded-none data-[range-middle=true]:bg-selected data-[range-middle=true]:text-selected-foreground data-[range-start=true]:rounded-(--cell-radius) data-[range-start=true]:rounded-l-(--cell-radius) data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground dark:hover:text-foreground [&>span]:text-xs [&>span]:opacity-70", defaultClassNames.day, className), ...props }));
75
93
  }
76
94
  export { Calendar, CalendarDayButton };
package/dist/checkbox.js CHANGED
@@ -5,6 +5,6 @@ import { Checkbox as CheckboxPrimitive } from "radix-ui";
5
5
  import { cn } from "./utils.js";
6
6
  import { CheckIcon } from "lucide-react";
7
7
  function Checkbox({ className, ...props }) {
8
- return (_jsx(CheckboxPrimitive.Root, { "data-slot": "checkbox", className: cn("peer relative flex size-4.5 shrink-0 items-center justify-center rounded-none border border-input bg-transparent transition-shadow outline-none group-has-disabled/field:opacity-50 after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/30 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-2 aria-invalid:ring-destructive/20 aria-invalid:aria-checked:border-primary dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-checked:border-primary data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary", className), ...props, children: _jsx(CheckboxPrimitive.Indicator, { "data-slot": "checkbox-indicator", className: "grid place-content-center text-current transition-none [&>svg]:size-3.5", children: _jsx(CheckIcon, {}) }) }));
8
+ return (_jsx(CheckboxPrimitive.Root, { "data-slot": "checkbox", className: cn("peer relative flex size-4.5 shrink-0 items-center justify-center rounded-none border border-input bg-transparent transition-shadow outline-none group-has-disabled/field:opacity-50 after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-2 aria-invalid:ring-destructive/20 aria-invalid:aria-checked:border-primary dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-checked:border-primary data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary", className), ...props, children: _jsx(CheckboxPrimitive.Indicator, { "data-slot": "checkbox-indicator", className: "grid place-content-center text-current transition-none [&>svg]:size-3.5", children: _jsx(CheckIcon, {}) }) }));
9
9
  }
10
10
  export { Checkbox };
@@ -0,0 +1,22 @@
1
+ import type * as React from "react";
2
+ export type ColorLegendItem = {
3
+ /** CSS color from the app (map bins, status scales). The kit does not own it. */
4
+ color: string;
5
+ range?: string;
6
+ label: string;
7
+ };
8
+ export type ColorLegendProps = {
9
+ title?: string;
10
+ items: ColorLegendItem[];
11
+ caption?: React.ReactNode;
12
+ children?: React.ReactNode;
13
+ "aria-label"?: string;
14
+ className?: string;
15
+ };
16
+ /**
17
+ * Key for a color scale: swatch, optional range, and label. Overlay chrome
18
+ * (absolute position, card fill) is the caller's `className`. Attribution
19
+ * and license lines stay in the app.
20
+ */
21
+ declare function ColorLegend({ title, items, caption, children, "aria-label": ariaLabel, className, }: ColorLegendProps): React.JSX.Element;
22
+ export { ColorLegend };
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cn } from "./utils.js";
3
+ /**
4
+ * Key for a color scale: swatch, optional range, and label. Overlay chrome
5
+ * (absolute position, card fill) is the caller's `className`. Attribution
6
+ * and license lines stay in the app.
7
+ */
8
+ function ColorLegend({ title, items, caption, children, "aria-label": ariaLabel, className, }) {
9
+ return (_jsxs("figure", { "data-slot": "color-legend", "aria-label": ariaLabel, className: cn("flex flex-col gap-2 text-sm", className), children: [title ? (_jsx("figcaption", { className: "font-heading text-xs font-semibold tracking-widest text-muted-foreground uppercase", children: title })) : null, _jsx("ul", { className: "flex flex-col gap-1.5", children: items.map((item) => (_jsxs("li", { className: "flex items-center gap-2", children: [_jsx("span", { "aria-hidden": true, className: "size-3.5 shrink-0 border border-input", style: { backgroundColor: item.color } }), item.range ? (_jsx("span", { className: "w-16 shrink-0 tabular-nums text-muted-foreground", children: item.range })) : null, _jsx("span", { children: item.label })] }, `${item.color}-${item.range ?? ""}-${item.label}`))) }), caption ? (_jsx("p", { className: "text-xs text-muted-foreground", children: caption })) : null, children] }));
10
+ }
11
+ export { ColorLegend };
@@ -14,6 +14,9 @@ type ComboboxProps = {
14
14
  id?: string;
15
15
  className?: string;
16
16
  "aria-invalid"?: boolean;
17
+ /** Name the trigger when there is no `<Label htmlFor={id}>`. */
18
+ "aria-label"?: string;
19
+ "aria-labelledby"?: string;
17
20
  };
18
21
  /**
19
22
  * Searchable single-select built from Button, Popover, and Input.
@@ -22,5 +25,5 @@ type ComboboxProps = {
22
25
  * Keyboard highlight (arrows, Home/End, PageUp/PageDown) scrolls the active
23
26
  * option into view inside the overflow list.
24
27
  */
25
- export declare function Combobox({ options, value, onValueChange, placeholder, searchPlaceholder, emptyText, disabled, id, className, "aria-invalid": ariaInvalid, }: ComboboxProps): React.JSX.Element;
28
+ export declare function Combobox({ options, value, onValueChange, placeholder, searchPlaceholder, emptyText, disabled, id, className, "aria-invalid": ariaInvalid, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, }: ComboboxProps): React.JSX.Element;
26
29
  export {};
package/dist/combobox.js CHANGED
@@ -20,7 +20,7 @@ const LISTBOX_PAGE_SIZE = 10;
20
20
  * Keyboard highlight (arrows, Home/End, PageUp/PageDown) scrolls the active
21
21
  * option into view inside the overflow list.
22
22
  */
23
- export function Combobox({ options, value, onValueChange, placeholder = "Select", searchPlaceholder = "Search", emptyText = "No results", disabled, id, className, "aria-invalid": ariaInvalid, }) {
23
+ export function Combobox({ options, value, onValueChange, placeholder = "Select", searchPlaceholder = "Search", emptyText = "No results", disabled, id, className, "aria-invalid": ariaInvalid, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, }) {
24
24
  const [open, setOpen] = React.useState(false);
25
25
  const [query, setQuery] = React.useState("");
26
26
  const [highlight, setHighlight] = React.useState(0);
@@ -61,10 +61,17 @@ export function Combobox({ options, value, onValueChange, placeholder = "Select"
61
61
  setQuery(next);
62
62
  setHighlight(0);
63
63
  });
64
+ // Closing from here does not go through Radix's onOpenChange, so clear the
65
+ // query too. A stale query would filter the next open while the highlight
66
+ // is computed against the full list, and Enter would pick the wrong option.
64
67
  const selectValue = (next) => {
65
68
  onValueChange(next);
66
69
  setOpen(false);
70
+ setQuery("");
67
71
  };
72
+ // Index-based ids: option values may contain spaces, which are not valid
73
+ // in an id or an aria-activedescendant reference.
74
+ const optionId = (index) => `${listId}-option-${index}`;
68
75
  const activeOption = filtered[highlight];
69
76
  const onSearchKeyDown = (event) => {
70
77
  if (event.key === "ArrowDown") {
@@ -102,11 +109,10 @@ export function Combobox({ options, value, onValueChange, placeholder = "Select"
102
109
  setHighlight((index) => Math.max(index - LISTBOX_PAGE_SIZE, 0));
103
110
  }
104
111
  };
105
- return (_jsxs(Popover, { open: open, onOpenChange: onOpenChange, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { id: id, type: "button", variant: "outline", role: "combobox", "aria-expanded": open, "aria-controls": listId, "aria-invalid": ariaInvalid, disabled: disabled, className: cn(formControlTriggerClassName, "justify-between", !selected && "text-muted-foreground", className), children: [_jsx("span", { className: "truncate", children: selected?.label ?? placeholder }), _jsx(ChevronDownIcon, { "data-icon": "inline-end", className: "opacity-50" })] }) }), _jsxs(PopoverContent, { align: "start", className: "w-[var(--radix-popover-trigger-width)] gap-0 rounded-none p-0", children: [_jsx("div", { className: "border-b p-2", children: _jsx(Input, { ref: inputRef, value: query, onChange: onQueryChange, onKeyDown: onSearchKeyDown, placeholder: searchPlaceholder, "aria-autocomplete": "list", "aria-controls": listId, "aria-activedescendant": activeOption
106
- ? `${listId}-option-${activeOption.value}`
107
- : undefined }) }), _jsx("ul", { id: listId, role: "listbox", className: "max-h-60 overflow-y-auto p-1", children: filtered.length === 0 ? (_jsx("li", { className: "px-3 py-4 text-center text-sm text-muted-foreground", children: emptyText })) : (filtered.map((option, index) => {
112
+ return (_jsxs(Popover, { open: open, onOpenChange: onOpenChange, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { id: id, type: "button", variant: "outline", role: "combobox", "aria-expanded": open, "aria-controls": listId, "aria-invalid": ariaInvalid, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, disabled: disabled, className: cn(formControlTriggerClassName, "justify-between", !selected && "text-muted-foreground", className), children: [_jsx("span", { className: "truncate", children: selected?.label ?? placeholder }), _jsx(ChevronDownIcon, { "data-icon": "inline-end", className: "opacity-50" })] }) }), _jsxs(PopoverContent, { align: "start", className: "w-[var(--radix-popover-trigger-width)] gap-0 rounded-none p-0", children: [_jsx("div", { className: "border-b p-2", children: _jsx(Input, { ref: inputRef, value: query, onChange: onQueryChange, onKeyDown: onSearchKeyDown, placeholder: searchPlaceholder, "aria-autocomplete": "list", "aria-controls": listId, "aria-activedescendant": activeOption ? optionId(highlight) : undefined }) }), _jsx("ul", { id: listId, role: "listbox", className: "max-h-60 overflow-y-auto p-1", children: filtered.length === 0 ? (_jsx("li", { className: "px-3 py-4 text-center text-sm text-muted-foreground", children: emptyText })) : (filtered.map((option, index) => {
108
113
  const isSelected = option.value === value;
109
114
  const isActive = index === highlight;
110
- return (_jsx("li", { role: "none", children: _jsxs("button", { ref: isActive ? scrollHighlightedOption : undefined, id: `${listId}-option-${option.value}`, type: "button", role: "option", "aria-selected": isSelected, className: cn("relative flex w-full cursor-default items-center gap-2 rounded-none py-2 pr-8 pl-3 text-left text-sm outline-hidden select-none", isSelected && "bg-selected text-selected-foreground", isActive && "bg-accent text-accent-foreground"), onMouseEnter: () => setHighlight(index), onClick: () => selectValue(option.value), children: [_jsx("span", { className: "truncate", children: option.label }), isSelected ? (_jsx(CheckIcon, { className: "pointer-events-none absolute right-2 size-3.5" })) : null] }) }, option.value));
115
+ return (_jsx("li", { role: "none", children: _jsxs("button", { ref: isActive ? scrollHighlightedOption : undefined, id: optionId(index), type: "button", role: "option", "aria-selected": isSelected, className: cn("relative flex w-full cursor-default items-center gap-2 rounded-none py-2 pr-8 pl-3 text-left text-sm outline-hidden select-none", isSelected && "bg-selected text-selected-foreground", isActive &&
116
+ "bg-accent text-accent-foreground shadow-[inset_2px_0_0_var(--color-ring)]"), onMouseEnter: () => setHighlight(index), onClick: () => selectValue(option.value), children: [_jsx("span", { className: "truncate", children: option.label }), isSelected ? (_jsx(CheckIcon, { className: "pointer-events-none absolute right-2 size-3.5" })) : null] }) }, option.value));
111
117
  })) })] })] }));
112
118
  }
@@ -77,11 +77,22 @@ function DatePickerRangeButton({ id, value, onChange, placeholder = "Pick a date
77
77
  const from = toDate(value?.from);
78
78
  const to = toDate(value?.to);
79
79
  const selected = from || to ? { from, to } : undefined;
80
- return (_jsxs(DatePickerShell, { id: id, disabled: disabled, ariaInvalid: ariaInvalid, className: className, empty: !from, label: formatRangeTriggerLabel(from, to, placeholder), open: open, onOpenChange: setOpen, side: side, children: [_jsx(Calendar, { mode: "range", selected: selected, defaultMonth: defaultMonth ?? from ?? to, numberOfMonths: months, onSelect: (next) => {
81
- onChange(next);
80
+ // Opening on a complete range means the next click starts a new one.
81
+ // Without this, Day Picker extends the old range and the popover closes
82
+ // on that first click, so a fresh range needs Clear first.
83
+ const restartRef = React.useRef(false);
84
+ const onOpenChange = (next) => {
85
+ if (next)
86
+ restartRef.current = Boolean(from && to);
87
+ setOpen(next);
88
+ };
89
+ return (_jsxs(DatePickerShell, { id: id, disabled: disabled, ariaInvalid: ariaInvalid, className: className, empty: !from, label: formatRangeTriggerLabel(from, to, placeholder), open: open, onOpenChange: onOpenChange, side: side, children: [_jsx(Calendar, { mode: "range", selected: selected, defaultMonth: defaultMonth ?? from ?? to, numberOfMonths: months, onSelect: (next, day) => {
90
+ const range = restartRef.current ? { from: day, to: day } : next;
91
+ restartRef.current = false;
92
+ onChange(range);
82
93
  // First click is a same-day range; keep the popover open so the
83
94
  // user can set `to`. Close once the ends differ.
84
- if (next?.from && next.to && !isSameDay(next.from, next.to)) {
95
+ if (range?.from && range.to && !isSameDay(range.from, range.to)) {
85
96
  setOpen(false);
86
97
  }
87
98
  } }), _jsx("div", { className: "flex justify-end border-t p-2", children: _jsx(Button, { type: "button", variant: "ghost", size: "sm", disabled: !from && !to, onClick: () => {
@@ -0,0 +1,36 @@
1
+ import * as React from "react";
2
+ import { PopoverContent } from "./popover.js";
3
+ /** Default clock increment, in minutes. Must divide 60. */
4
+ export declare const DEFAULT_TIME_STEP = 15;
5
+ export type DateTimePickerButtonProps = {
6
+ /** Controlled instant. `null` or omitted is empty. */
7
+ value?: Date | null;
8
+ onChange: (date: Date | null) => void;
9
+ /**
10
+ * Minute increment for the time selects (1, 5, 15, 30, or 60).
11
+ * Values that do not divide 60 fall back to 15 so the list stays even.
12
+ */
13
+ timeStep?: number;
14
+ id?: string;
15
+ placeholder?: string;
16
+ disabled?: boolean;
17
+ className?: string;
18
+ "aria-invalid"?: boolean;
19
+ /** Popover side. Defaults to `bottom`; Radix still flips on collision. */
20
+ side?: React.ComponentProps<typeof PopoverContent>["side"];
21
+ /** Month shown when the popover opens; falls back to the selected date. */
22
+ defaultMonth?: Date;
23
+ };
24
+ /**
25
+ * Normalize a minute step so option lists stay even (1, 5, 15, 30, 60).
26
+ * Invalid values fall back to 15 so a bad prop cannot empty the minute list.
27
+ */
28
+ export declare function resolveTimeStep(step: number | undefined): number;
29
+ /**
30
+ * Calendar plus hour/minute popover trigger styled as a form control (same
31
+ * chrome as Input, SelectTrigger, and DatePickerButton). `value` is a `Date`
32
+ * or `null`. Picking a day keeps the popover open so the time can be set;
33
+ * Clear emits `null`. One month only, clamped to the viewport, so the panel
34
+ * stays on-screen at a 390px width.
35
+ */
36
+ export declare function DateTimePickerButton({ id, value, onChange, timeStep, placeholder, disabled, className, "aria-invalid": ariaInvalid, side, defaultMonth, }: DateTimePickerButtonProps): React.JSX.Element;
@@ -0,0 +1,105 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { format } from "date-fns";
5
+ import { CalendarIcon } from "lucide-react";
6
+ import { Button } from "./button.js";
7
+ import { Calendar } from "./calendar.js";
8
+ import { formControlTriggerClassName } from "./internal/form-control.js";
9
+ import { Popover, PopoverContent, PopoverTrigger } from "./popover.js";
10
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "./select.js";
11
+ import { cn } from "./utils.js";
12
+ /** Default clock increment, in minutes. Must divide 60. */
13
+ export const DEFAULT_TIME_STEP = 15;
14
+ /** Gutter used when the calendar popover collides with the viewport. */
15
+ const DATE_TIME_PICKER_COLLISION_PADDING = 16;
16
+ /**
17
+ * Normalize a minute step so option lists stay even (1, 5, 15, 30, 60).
18
+ * Invalid values fall back to 15 so a bad prop cannot empty the minute list.
19
+ */
20
+ export function resolveTimeStep(step) {
21
+ if (step == null)
22
+ return DEFAULT_TIME_STEP;
23
+ if (!Number.isInteger(step) || step <= 0 || step > 60 || 60 % step !== 0) {
24
+ return DEFAULT_TIME_STEP;
25
+ }
26
+ return step;
27
+ }
28
+ /** Two-digit clock fragment (`00`–`23` / `00`–`59`). */
29
+ function padTimePart(value) {
30
+ return String(value).padStart(2, "0");
31
+ }
32
+ /**
33
+ * Build a local Date from a calendar day and a clock time.
34
+ * Seconds and milliseconds stay zero so a stepped minute remains exact.
35
+ */
36
+ function combineDateAndTime(day, hours, minutes) {
37
+ const next = new Date(day);
38
+ next.setHours(hours, minutes, 0, 0);
39
+ return next;
40
+ }
41
+ /**
42
+ * Minute options for `timeStep`, plus the current minute when it is off-step
43
+ * so a loaded value such as 14:07 still appears in the select.
44
+ */
45
+ function minuteOptions(step, current) {
46
+ const minutes = [];
47
+ for (let minute = 0; minute < 60; minute += step) {
48
+ minutes.push(minute);
49
+ }
50
+ if (current != null && !minutes.includes(current)) {
51
+ minutes.push(current);
52
+ minutes.sort((a, b) => a - b);
53
+ }
54
+ return minutes;
55
+ }
56
+ const HOUR_OPTIONS = Array.from({ length: 24 }, (_, hour) => padTimePart(hour));
57
+ /**
58
+ * Keep the date-time popover open when a portaled Select option is clicked.
59
+ * Select content renders outside the popover, so Radix would otherwise treat
60
+ * that pointer down as an outside dismiss.
61
+ */
62
+ function preventDismissForPortaledSelect(event) {
63
+ const target = event.target;
64
+ if (target instanceof Element &&
65
+ target.closest("[data-slot=select-content]")) {
66
+ event.preventDefault();
67
+ }
68
+ }
69
+ /**
70
+ * Calendar plus hour/minute popover trigger styled as a form control (same
71
+ * chrome as Input, SelectTrigger, and DatePickerButton). `value` is a `Date`
72
+ * or `null`. Picking a day keeps the popover open so the time can be set;
73
+ * Clear emits `null`. One month only, clamped to the viewport, so the panel
74
+ * stays on-screen at a 390px width.
75
+ */
76
+ export function DateTimePickerButton({ id, value, onChange, timeStep, placeholder = "Pick a date and time", disabled, className, "aria-invalid": ariaInvalid, side, defaultMonth, }) {
77
+ const [open, setOpen] = React.useState(false);
78
+ const date = value ?? null;
79
+ const step = resolveTimeStep(timeStep);
80
+ const empty = date == null;
81
+ const label = date ? format(date, "PPP p") : placeholder;
82
+ const hourId = React.useId();
83
+ const minuteId = React.useId();
84
+ return (_jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { id: id, type: "button", variant: "outline", disabled: disabled, "aria-invalid": ariaInvalid, "data-empty": empty, className: cn(formControlTriggerClassName, "justify-start data-[empty=true]:text-muted-foreground", className), children: [_jsx(CalendarIcon, { "data-icon": "inline-start" }), label] }) }), _jsxs(PopoverContent, { align: "start", side: side, collisionPadding: DATE_TIME_PICKER_COLLISION_PADDING, onInteractOutside: preventDismissForPortaledSelect, className: "w-auto min-w-0 max-w-[min(100vw-2rem,24rem)] max-h-(--radix-popover-content-available-height) overflow-y-auto rounded-none p-0", children: [_jsx(Calendar, { mode: "single", selected: date ?? undefined, defaultMonth: defaultMonth ?? date ?? undefined, onSelect: (next) => {
85
+ // Re-clicking a selected day would clear it; require Clear instead
86
+ // so a time already set is not dropped by an accidental second tap.
87
+ if (!next)
88
+ return;
89
+ onChange(combineDateAndTime(next, date?.getHours() ?? 0, date?.getMinutes() ?? 0));
90
+ } }), _jsxs("div", { role: "group", "aria-label": "Time", className: "flex min-w-0 flex-col gap-2 border-t p-3", children: [_jsx("p", { className: "text-xs font-semibold tracking-wide uppercase", children: "Time" }), _jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [_jsxs(Select, { value: date ? padTimePart(date.getHours()) : undefined, onValueChange: (nextHour) => {
91
+ if (!date)
92
+ return;
93
+ onChange(combineDateAndTime(date, Number(nextHour), date.getMinutes()));
94
+ }, disabled: empty, children: [_jsx(SelectTrigger, { id: hourId, "aria-label": "Hour", className: "min-w-0 flex-1", children: _jsx(SelectValue, { placeholder: "\u2013\u2013" }) }), _jsx(SelectContent, { children: HOUR_OPTIONS.map((hour) => (_jsx(SelectItem, { value: hour, children: hour }, hour))) })] }), _jsx("span", { className: "text-muted-foreground", "aria-hidden": "true", children: ":" }), _jsxs(Select, { value: date ? padTimePart(date.getMinutes()) : undefined, onValueChange: (nextMinute) => {
95
+ if (!date)
96
+ return;
97
+ onChange(combineDateAndTime(date, date.getHours(), Number(nextMinute)));
98
+ }, disabled: empty, children: [_jsx(SelectTrigger, { id: minuteId, "aria-label": "Minute", className: "min-w-0 flex-1", children: _jsx(SelectValue, { placeholder: "\u2013\u2013" }) }), _jsx(SelectContent, { children: minuteOptions(step, date?.getMinutes()).map((minute) => {
99
+ const value = padTimePart(minute);
100
+ return (_jsx(SelectItem, { value: value, children: value }, value));
101
+ }) })] })] })] }), _jsx("div", { className: "flex justify-end border-t p-2", children: _jsx(Button, { type: "button", variant: "ghost", size: "sm", disabled: empty, onClick: () => {
102
+ onChange(null);
103
+ setOpen(false);
104
+ }, children: "Clear" }) })] })] }));
105
+ }
@@ -0,0 +1,26 @@
1
+ import * as React from "react";
2
+ import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui";
3
+ /** Action menu for commands (row actions, overflow menus). Use `Select` to pick a value. */
4
+ declare function DropdownMenu({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Root>): React.JSX.Element;
5
+ declare function DropdownMenuPortal({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>): React.JSX.Element;
6
+ declare function DropdownMenuTrigger({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>): React.JSX.Element;
7
+ declare function DropdownMenuContent({ className, align, sideOffset, collisionPadding, ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Content>): React.JSX.Element;
8
+ declare function DropdownMenuGroup({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Group>): React.JSX.Element;
9
+ declare function DropdownMenuItem({ className, inset, variant, ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
10
+ inset?: boolean;
11
+ variant?: "default" | "destructive";
12
+ }): React.JSX.Element;
13
+ declare function DropdownMenuCheckboxItem({ className, children, checked, ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>): React.JSX.Element;
14
+ declare function DropdownMenuRadioGroup({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>): React.JSX.Element;
15
+ declare function DropdownMenuRadioItem({ className, children, ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>): React.JSX.Element;
16
+ declare function DropdownMenuLabel({ className, inset, ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
17
+ inset?: boolean;
18
+ }): React.JSX.Element;
19
+ declare function DropdownMenuSeparator({ className, ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>): React.JSX.Element;
20
+ declare function DropdownMenuShortcut({ className, ...props }: React.ComponentProps<"span">): React.JSX.Element;
21
+ declare function DropdownMenuSub({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>): React.JSX.Element;
22
+ declare function DropdownMenuSubTrigger({ className, inset, children, ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
23
+ inset?: boolean;
24
+ }): React.JSX.Element;
25
+ declare function DropdownMenuSubContent({ className, ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>): React.JSX.Element;
26
+ export { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, };
@@ -0,0 +1,56 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui";
5
+ import { CheckIcon, ChevronRightIcon } from "lucide-react";
6
+ import { cn } from "./utils.js";
7
+ /** Item highlight and selected chrome shared with `SelectItem`. */
8
+ const menuItemClassName = "relative flex cursor-default items-center gap-2.5 rounded-none px-3 py-2 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:shadow-[inset_2px_0_0_var(--color-ring)] data-disabled:pointer-events-none data-disabled:opacity-50 data-inset:pl-9 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5";
9
+ const menuContentClassName = "z-50 min-w-40 overflow-x-hidden overflow-y-auto rounded-none bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95";
10
+ /** Action menu for commands (row actions, overflow menus). Use `Select` to pick a value. */
11
+ function DropdownMenu({ ...props }) {
12
+ return _jsx(DropdownMenuPrimitive.Root, { "data-slot": "dropdown-menu", ...props });
13
+ }
14
+ function DropdownMenuPortal({ ...props }) {
15
+ return (_jsx(DropdownMenuPrimitive.Portal, { "data-slot": "dropdown-menu-portal", ...props }));
16
+ }
17
+ function DropdownMenuTrigger({ ...props }) {
18
+ return (_jsx(DropdownMenuPrimitive.Trigger, { "data-slot": "dropdown-menu-trigger", ...props }));
19
+ }
20
+ function DropdownMenuContent({ className, align = "start", sideOffset = 4, collisionPadding = 8, ...props }) {
21
+ return (_jsx(DropdownMenuPrimitive.Portal, { children: _jsx(DropdownMenuPrimitive.Content, { "data-slot": "dropdown-menu-content", align: align, sideOffset: sideOffset, collisionPadding: collisionPadding, className: cn(menuContentClassName, "max-h-(--radix-dropdown-menu-content-available-height) origin-(--radix-dropdown-menu-content-transform-origin)", className), ...props }) }));
22
+ }
23
+ function DropdownMenuGroup({ ...props }) {
24
+ return (_jsx(DropdownMenuPrimitive.Group, { "data-slot": "dropdown-menu-group", ...props }));
25
+ }
26
+ function DropdownMenuItem({ className, inset, variant = "default", ...props }) {
27
+ return (_jsx(DropdownMenuPrimitive.Item, { "data-slot": "dropdown-menu-item", "data-inset": inset || undefined, "data-variant": variant, className: cn(menuItemClassName, "data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive", className), ...props }));
28
+ }
29
+ function DropdownMenuCheckboxItem({ className, children, checked, ...props }) {
30
+ return (_jsxs(DropdownMenuPrimitive.CheckboxItem, { "data-slot": "dropdown-menu-checkbox-item", className: cn(menuItemClassName, "pl-9", className), checked: checked, ...props, children: [_jsx("span", { className: "pointer-events-none absolute left-3 flex size-3.5 items-center justify-center", children: _jsx(DropdownMenuPrimitive.ItemIndicator, { children: _jsx(CheckIcon, {}) }) }), children] }));
31
+ }
32
+ function DropdownMenuRadioGroup({ ...props }) {
33
+ return (_jsx(DropdownMenuPrimitive.RadioGroup, { "data-slot": "dropdown-menu-radio-group", ...props }));
34
+ }
35
+ function DropdownMenuRadioItem({ className, children, ...props }) {
36
+ return (_jsxs(DropdownMenuPrimitive.RadioItem, { "data-slot": "dropdown-menu-radio-item", className: cn(menuItemClassName, "pl-9", className), ...props, children: [_jsx("span", { className: "pointer-events-none absolute left-3 flex size-3.5 items-center justify-center", children: _jsx(DropdownMenuPrimitive.ItemIndicator, { children: _jsx("span", { className: "block size-1.5 rounded-full bg-current" }) }) }), children] }));
37
+ }
38
+ function DropdownMenuLabel({ className, inset, ...props }) {
39
+ return (_jsx(DropdownMenuPrimitive.Label, { "data-slot": "dropdown-menu-label", "data-inset": inset || undefined, className: cn("px-3 py-2 text-xs font-semibold tracking-wider text-muted-foreground uppercase data-inset:pl-9", className), ...props }));
40
+ }
41
+ function DropdownMenuSeparator({ className, ...props }) {
42
+ return (_jsx(DropdownMenuPrimitive.Separator, { "data-slot": "dropdown-menu-separator", className: cn("-mx-1 my-1 h-px bg-border", className), ...props }));
43
+ }
44
+ function DropdownMenuShortcut({ className, ...props }) {
45
+ return (_jsx("span", { "data-slot": "dropdown-menu-shortcut", className: cn("ml-auto text-xs tracking-widest text-muted-foreground", className), ...props }));
46
+ }
47
+ function DropdownMenuSub({ ...props }) {
48
+ return _jsx(DropdownMenuPrimitive.Sub, { "data-slot": "dropdown-menu-sub", ...props });
49
+ }
50
+ function DropdownMenuSubTrigger({ className, inset, children, ...props }) {
51
+ return (_jsxs(DropdownMenuPrimitive.SubTrigger, { "data-slot": "dropdown-menu-sub-trigger", "data-inset": inset || undefined, className: cn(menuItemClassName, "data-open:bg-accent data-open:text-accent-foreground", className), ...props, children: [children, _jsx(ChevronRightIcon, { className: "ml-auto" })] }));
52
+ }
53
+ function DropdownMenuSubContent({ className, ...props }) {
54
+ return (_jsx(DropdownMenuPrimitive.SubContent, { "data-slot": "dropdown-menu-sub-content", className: cn(menuContentClassName, "origin-(--radix-dropdown-menu-content-transform-origin)", className), ...props }));
55
+ }
56
+ export { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, };
@@ -29,6 +29,6 @@ export declare class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBo
29
29
  componentDidCatch(error: Error, info: ErrorInfo): void;
30
30
  retry: () => void;
31
31
  reload: () => void;
32
- render(): string | number | bigint | boolean | import("react").JSX.Element | Iterable<ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | null | undefined;
32
+ render(): string | number | bigint | boolean | Iterable<ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | import("react").JSX.Element | null | undefined;
33
33
  }
34
34
  export {};
package/dist/field.js CHANGED
@@ -39,7 +39,7 @@ function FieldTitle({ className, ...props }) {
39
39
  return (_jsx("div", { "data-slot": "field-label", className: cn("flex w-fit items-center gap-2 text-xs font-semibold uppercase group-data-[disabled=true]/field:opacity-50 in-data-[slot=field-label]:font-semibold", className), ...props }));
40
40
  }
41
41
  function FieldDescription({ className, ...props }) {
42
- return (_jsx("p", { "data-slot": "field-description", className: cn("text-left text-sm leading-normal font-normal tracking-normal text-muted-foreground normal-case group-has-data-horizontal/field:text-balance [[data-variant=legend]+&]:-mt-1.5", "last:mt-0 nth-last-2:-mt-1", "[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary", className), ...props }));
42
+ return (_jsx("p", { "data-slot": "field-description", className: cn("text-left text-sm leading-normal font-normal tracking-normal text-muted-foreground normal-case group-has-data-horizontal/field:text-balance [[data-variant=legend]+&]:-mt-1.5", "last:mt-0 nth-last-2:-mt-1", "[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-selected-foreground", className), ...props }));
43
43
  }
44
44
  function FieldSeparator({ children, className, ...props }) {
45
45
  return (_jsxs("div", { "data-slot": "field-separator", "data-content": !!children, className: cn("relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2", className), ...props, children: [_jsx(Separator, { className: "absolute inset-0 top-1/2" }), children && (_jsx("span", { className: "relative mx-auto block w-fit bg-background px-2 text-muted-foreground", "data-slot": "field-separator-content", children: children }))] }));
@@ -0,0 +1,16 @@
1
+ import { type FieldMetaState } from "./field-error.js";
2
+ type Props = {
3
+ value: Date | null;
4
+ onValueChange: (date: Date | null) => void;
5
+ label: string;
6
+ meta?: FieldMetaState;
7
+ placeholder?: string;
8
+ disabled?: boolean;
9
+ /** Minute increment forwarded to the picker. */
10
+ timeStep?: number;
11
+ /** Forwarded to the date-time picker trigger. */
12
+ className?: string;
13
+ };
14
+ /** Date-time picker with built-in field error text and invalid styling. */
15
+ export declare function FormDateTimePickerField({ value, onValueChange, label, meta, placeholder, disabled, timeStep, className, }: Props): import("react").JSX.Element;
16
+ export {};
@@ -0,0 +1,12 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useId } from "react";
4
+ import { DateTimePickerButton } from "../date-time-picker.js";
5
+ import { Label } from "../label.js";
6
+ import { FieldError, getFieldErrorMessage, } from "./field-error.js";
7
+ /** Date-time picker with built-in field error text and invalid styling. */
8
+ export function FormDateTimePickerField({ value, onValueChange, label, meta, placeholder, disabled, timeStep, className, }) {
9
+ const errorText = meta ? getFieldErrorMessage(meta.errors[0]) : undefined;
10
+ const id = useId();
11
+ return (_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { htmlFor: id, children: label }), _jsx(DateTimePickerButton, { id: id, value: value, onChange: onValueChange, placeholder: placeholder, disabled: disabled, timeStep: timeStep, className: className, "aria-invalid": errorText ? true : undefined }), _jsx(FieldError, { text: errorText })] }));
12
+ }