atlas-react-kit 0.9.4 → 0.10.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/COMPONENTS.md CHANGED
@@ -137,8 +137,8 @@ action. Pass `side` to prefer a placement; Radix still flips on collision.
137
137
  form-control chrome. The popover is one month, a labelled Hour and Minute
138
138
  pair (24-hour, stepped by `timeStep`, default 15), and Clear. Picking a day
139
139
  keeps the popover open so the time can be set; the time selects stay
140
- disabled until a day is chosen. The panel is `w-auto`, viewport-clamped, and
141
- capped at `min(100vw - 2rem, 24rem)` so it stays on-screen at 390px. Pass
140
+ disabled until a day is chosen. The panel is `w-auto`, sized by the
141
+ calendar, and clamped to the viewport so it stays on-screen at 390px. Pass
142
142
  `side` to prefer a placement. `FormDateTimePickerField` forwards `className`
143
143
  and `timeStep` to that control.
144
144
 
package/DESIGN_RULES.md CHANGED
@@ -107,6 +107,20 @@ column, and `min-w-0` so nested tables scroll instead of widening the card.
107
107
  `Dialog` already scrolls inside the viewport on small screens; avoid
108
108
  additional height limits.
109
109
 
110
+ Apps decide how wide and tall containers and overlays are. The kit sets
111
+ intrinsic control sizes (the button, input, tab and row heights, icons,
112
+ checkboxes, switches, avatars, slider thumbs, and track thickness) and
113
+ clamps overlays to the viewport, but no design widths or heights on
114
+ dialogs, popovers, menus, or lists. Set them with `className`: give each
115
+ `DialogContent` a width such as `sm:max-w-md`, and each `PopoverContent`
116
+ one such as `w-72`. Where the sized part is not the element `className`
117
+ reaches, a prop names it: `InfoTip` takes `contentClassName` (default
118
+ `w-72`), and `Combobox` and `SearchPicker` take `listClassName` (default
119
+ `max-h-60`). `TabsNav` fills a block container but sizes to its links in a
120
+ flex row; add `w-full` to stretch it there. `Tooltip` keeps a `max-w-xs` reading width, `Textarea` a
121
+ `min-h-16` starting height, and toasts their 356px width; override the first
122
+ two with `className`.
123
+
110
124
  `Input`, `SelectTrigger`, `DatePickerButton`, `DateTimePickerButton`, and
111
125
  `Combobox` share form-control chrome: 40px height, `px-3`,
112
126
  `text-base md:text-sm`, `border-input`, and `shadow-xs`. Do not restyle those
package/README.md CHANGED
@@ -8,7 +8,7 @@ React 19, Tailwind 4, and Node >=20.19 are required. Next 16 is needed only
8
8
  when using `next/tabs-nav-link`.
9
9
 
10
10
  ```sh
11
- npm install --save-exact atlas-react-kit@0.9.4
11
+ npm install --save-exact atlas-react-kit@0.10.0
12
12
  ```
13
13
 
14
14
  Import components by subpath:
@@ -128,7 +128,7 @@ artifact with the package owner's npm login:
128
128
  ```sh
129
129
  mkdir -p release-artifacts
130
130
  npm pack --pack-destination release-artifacts
131
- npm publish ./release-artifacts/atlas-react-kit-0.9.4.tgz --access public
131
+ npm publish ./release-artifacts/atlas-react-kit-0.10.0.tgz --access public
132
132
  ```
133
133
 
134
134
  Pin an exact package version in consuming applications. Roll back an upgrade by
@@ -12,7 +12,10 @@ type ComboboxProps = {
12
12
  emptyText?: string;
13
13
  disabled?: boolean;
14
14
  id?: string;
15
+ /** Classes for the trigger, e.g. its width; the list matches it. */
15
16
  className?: string;
17
+ /** Classes for the listbox, e.g. its height. Defaults to `max-h-60`. */
18
+ listClassName?: string;
16
19
  "aria-invalid"?: boolean;
17
20
  /** Name the trigger when there is no `<Label htmlFor={id}>`. */
18
21
  "aria-label"?: string;
@@ -43,5 +46,5 @@ type ComboboxProps = {
43
46
  * Opt-in `creatable` appends a create action when the query is not already
44
47
  * an option; choosing it calls `onValueChange` like a normal selection.
45
48
  */
46
- export declare function Combobox({ options, value, onValueChange, placeholder, searchPlaceholder, emptyText, disabled, id, className, "aria-invalid": ariaInvalid, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, creatable, normalizeCreateValue, createLabel, }: ComboboxProps): React.JSX.Element;
49
+ export declare function Combobox({ options, value, onValueChange, placeholder, searchPlaceholder, emptyText, disabled, id, className, listClassName, "aria-invalid": ariaInvalid, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, creatable, normalizeCreateValue, createLabel, }: ComboboxProps): React.JSX.Element;
47
50
  export {};
package/dist/combobox.js CHANGED
@@ -49,7 +49,7 @@ function resolveCreateValue(query, options, normalize) {
49
49
  * Opt-in `creatable` appends a create action when the query is not already
50
50
  * an option; choosing it calls `onValueChange` like a normal selection.
51
51
  */
52
- 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, creatable = false, normalizeCreateValue = defaultNormalizeCreateValue, createLabel = defaultCreateLabel, }) {
52
+ export function Combobox({ options, value, onValueChange, placeholder = "Select", searchPlaceholder = "Search", emptyText = "No results", disabled, id, className, listClassName, "aria-invalid": ariaInvalid, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, creatable = false, normalizeCreateValue = defaultNormalizeCreateValue, createLabel = defaultCreateLabel, }) {
53
53
  const [open, setOpen] = React.useState(false);
54
54
  const [query, setQuery] = React.useState("");
55
55
  const [highlight, setHighlight] = React.useState(0);
@@ -123,7 +123,7 @@ export function Combobox({ options, value, onValueChange, placeholder = "Select"
123
123
  selectValue(option.value);
124
124
  }
125
125
  };
126
- 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 ? listboxOptionId(listId, highlight) : undefined }) }), _jsx("ul", { id: listId, role: "listbox", className: "max-h-60 overflow-y-auto p-1", children: list.length === 0 ? (_jsx("li", { className: listboxEmpty, children: emptyText })) : (list.map((option, index) => {
126
+ 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 ? listboxOptionId(listId, highlight) : undefined }) }), _jsx("ul", { id: listId, role: "listbox", className: cn("max-h-60 overflow-y-auto p-1", listClassName), children: list.length === 0 ? (_jsx("li", { className: listboxEmpty, children: emptyText })) : (list.map((option, index) => {
127
127
  const isCreate = createOption != null && option === createOption;
128
128
  const isSelected = option.value === value && !isCreate;
129
129
  const isActive = index === highlight;
@@ -4,9 +4,9 @@ import * as React from "react";
4
4
  import { format, isSameDay, parseISO } from "date-fns";
5
5
  import { CalendarIcon } from "lucide-react";
6
6
  import { Button } from "./button.js";
7
- import { Calendar } from "./calendar.js";
8
7
  import { PICKER_COLLISION_PADDING, pickerContentClassName, pickerTriggerClassName, } from "./internal/form-control.js";
9
8
  import { MD_MEDIA_QUERY, useMediaQuery } from "./internal/use-media-query.js";
9
+ import { LazyCalendar, preloadCalendar } from "./lazy-calendar.js";
10
10
  import { Popover, PopoverContent, PopoverTrigger } from "./popover.js";
11
11
  import { cn } from "./utils.js";
12
12
  /**
@@ -58,12 +58,12 @@ function formatRangeTriggerLabel(from, to, placeholder) {
58
58
  }
59
59
  /** Shared trigger + auto-sized popover chrome for single and range modes. */
60
60
  function DatePickerShell({ id, disabled, ariaInvalid, className, empty, label, open, onOpenChange, side, children, }) {
61
- return (_jsxs(Popover, { open: open, onOpenChange: onOpenChange, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { id: id, type: "button", variant: "outline", disabled: disabled, "aria-invalid": ariaInvalid, "data-empty": empty, className: cn(pickerTriggerClassName, className), children: [_jsx(CalendarIcon, { "data-icon": "inline-start" }), label] }) }), _jsx(PopoverContent, { align: "start", side: side, collisionPadding: PICKER_COLLISION_PADDING, className: cn("w-auto", pickerContentClassName), children: children })] }));
61
+ return (_jsxs(Popover, { open: open, onOpenChange: onOpenChange, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { id: id, type: "button", variant: "outline", disabled: disabled, "aria-invalid": ariaInvalid, "data-empty": empty, className: cn(pickerTriggerClassName, className), onPointerEnter: preloadCalendar, onFocus: preloadCalendar, children: [_jsx(CalendarIcon, { "data-icon": "inline-start" }), label] }) }), _jsx(PopoverContent, { align: "start", side: side, collisionPadding: PICKER_COLLISION_PADDING, className: cn("w-auto", pickerContentClassName), children: children })] }));
62
62
  }
63
63
  function DatePickerSingleButton({ id, value, onChange, placeholder = "Pick a date", disabled, className, "aria-invalid": ariaInvalid, numberOfMonths, side, defaultMonth, }) {
64
64
  const [open, setOpen] = React.useState(false);
65
65
  const date = toDate(value);
66
- return (_jsx(DatePickerShell, { id: id, disabled: disabled, ariaInvalid: ariaInvalid, className: className, empty: !date, label: date ? format(date, "PPP") : placeholder, open: open, onOpenChange: setOpen, side: side, children: _jsx(Calendar, { mode: "single", selected: date, defaultMonth: defaultMonth ?? date, numberOfMonths: numberOfMonths, onSelect: (next) => {
66
+ return (_jsx(DatePickerShell, { id: id, disabled: disabled, ariaInvalid: ariaInvalid, className: className, empty: !date, label: date ? format(date, "PPP") : placeholder, open: open, onOpenChange: setOpen, side: side, children: _jsx(LazyCalendar, { mode: "single", selected: date, defaultMonth: defaultMonth ?? date, numberOfMonths: numberOfMonths, onSelect: (next) => {
67
67
  onChange(next);
68
68
  setOpen(false);
69
69
  } }) }));
@@ -84,7 +84,7 @@ function DatePickerRangeButton({ id, value, onChange, placeholder = "Pick a date
84
84
  restartRef.current = Boolean(from && to);
85
85
  setOpen(next);
86
86
  };
87
- 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) => {
87
+ 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(LazyCalendar, { mode: "range", selected: selected, defaultMonth: defaultMonth ?? from ?? to, numberOfMonths: months, onSelect: (next, day) => {
88
88
  const range = restartRef.current ? { from: day, to: day } : next;
89
89
  restartRef.current = false;
90
90
  onChange(range);
@@ -4,8 +4,8 @@ import * as React from "react";
4
4
  import { format } from "date-fns";
5
5
  import { CalendarIcon } from "lucide-react";
6
6
  import { Button } from "./button.js";
7
- import { Calendar } from "./calendar.js";
8
7
  import { PICKER_COLLISION_PADDING, pickerContentClassName, pickerTriggerClassName, } from "./internal/form-control.js";
8
+ import { LazyCalendar, preloadCalendar } from "./lazy-calendar.js";
9
9
  import { Popover, PopoverContent, PopoverTrigger } from "./popover.js";
10
10
  import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "./select.js";
11
11
  import { cn } from "./utils.js";
@@ -79,7 +79,7 @@ export function DateTimePickerButton({ id, value, onChange, timeStep, placeholde
79
79
  const label = date ? format(date, "PPP p") : placeholder;
80
80
  const hourId = React.useId();
81
81
  const minuteId = React.useId();
82
- 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(pickerTriggerClassName, className), children: [_jsx(CalendarIcon, { "data-icon": "inline-start" }), label] }) }), _jsxs(PopoverContent, { align: "start", side: side, collisionPadding: PICKER_COLLISION_PADDING, onInteractOutside: preventDismissForPortaledSelect, className: cn("w-auto min-w-0 max-w-[min(100vw-2rem,24rem)]", pickerContentClassName), children: [_jsx(Calendar, { mode: "single", selected: date ?? undefined, defaultMonth: defaultMonth ?? date ?? undefined, onSelect: (next) => {
82
+ 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(pickerTriggerClassName, className), onPointerEnter: preloadCalendar, onFocus: preloadCalendar, children: [_jsx(CalendarIcon, { "data-icon": "inline-start" }), label] }) }), _jsxs(PopoverContent, { align: "start", side: side, collisionPadding: PICKER_COLLISION_PADDING, onInteractOutside: preventDismissForPortaledSelect, className: cn("w-auto min-w-0", pickerContentClassName), children: [_jsx(LazyCalendar, { mode: "single", selected: date ?? undefined, defaultMonth: defaultMonth ?? date ?? undefined, onSelect: (next) => {
83
83
  // Re-clicking a selected day would clear it; require Clear instead
84
84
  // so a time already set is not dropped by an accidental second tap.
85
85
  if (!next)
package/dist/dialog.js CHANGED
@@ -21,7 +21,7 @@ function DialogOverlay({ className, ...props }) {
21
21
  return (_jsx(DialogPrimitive.Overlay, { "data-slot": "dialog-overlay", className: cn("fixed inset-0 isolate z-50 bg-black/20 duration-100 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0 supports-backdrop-filter:backdrop-blur-sm", className), ...props }));
22
22
  }
23
23
  function DialogContent({ className, children, showCloseButton = true, ...props }) {
24
- return (_jsxs(DialogPortal, { children: [_jsx(DialogOverlay, {}), _jsxs(DialogPrimitive.Content, { "data-slot": "dialog-content", className: cn("fixed top-1/2 left-1/2 z-50 grid max-h-[90dvh] w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-6 overflow-y-auto rounded-none bg-popover p-6 text-sm text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 outline-none data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 sm:max-w-md", className), ...props, children: [children, showCloseButton && (_jsx(DialogPrimitive.Close, { "data-slot": "dialog-close", asChild: true, children: _jsxs(Button, { variant: "ghost", size: "icon-sm", className: "absolute top-4 right-4 text-muted-foreground hover:text-foreground", children: [_jsx(XIcon, {}), _jsx("span", { className: "sr-only", children: "Close" })] }) }))] })] }));
24
+ return (_jsxs(DialogPortal, { children: [_jsx(DialogOverlay, {}), _jsxs(DialogPrimitive.Content, { "data-slot": "dialog-content", className: cn("fixed top-1/2 left-1/2 z-50 grid max-h-[90dvh] w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-6 overflow-y-auto rounded-none bg-popover p-6 text-sm text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 outline-none data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95", className), ...props, children: [children, showCloseButton && (_jsx(DialogPrimitive.Close, { "data-slot": "dialog-close", asChild: true, children: _jsxs(Button, { variant: "ghost", size: "icon-sm", className: "absolute top-4 right-4 text-muted-foreground hover:text-foreground", children: [_jsx(XIcon, {}), _jsx("span", { className: "sr-only", children: "Close" })] }) }))] })] }));
25
25
  }
26
26
  function DialogHeader({ className, ...props }) {
27
27
  return (_jsx("div", { "data-slot": "dialog-header", className: cn("flex flex-col gap-2", className), ...props }));
@@ -7,7 +7,7 @@ import { dataDisabledState, optionState } from "./internal/styles.js";
7
7
  import { cn } from "./utils.js";
8
8
  /** Item highlight and selected chrome shared with `SelectItem`. */
9
9
  const menuItemClassName = cn(optionState.focus, dataDisabledState, "relative flex cursor-default items-center gap-2.5 rounded-none px-3 py-2 text-sm outline-hidden select-none data-inset:pl-9 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5");
10
- 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
+ const menuContentClassName = "z-50 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";
11
11
  /** Action menu for commands (row actions, overflow menus). Use `Select` to pick a value. */
12
12
  function DropdownMenu({ ...props }) {
13
13
  return _jsx(DropdownMenuPrimitive.Root, { "data-slot": "dropdown-menu", ...props });
@@ -1,12 +1,17 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useState } from "react";
4
- import { Markdown } from "../markdown.js";
3
+ import { lazy, Suspense, useState } from "react";
4
+ import { Spinner } from "../spinner.js";
5
5
  import { Textarea } from "../textarea.js";
6
6
  import { ToggleGroup, ToggleGroupItem } from "../toggle-group.js";
7
7
  import { fieldSurface } from "../internal/styles.js";
8
8
  import { cn, onInputChange } from "../utils.js";
9
9
  import { FormFieldShell, invalidWhen, metaErrorText } from "./field-shell.js";
10
+ // The renderer (react-markdown + remark-gfm) is fetched the first time
11
+ // Preview is shown or hovered, so a form in Write mode stays light.
12
+ const loadMarkdown = () => import("../markdown.js");
13
+ const preloadMarkdown = () => void loadMarkdown();
14
+ const Markdown = lazy(() => loadMarkdown().then((module) => ({ default: module.Markdown })));
10
15
  /**
11
16
  * GFM body field: Write is a textarea, Preview is the sanitized Markdown
12
17
  * renderer. String-in/string-out — not a WYSIWYG document model.
@@ -20,5 +25,5 @@ export function FormMarkdownField({ field, id = field.name, label, placeholder =
20
25
  if (value === "write" || value === "preview")
21
26
  setMode(value);
22
27
  }
23
- return (_jsx(FormFieldShell, { id: id, label: label, errorText: errorText, labelAction: _jsxs(ToggleGroup, { type: "single", value: mode, onValueChange: handleModeChange, variant: "outline", size: "sm", spacing: 0, "aria-label": `${label} mode`, children: [_jsx(ToggleGroupItem, { value: "write", children: "Write" }), _jsx(ToggleGroupItem, { value: "preview", children: "Preview" })] }), children: mode === "write" ? (_jsx(Textarea, { id: id, value: field.state.value, onChange: onChange, onBlur: field.handleBlur, placeholder: placeholder, rows: rows, "aria-invalid": invalidWhen(errorText) })) : (_jsx("div", { className: cn(fieldSurface, "min-h-16"), "aria-live": "polite", children: trimmed ? (_jsx(Markdown, { content: trimmed })) : (_jsx("p", { className: "text-sm text-muted-foreground", children: "Nothing to preview" })) })) }));
28
+ return (_jsx(FormFieldShell, { id: id, label: label, errorText: errorText, labelAction: _jsxs(ToggleGroup, { type: "single", value: mode, onValueChange: handleModeChange, variant: "outline", size: "sm", spacing: 0, "aria-label": `${label} mode`, children: [_jsx(ToggleGroupItem, { value: "write", children: "Write" }), _jsx(ToggleGroupItem, { value: "preview", onPointerEnter: preloadMarkdown, onFocus: preloadMarkdown, children: "Preview" })] }), children: mode === "write" ? (_jsx(Textarea, { id: id, value: field.state.value, onChange: onChange, onBlur: field.handleBlur, placeholder: placeholder, rows: rows, "aria-invalid": invalidWhen(errorText) })) : (_jsx("div", { className: cn(fieldSurface, "min-h-16"), "aria-live": "polite", children: trimmed ? (_jsx(Suspense, { fallback: _jsx(Spinner, { "aria-label": "Loading preview" }), children: _jsx(Markdown, { content: trimmed }) })) : (_jsx("p", { className: "text-sm text-muted-foreground", children: "Nothing to preview" })) })) }));
24
29
  }
@@ -14,14 +14,17 @@ export type InfoTipProps = {
14
14
  align?: React.ComponentProps<typeof PopoverContent>["align"];
15
15
  badge?: React.ReactNode;
16
16
  icon?: React.ReactNode;
17
+ /** Classes for the trigger button. */
17
18
  className?: string;
19
+ /** Classes for the panel, e.g. its width. Defaults to `w-72`. */
20
+ contentClassName?: string;
18
21
  };
19
22
  /**
20
23
  * Longer help than a Tooltip: a Popover with a 20px glyph and a 44px hit
21
24
  * area. Hover opens only on a fine pointer; click, tap, and keyboard always
22
25
  * toggle it. Not a glossary or "coming soon" chip — pass that copy in.
23
26
  */
24
- declare function InfoTip({ label, title, description, children, layout, side, align, badge, icon, className, }: InfoTipProps): React.JSX.Element;
27
+ declare function InfoTip({ label, title, description, children, layout, side, align, badge, icon, className, contentClassName, }: InfoTipProps): React.JSX.Element;
25
28
  /** InfoTip that sits in a line of text without stretching the line box. */
26
29
  declare function InlineTip(props: Omit<InfoTipProps, "layout">): React.JSX.Element;
27
30
  export { InfoTip, InlineTip };
package/dist/info-tip.js CHANGED
@@ -17,7 +17,7 @@ function canHoverOpen() {
17
17
  * area. Hover opens only on a fine pointer; click, tap, and keyboard always
18
18
  * toggle it. Not a glossary or "coming soon" chip — pass that copy in.
19
19
  */
20
- function InfoTip({ label, title, description, children, layout = "box", side = "top", align = "center", badge, icon, className, }) {
20
+ function InfoTip({ label, title, description, children, layout = "box", side = "top", align = "center", badge, icon, className, contentClassName, }) {
21
21
  const [open, setOpen] = React.useState(false);
22
22
  const closeTimer = React.useRef(null);
23
23
  const hoverOpened = React.useRef(false);
@@ -55,7 +55,7 @@ function InfoTip({ label, title, description, children, layout = "box", side = "
55
55
  setOpen(next);
56
56
  }, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx("button", { type: "button", "aria-label": label, "data-slot": "info-tip-trigger", "data-layout": layout, className: cn(focusRing, "relative inline-flex shrink-0 items-center justify-center rounded-none text-muted-foreground hover:text-selected-foreground focus-visible:text-selected-foreground", inline
57
57
  ? "size-5 align-middle after:absolute after:top-1/2 after:left-1/2 after:size-11 after:-translate-x-1/2 after:-translate-y-1/2"
58
- : "size-11", className), onMouseEnter: openFromHover, onMouseLeave: closeFromHover, children: icon ?? (_jsx(CircleHelpIcon, { className: "size-5", "data-icon": "info-tip", "aria-hidden": true })) }) }), _jsxs(PopoverContent, { side: side, align: align, className: "w-72", onMouseEnter: openFromHover, onMouseLeave: closeFromHover, children: [_jsxs(PopoverHeader, { children: [_jsxs("div", { className: "flex items-start justify-between gap-2", children: [title ? _jsx(PopoverTitle, { children: title }) : null, badge] }), description ? (_jsx(PopoverDescription, { children: description })) : null] }), children] })] }));
58
+ : "size-11", className), onMouseEnter: openFromHover, onMouseLeave: closeFromHover, children: icon ?? (_jsx(CircleHelpIcon, { className: "size-5", "data-icon": "info-tip", "aria-hidden": true })) }) }), _jsxs(PopoverContent, { side: side, align: align, className: cn("w-72", contentClassName), onMouseEnter: openFromHover, onMouseLeave: closeFromHover, children: [_jsxs(PopoverHeader, { children: [_jsxs("div", { className: "flex items-start justify-between gap-2", children: [title ? _jsx(PopoverTitle, { children: title }) : null, badge] }), description ? (_jsx(PopoverDescription, { children: description })) : null] }), children] })] }));
59
59
  }
60
60
  /** InfoTip that sits in a line of text without stretching the line box. */
61
61
  function InlineTip(props) {
@@ -0,0 +1,6 @@
1
+ import * as React from "react";
2
+ /** Start fetching the calendar early, e.g. when the trigger is hovered. */
3
+ export declare function preloadCalendar(): void;
4
+ declare const LazyCalendarInner: React.LazyExoticComponent<typeof import("./calendar.js").Calendar>;
5
+ export declare function LazyCalendar(props: React.ComponentProps<typeof LazyCalendarInner>): React.JSX.Element;
6
+ export {};
@@ -0,0 +1,18 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { Spinner } from "./spinner.js";
5
+ /**
6
+ * Calendar for the picker popovers, fetched on first use so a page that only
7
+ * shows the trigger does not download Day Picker. Not a public entry: apps
8
+ * that want an inline calendar import `atlas-react-kit/calendar`.
9
+ */
10
+ const loadCalendar = () => import("./calendar.js");
11
+ /** Start fetching the calendar early, e.g. when the trigger is hovered. */
12
+ export function preloadCalendar() {
13
+ void loadCalendar();
14
+ }
15
+ const LazyCalendarInner = React.lazy(() => loadCalendar().then((module) => ({ default: module.Calendar })));
16
+ export function LazyCalendar(props) {
17
+ return (_jsx(React.Suspense, { fallback: _jsx("div", { className: "flex h-72 w-64 items-center justify-center", children: _jsx(Spinner, { "aria-label": "Loading calendar" }) }), children: _jsx(LazyCalendarInner, { ...props }) }));
18
+ }
package/dist/popover.js CHANGED
@@ -10,7 +10,7 @@ function PopoverTrigger({ ...props }) {
10
10
  return _jsx(PopoverPrimitive.Trigger, { "data-slot": "popover-trigger", ...props });
11
11
  }
12
12
  function PopoverContent({ className, align = "center", sideOffset = 4, collisionPadding = 8, ...props }) {
13
- return (_jsx(PopoverPrimitive.Portal, { children: _jsx(PopoverPrimitive.Content, { "data-slot": "popover-content", align: align, sideOffset: sideOffset, collisionPadding: collisionPadding, className: cn("z-50 flex w-72 origin-(--radix-popover-content-transform-origin) flex-col gap-4 rounded-none bg-popover p-4 text-sm text-popover-foreground shadow-md ring-1 ring-foreground/10 outline-hidden 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-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95", className), ...props }) }));
13
+ return (_jsx(PopoverPrimitive.Portal, { children: _jsx(PopoverPrimitive.Content, { "data-slot": "popover-content", align: align, sideOffset: sideOffset, collisionPadding: collisionPadding, className: cn("z-50 flex max-w-(--radix-popover-content-available-width) origin-(--radix-popover-content-transform-origin) flex-col gap-4 rounded-none bg-popover p-4 text-sm text-popover-foreground shadow-md ring-1 ring-foreground/10 outline-hidden 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-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95", className), ...props }) }));
14
14
  }
15
15
  function PopoverAnchor({ ...props }) {
16
16
  return _jsx(PopoverPrimitive.Anchor, { "data-slot": "popover-anchor", ...props });
@@ -20,11 +20,13 @@ export type SearchPickerProps<T> = {
20
20
  listLabel?: string;
21
21
  disabled?: boolean;
22
22
  className?: string;
23
+ /** Classes for the listbox, e.g. its height. Defaults to `max-h-60`. */
24
+ listClassName?: string;
23
25
  };
24
26
  /**
25
27
  * Always-visible combobox: a labelled input and a listbox. Unlike Combobox,
26
28
  * the list is not in a popover. Keyboard: arrows, Home/End, PageUp/PageDown,
27
29
  * Enter, Escape.
28
30
  */
29
- declare function SearchPicker<T>({ items, getValue, getLabel, filter, value, onSelect, label, placeholder, emptyText, listLabel, disabled, className, }: SearchPickerProps<T>): React.JSX.Element;
31
+ declare function SearchPicker<T>({ items, getValue, getLabel, filter, value, onSelect, label, placeholder, emptyText, listLabel, disabled, className, listClassName, }: SearchPickerProps<T>): React.JSX.Element;
30
32
  export { SearchPicker };
@@ -26,7 +26,7 @@ export function defaultSearchFilter(item, query, getLabel) {
26
26
  * the list is not in a popover. Keyboard: arrows, Home/End, PageUp/PageDown,
27
27
  * Enter, Escape.
28
28
  */
29
- function SearchPicker({ items, getValue, getLabel, filter = defaultSearchFilter, value, onSelect, label, placeholder = "Search", emptyText = "No results", listLabel, disabled, className, }) {
29
+ function SearchPicker({ items, getValue, getLabel, filter = defaultSearchFilter, value, onSelect, label, placeholder = "Search", emptyText = "No results", listLabel, disabled, className, listClassName, }) {
30
30
  const [query, setQuery] = React.useState("");
31
31
  const listId = React.useId();
32
32
  const inputId = React.useId();
@@ -87,7 +87,7 @@ function SearchPicker({ items, getValue, getLabel, filter = defaultSearchFilter,
87
87
  event.currentTarget.blur();
88
88
  }
89
89
  };
90
- return (_jsxs("div", { "data-slot": "search-picker", className: cn("flex flex-col gap-1.5", className), children: [_jsx(Label, { htmlFor: inputId, children: label }), _jsx(Input, { id: inputId, role: "combobox", "aria-expanded": true, "aria-controls": listId, "aria-autocomplete": "list", "aria-activedescendant": active ? listboxOptionId(listId, highlight) : undefined, value: query, disabled: disabled, placeholder: placeholder, onChange: (event) => setQuery(event.target.value), onKeyDown: onKeyDown }), _jsx("ul", { id: listId, role: "listbox", "aria-label": listLabel ?? label, className: "max-h-60 overflow-y-auto border border-input bg-background", children: filtered.length === 0 ? (_jsx("li", { className: listboxEmpty, children: emptyText })) : (filtered.map((item, index) => {
90
+ return (_jsxs("div", { "data-slot": "search-picker", className: cn("flex flex-col gap-1.5", className), children: [_jsx(Label, { htmlFor: inputId, children: label }), _jsx(Input, { id: inputId, role: "combobox", "aria-expanded": true, "aria-controls": listId, "aria-autocomplete": "list", "aria-activedescendant": active ? listboxOptionId(listId, highlight) : undefined, value: query, disabled: disabled, placeholder: placeholder, onChange: (event) => setQuery(event.target.value), onKeyDown: onKeyDown }), _jsx("ul", { id: listId, role: "listbox", "aria-label": listLabel ?? label, className: cn("max-h-60 overflow-y-auto border border-input bg-background", listClassName), children: filtered.length === 0 ? (_jsx("li", { className: listboxEmpty, children: emptyText })) : (filtered.map((item, index) => {
91
91
  const itemValue = getValue(item);
92
92
  const isSelected = itemValue === value;
93
93
  const isActive = index === highlight;
@@ -3,5 +3,5 @@ import { Skeleton } from "./skeleton.js";
3
3
  import { TableBody, TableCell, TableRow } from "./table.js";
4
4
  /** Skeleton table body rows for client query loading states. */
5
5
  export function TableBodySkeleton({ columns, rows = 5 }) {
6
- return (_jsx(TableBody, { children: Array.from({ length: rows }, (_, row) => (_jsx(TableRow, { children: Array.from({ length: columns }, (_, col) => (_jsx(TableCell, { children: _jsx(Skeleton, { className: "h-4 w-full max-w-[12rem]" }) }, col))) }, row))) }));
6
+ return (_jsx(TableBody, { children: Array.from({ length: rows }, (_, row) => (_jsx(TableRow, { children: Array.from({ length: columns }, (_, col) => (_jsx(TableCell, { children: _jsx(Skeleton, { className: "h-4 w-full" }) }, col))) }, row))) }));
7
7
  }
package/dist/tabs.js CHANGED
@@ -60,6 +60,6 @@ function TabsContent({ className, ...props }) {
60
60
  * `overflow-x-auto`.
61
61
  */
62
62
  function TabsNav({ className, children, ...props }) {
63
- return (_jsx("nav", { "data-slot": "tabs-nav", "data-orientation": "horizontal", className: cn("group/tabs min-w-0 w-full max-w-full", className), ...props, children: _jsx(TabsScroller, { children: _jsx("div", { "data-slot": "tabs-list", "data-variant": "default", className: cn(tabsListVariants(), "bg-transparent"), children: children }) }) }));
63
+ return (_jsx("nav", { "data-slot": "tabs-nav", "data-orientation": "horizontal", className: cn("group/tabs min-w-0 max-w-full", className), ...props, children: _jsx(TabsScroller, { children: _jsx("div", { "data-slot": "tabs-list", "data-variant": "default", className: cn(tabsListVariants(), "bg-transparent"), children: children }) }) }));
64
64
  }
65
65
  export { Tabs, TabsList, TabsTrigger, TabsContent, TabsNav, tabsListVariants, tabsTriggerVariants, };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "atlas-react-kit",
3
- "version": "0.9.4",
3
+ "version": "0.10.0",
4
4
  "description": "Shared React components and Tailwind theme for Atlas",
5
5
  "type": "module",
6
6
  "license": "MIT",