atlas-react-kit 0.4.1 → 0.5.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 (51) hide show
  1. package/COMPONENTS.md +122 -39
  2. package/DESIGN_RULES.md +61 -17
  3. package/README.md +56 -10
  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/breadcrumb.d.ts +16 -0
  12. package/dist/breadcrumb.js +33 -0
  13. package/dist/button.d.ts +6 -5
  14. package/dist/button.js +6 -5
  15. package/dist/calendar.d.ts +1 -1
  16. package/dist/calendar.js +19 -1
  17. package/dist/combobox.d.ts +4 -1
  18. package/dist/combobox.js +10 -5
  19. package/dist/date-picker.js +14 -3
  20. package/dist/date-time-picker.d.ts +36 -0
  21. package/dist/date-time-picker.js +105 -0
  22. package/dist/dropdown-menu.d.ts +26 -0
  23. package/dist/dropdown-menu.js +56 -0
  24. package/dist/error-boundary.d.ts +1 -1
  25. package/dist/field.js +1 -1
  26. package/dist/form/date-time-picker-field.d.ts +16 -0
  27. package/dist/form/date-time-picker-field.js +12 -0
  28. package/dist/form/text-field.d.ts +7 -1
  29. package/dist/form/text-field.js +3 -3
  30. package/dist/pagination.d.ts +18 -0
  31. package/dist/pagination.js +35 -0
  32. package/dist/progress.d.ts +8 -0
  33. package/dist/progress.js +13 -0
  34. package/dist/radio-group.d.ts +13 -0
  35. package/dist/radio-group.js +20 -0
  36. package/dist/spinner.d.ts +8 -0
  37. package/dist/spinner.js +13 -0
  38. package/dist/switch.d.ts +9 -0
  39. package/dist/switch.js +14 -0
  40. package/dist/table.d.ts +2 -0
  41. package/dist/table.js +25 -1
  42. package/dist/tabs.d.ts +6 -3
  43. package/dist/tabs.js +5 -2
  44. package/dist/theme-provider.d.ts +2 -1
  45. package/dist/theme-provider.js +28 -4
  46. package/dist/toggle.js +1 -1
  47. package/dist/tooltip.d.ts +15 -0
  48. package/dist/tooltip.js +26 -0
  49. package/package.json +58 -5
  50. package/skills/atlas-react-kit/SKILL.md +139 -0
  51. package/theme.css +5 -5
@@ -0,0 +1,8 @@
1
+ import * as React from "react";
2
+ import { Progress as ProgressPrimitive } from "radix-ui";
3
+ /**
4
+ * Determinate progress (uploads, multi-step jobs). `value` is 0–100; omit
5
+ * it for an indeterminate state and pair the bar with a visible label.
6
+ */
7
+ declare function Progress({ className, value, ...props }: React.ComponentProps<typeof ProgressPrimitive.Root>): React.JSX.Element;
8
+ export { Progress };
@@ -0,0 +1,13 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { Progress as ProgressPrimitive } from "radix-ui";
5
+ import { cn } from "./utils.js";
6
+ /**
7
+ * Determinate progress (uploads, multi-step jobs). `value` is 0–100; omit
8
+ * it for an indeterminate state and pair the bar with a visible label.
9
+ */
10
+ function Progress({ className, value, ...props }) {
11
+ return (_jsx(ProgressPrimitive.Root, { "data-slot": "progress", value: value, className: cn("relative h-2 w-full overflow-hidden rounded-none bg-primary/15", className), ...props, children: _jsx(ProgressPrimitive.Indicator, { "data-slot": "progress-indicator", className: "h-full w-full flex-1 bg-primary transition-transform", style: { transform: `translateX(-${100 - (value ?? 0)}%)` } }) }));
12
+ }
13
+ export { Progress };
@@ -0,0 +1,13 @@
1
+ import * as React from "react";
2
+ import { RadioGroup as RadioGroupPrimitive } from "radix-ui";
3
+ /**
4
+ * One choice from a short, visible list. Arrow keys move the selection.
5
+ * Use `Select` or `Combobox` when the list is long.
6
+ */
7
+ declare function RadioGroup({ className, ...props }: React.ComponentProps<typeof RadioGroupPrimitive.Root>): React.JSX.Element;
8
+ /**
9
+ * Radio dot. Radios stay round (a circle, per the shape rules) so they read
10
+ * differently from square checkboxes.
11
+ */
12
+ declare function RadioGroupItem({ className, ...props }: React.ComponentProps<typeof RadioGroupPrimitive.Item>): React.JSX.Element;
13
+ export { RadioGroup, RadioGroupItem };
@@ -0,0 +1,20 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { RadioGroup as RadioGroupPrimitive } from "radix-ui";
5
+ import { cn } from "./utils.js";
6
+ /**
7
+ * One choice from a short, visible list. Arrow keys move the selection.
8
+ * Use `Select` or `Combobox` when the list is long.
9
+ */
10
+ function RadioGroup({ className, ...props }) {
11
+ return (_jsx(RadioGroupPrimitive.Root, { "data-slot": "radio-group", className: cn("grid gap-3", className), ...props }));
12
+ }
13
+ /**
14
+ * Radio dot. Radios stay round (a circle, per the shape rules) so they read
15
+ * differently from square checkboxes.
16
+ */
17
+ function RadioGroupItem({ className, ...props }) {
18
+ return (_jsx(RadioGroupPrimitive.Item, { "data-slot": "radio-group-item", className: cn("peer relative flex aspect-square size-4.5 shrink-0 items-center justify-center rounded-full border border-input bg-transparent shadow-xs transition-shadow outline-none 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 data-checked:border-primary", className), ...props, children: _jsx(RadioGroupPrimitive.Indicator, { "data-slot": "radio-group-indicator", className: "flex items-center justify-center", children: _jsx("span", { className: "size-2 rounded-full bg-primary" }) }) }));
19
+ }
20
+ export { RadioGroup, RadioGroupItem };
@@ -0,0 +1,8 @@
1
+ import * as React from "react";
2
+ /**
3
+ * Indeterminate loading indicator for a button or small region. Announced as
4
+ * "Loading" by default; pass `aria-label` for something more specific. Use
5
+ * `Skeleton` or `TableBodySkeleton` when the layout of the result is known.
6
+ */
7
+ declare function Spinner({ className, "aria-label": ariaLabel, ...props }: React.ComponentProps<"svg">): React.JSX.Element;
8
+ export { Spinner };
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import * as React from "react";
3
+ import { Loader2Icon } from "lucide-react";
4
+ import { cn } from "./utils.js";
5
+ /**
6
+ * Indeterminate loading indicator for a button or small region. Announced as
7
+ * "Loading" by default; pass `aria-label` for something more specific. Use
8
+ * `Skeleton` or `TableBodySkeleton` when the layout of the result is known.
9
+ */
10
+ function Spinner({ className, "aria-label": ariaLabel = "Loading", ...props }) {
11
+ return (_jsx(Loader2Icon, { role: "status", "aria-label": ariaLabel, "data-slot": "spinner", className: cn("size-4 animate-spin", className), ...props }));
12
+ }
13
+ export { Spinner };
@@ -0,0 +1,9 @@
1
+ import * as React from "react";
2
+ import { Switch as SwitchPrimitive } from "radix-ui";
3
+ /**
4
+ * On/off setting that applies immediately (notifications, dark mode). Use
5
+ * `Checkbox` for choices submitted with a form. Square like the other
6
+ * primitives; checked uses the primary fill, as `Checkbox` does.
7
+ */
8
+ declare function Switch({ className, ...props }: React.ComponentProps<typeof SwitchPrimitive.Root>): React.JSX.Element;
9
+ export { Switch };
package/dist/switch.js ADDED
@@ -0,0 +1,14 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { Switch as SwitchPrimitive } from "radix-ui";
5
+ import { cn } from "./utils.js";
6
+ /**
7
+ * On/off setting that applies immediately (notifications, dark mode). Use
8
+ * `Checkbox` for choices submitted with a form. Square like the other
9
+ * primitives; checked uses the primary fill, as `Checkbox` does.
10
+ */
11
+ function Switch({ className, ...props }) {
12
+ return (_jsx(SwitchPrimitive.Root, { "data-slot": "switch", className: cn("peer inline-flex h-5 w-9 shrink-0 items-center rounded-none border border-transparent bg-input p-0.5 shadow-xs transition-colors outline-none 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 data-checked:bg-primary dark:bg-input/80 dark:data-checked:bg-primary", className), ...props, children: _jsx(SwitchPrimitive.Thumb, { "data-slot": "switch-thumb", className: "pointer-events-none block size-3.5 rounded-none bg-background shadow-sm ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0 dark:data-[state=checked]:bg-primary-foreground" }) }));
13
+ }
14
+ export { Switch };
package/dist/table.d.ts CHANGED
@@ -2,6 +2,8 @@ import * as React from "react";
2
2
  /**
3
3
  * Data table. The root is a `min-w-0 overflow-x-auto` scroller so nowrap
4
4
  * columns scroll inside the table instead of widening a card or panel.
5
+ * While it overflows, the scroller joins the tab order so keyboard users can
6
+ * scroll it with the arrow keys (WCAG 2.1.1).
5
7
  * Cell padding is `px-2` below `sm` and the original `p-3` from `sm` up.
6
8
  */
7
9
  declare function Table({ className, ...props }: React.ComponentProps<"table">): React.JSX.Element;
package/dist/table.js CHANGED
@@ -2,13 +2,37 @@
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { cn } from "./utils.js";
5
+ /**
6
+ * True while the element's content is wider than its box. Re-checked when
7
+ * the container or its content resizes.
8
+ */
9
+ function useHorizontalOverflow(ref) {
10
+ const [overflowing, setOverflowing] = React.useState(false);
11
+ React.useEffect(() => {
12
+ const element = ref.current;
13
+ if (!element || typeof ResizeObserver === "undefined")
14
+ return;
15
+ const check = () => setOverflowing(element.scrollWidth > element.clientWidth + 1);
16
+ check();
17
+ const observer = new ResizeObserver(check);
18
+ observer.observe(element);
19
+ if (element.firstElementChild)
20
+ observer.observe(element.firstElementChild);
21
+ return () => observer.disconnect();
22
+ }, [ref]);
23
+ return overflowing;
24
+ }
5
25
  /**
6
26
  * Data table. The root is a `min-w-0 overflow-x-auto` scroller so nowrap
7
27
  * columns scroll inside the table instead of widening a card or panel.
28
+ * While it overflows, the scroller joins the tab order so keyboard users can
29
+ * scroll it with the arrow keys (WCAG 2.1.1).
8
30
  * Cell padding is `px-2` below `sm` and the original `p-3` from `sm` up.
9
31
  */
10
32
  function Table({ className, ...props }) {
11
- return (_jsx("div", { "data-slot": "table-container", className: "relative w-full min-w-0 max-w-full overflow-x-auto overscroll-x-contain", children: _jsx("table", { "data-slot": "table", className: cn("w-full min-w-full caption-bottom text-sm", className), ...props }) }));
33
+ const containerRef = React.useRef(null);
34
+ const scrollable = useHorizontalOverflow(containerRef);
35
+ return (_jsx("div", { ref: containerRef, "data-slot": "table-container", tabIndex: scrollable ? 0 : undefined, className: "relative w-full min-w-0 max-w-full overflow-x-auto overscroll-x-contain outline-none focus-visible:ring-2 focus-visible:ring-ring/30", children: _jsx("table", { "data-slot": "table", className: cn("w-full min-w-full caption-bottom text-sm", className), ...props }) }));
12
36
  }
13
37
  function TableHeader({ className, ...props }) {
14
38
  return (_jsx("thead", { "data-slot": "table-header", className: cn("[&_tr]:border-b", className), ...props }));
package/dist/tabs.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as React from "react";
2
2
  import { type VariantProps } from "class-variance-authority";
3
3
  import { Tabs as TabsPrimitive } from "radix-ui";
4
- import { tabsListVariants } from "./internal/tabs-styles.js";
4
+ import { tabsListVariants, tabsTriggerVariants } from "./internal/tabs-styles.js";
5
5
  declare function Tabs({ className, orientation, ...props }: React.ComponentProps<typeof TabsPrimitive.Root>): React.JSX.Element;
6
6
  declare function TabsList({ className, variant, ...props }: React.ComponentProps<typeof TabsPrimitive.List> & VariantProps<typeof tabsListVariants>): React.JSX.Element;
7
7
  declare function TabsTrigger({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Trigger>): React.JSX.Element;
@@ -9,9 +9,12 @@ declare function TabsContent({ className, ...props }: React.ComponentProps<typeo
9
9
  /**
10
10
  * Tab-styled links for a view switcher that changes the URL. Use this, not
11
11
  * `Tabs` + `TabsTrigger asChild`, when there are no `TabsContent` panels:
12
- * tabs would point assistive tech at panels that don't exist.
12
+ * tabs would point assistive tech at panels that don't exist. Style each link
13
+ * with `tabsTriggerVariants()` and mark the active one with
14
+ * `data-state="active"` and `aria-current="page"` (the Next adapter
15
+ * `TabsNavLink` does this for you).
13
16
  */
14
17
  declare function TabsNav({ className, children, ...props }: React.ComponentProps<"nav"> & {
15
18
  "aria-label": string;
16
19
  }): React.JSX.Element;
17
- export { Tabs, TabsList, TabsTrigger, TabsContent, TabsNav, tabsListVariants };
20
+ export { Tabs, TabsList, TabsTrigger, TabsContent, TabsNav, tabsListVariants, tabsTriggerVariants, };
package/dist/tabs.js CHANGED
@@ -20,9 +20,12 @@ function TabsContent({ className, ...props }) {
20
20
  /**
21
21
  * Tab-styled links for a view switcher that changes the URL. Use this, not
22
22
  * `Tabs` + `TabsTrigger asChild`, when there are no `TabsContent` panels:
23
- * tabs would point assistive tech at panels that don't exist.
23
+ * tabs would point assistive tech at panels that don't exist. Style each link
24
+ * with `tabsTriggerVariants()` and mark the active one with
25
+ * `data-state="active"` and `aria-current="page"` (the Next adapter
26
+ * `TabsNavLink` does this for you).
24
27
  */
25
28
  function TabsNav({ className, children, ...props }) {
26
29
  return (_jsx("nav", { "data-slot": "tabs-nav", "data-orientation": "horizontal", className: cn("group/tabs", className), ...props, children: _jsx("div", { "data-slot": "tabs-list", "data-variant": "default", className: tabsListVariants(), children: children }) }));
27
30
  }
28
- export { Tabs, TabsList, TabsTrigger, TabsContent, TabsNav, tabsListVariants };
31
+ export { Tabs, TabsList, TabsTrigger, TabsContent, TabsNav, tabsListVariants, tabsTriggerVariants, };
@@ -3,7 +3,8 @@ import { useTheme, type ThemeProviderProps as NextThemesProviderProps } from "ne
3
3
  export type ThemeProviderProps = NextThemesProviderProps & {
4
4
  /**
5
5
  * When true, pressing `d` toggles dark/light unless focus is in an editable
6
- * field. Defaults to true.
6
+ * field or a widget with letter typeahead (such as `Select`). Defaults to
7
+ * true.
7
8
  */
8
9
  enableShortcut?: boolean;
9
10
  };
@@ -2,22 +2,46 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { ThemeProvider as NextThemesProvider, useTheme, } from "next-themes";
5
- const EDITABLE_SELECTOR = "input, textarea, select, [contenteditable]:not([contenteditable='false'])";
6
- function isEditableTarget(target) {
5
+ /**
6
+ * Targets where a bare `d` is input, not a shortcut: native and ARIA text
7
+ * fields, plus widgets with letter typeahead (Select trigger and list, menus,
8
+ * trees, grids).
9
+ */
10
+ const KEY_CONSUMING_SELECTOR = [
11
+ "input",
12
+ "textarea",
13
+ "select",
14
+ "[contenteditable]:not([contenteditable='false'])",
15
+ ...[
16
+ "textbox",
17
+ "searchbox",
18
+ "spinbutton",
19
+ "combobox",
20
+ "listbox",
21
+ "menu",
22
+ "menubar",
23
+ "tree",
24
+ "treegrid",
25
+ "grid",
26
+ ].map((role) => `[role="${role}"]`),
27
+ ].join(", ");
28
+ function isKeyConsumingTarget(target) {
7
29
  if (!(target instanceof HTMLElement))
8
30
  return false;
9
- return Boolean(target.closest(EDITABLE_SELECTOR));
31
+ return Boolean(target.closest(KEY_CONSUMING_SELECTOR));
10
32
  }
11
33
  /** Listens for the optional `d` shortcut inside an existing theme context. */
12
34
  function ThemeShortcut() {
13
35
  const { resolvedTheme, setTheme } = useTheme();
14
36
  React.useEffect(() => {
15
37
  function onKeyDown(event) {
38
+ if (event.defaultPrevented || event.repeat || event.isComposing)
39
+ return;
16
40
  if (event.metaKey || event.ctrlKey || event.altKey)
17
41
  return;
18
42
  if (event.key !== "d" && event.key !== "D")
19
43
  return;
20
- if (isEditableTarget(event.target))
44
+ if (isKeyConsumingTarget(event.target))
21
45
  return;
22
46
  event.preventDefault();
23
47
  setTheme(resolvedTheme === "dark" ? "light" : "dark");
package/dist/toggle.js CHANGED
@@ -4,7 +4,7 @@ import * as React from "react";
4
4
  import { cva } from "class-variance-authority";
5
5
  import { Toggle as TogglePrimitive } from "radix-ui";
6
6
  import { cn } from "./utils.js";
7
- const toggleVariants = cva("group/toggle inline-flex items-center justify-center gap-1.5 rounded-none text-xs font-semibold tracking-widest whitespace-nowrap uppercase transition-colors outline-none hover:border-primary hover:bg-selected hover:text-primary focus-visible:border-primary focus-visible:text-primary focus-visible:ring-[3px] focus-visible:ring-primary/30 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 aria-pressed:bg-selected aria-pressed:text-selected-foreground dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5", {
7
+ const toggleVariants = cva("group/toggle inline-flex items-center justify-center gap-1.5 rounded-none text-xs font-semibold tracking-widest whitespace-nowrap uppercase transition-colors outline-none hover:border-primary hover:bg-selected hover:text-selected-foreground focus-visible:border-primary focus-visible:text-selected-foreground focus-visible:ring-[3px] focus-visible:ring-primary/30 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 aria-pressed:bg-selected aria-pressed:text-selected-foreground dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5", {
8
8
  variants: {
9
9
  variant: {
10
10
  default: "bg-transparent",
@@ -0,0 +1,15 @@
1
+ import * as React from "react";
2
+ import { Tooltip as TooltipPrimitive } from "radix-ui";
3
+ /**
4
+ * Shares open delay across tooltips. `Tooltip` adds one per instance, so a
5
+ * provider at the app root is optional; add it to tune `delayDuration`.
6
+ */
7
+ declare function TooltipProvider({ delayDuration, ...props }: React.ComponentProps<typeof TooltipPrimitive.Provider>): React.JSX.Element;
8
+ /**
9
+ * Short label for an icon button or truncated text. Opens on hover and
10
+ * keyboard focus; put essential information in visible text instead.
11
+ */
12
+ declare function Tooltip({ ...props }: React.ComponentProps<typeof TooltipPrimitive.Root>): React.JSX.Element;
13
+ declare function TooltipTrigger({ ...props }: React.ComponentProps<typeof TooltipPrimitive.Trigger>): React.JSX.Element;
14
+ declare function TooltipContent({ className, sideOffset, children, ...props }: React.ComponentProps<typeof TooltipPrimitive.Content>): React.JSX.Element;
15
+ export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger };
@@ -0,0 +1,26 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { Tooltip as TooltipPrimitive } from "radix-ui";
5
+ import { cn } from "./utils.js";
6
+ /**
7
+ * Shares open delay across tooltips. `Tooltip` adds one per instance, so a
8
+ * provider at the app root is optional; add it to tune `delayDuration`.
9
+ */
10
+ function TooltipProvider({ delayDuration = 0, ...props }) {
11
+ return (_jsx(TooltipPrimitive.Provider, { "data-slot": "tooltip-provider", delayDuration: delayDuration, ...props }));
12
+ }
13
+ /**
14
+ * Short label for an icon button or truncated text. Opens on hover and
15
+ * keyboard focus; put essential information in visible text instead.
16
+ */
17
+ function Tooltip({ ...props }) {
18
+ return (_jsx(TooltipProvider, { children: _jsx(TooltipPrimitive.Root, { "data-slot": "tooltip", ...props }) }));
19
+ }
20
+ function TooltipTrigger({ ...props }) {
21
+ return _jsx(TooltipPrimitive.Trigger, { "data-slot": "tooltip-trigger", ...props });
22
+ }
23
+ function TooltipContent({ className, sideOffset = 4, children, ...props }) {
24
+ return (_jsx(TooltipPrimitive.Portal, { children: _jsxs(TooltipPrimitive.Content, { "data-slot": "tooltip-content", sideOffset: sideOffset, className: cn("z-50 w-fit max-w-xs origin-(--radix-tooltip-content-transform-origin) rounded-none bg-foreground px-3 py-1.5 text-xs text-balance text-background 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=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-[state=instant-open]:animate-in data-[state=instant-open]:fade-in-0", className), ...props, children: [children, _jsx(TooltipPrimitive.Arrow, { className: "fill-foreground" })] }) }));
25
+ }
26
+ export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "atlas-react-kit",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "Shared React components and Tailwind theme for Atlas",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -13,16 +13,33 @@
13
13
  "theme.css",
14
14
  "DESIGN_RULES.md",
15
15
  "COMPONENTS.md",
16
- "NOTICE"
16
+ "NOTICE",
17
+ "skills"
17
18
  ],
18
19
  "sideEffects": [
19
20
  "**/*.css"
20
21
  ],
21
22
  "exports": {
23
+ "./accordion": {
24
+ "types": "./dist/accordion.d.ts",
25
+ "default": "./dist/accordion.js"
26
+ },
27
+ "./alert": {
28
+ "types": "./dist/alert.d.ts",
29
+ "default": "./dist/alert.js"
30
+ },
31
+ "./avatar": {
32
+ "types": "./dist/avatar.d.ts",
33
+ "default": "./dist/avatar.js"
34
+ },
22
35
  "./badge": {
23
36
  "types": "./dist/badge.d.ts",
24
37
  "default": "./dist/badge.js"
25
38
  },
39
+ "./breadcrumb": {
40
+ "types": "./dist/breadcrumb.d.ts",
41
+ "default": "./dist/breadcrumb.js"
42
+ },
26
43
  "./button": {
27
44
  "types": "./dist/button.d.ts",
28
45
  "default": "./dist/button.js"
@@ -47,10 +64,18 @@
47
64
  "types": "./dist/date-picker.d.ts",
48
65
  "default": "./dist/date-picker.js"
49
66
  },
67
+ "./date-time-picker": {
68
+ "types": "./dist/date-time-picker.d.ts",
69
+ "default": "./dist/date-time-picker.js"
70
+ },
50
71
  "./dialog": {
51
72
  "types": "./dist/dialog.d.ts",
52
73
  "default": "./dist/dialog.js"
53
74
  },
75
+ "./dropdown-menu": {
76
+ "types": "./dist/dropdown-menu.d.ts",
77
+ "default": "./dist/dropdown-menu.js"
78
+ },
54
79
  "./empty-panel": {
55
80
  "types": "./dist/empty-panel.d.ts",
56
81
  "default": "./dist/empty-panel.js"
@@ -71,10 +96,22 @@
71
96
  "types": "./dist/label.d.ts",
72
97
  "default": "./dist/label.js"
73
98
  },
99
+ "./pagination": {
100
+ "types": "./dist/pagination.d.ts",
101
+ "default": "./dist/pagination.js"
102
+ },
74
103
  "./popover": {
75
104
  "types": "./dist/popover.d.ts",
76
105
  "default": "./dist/popover.js"
77
106
  },
107
+ "./progress": {
108
+ "types": "./dist/progress.d.ts",
109
+ "default": "./dist/progress.js"
110
+ },
111
+ "./radio-group": {
112
+ "types": "./dist/radio-group.d.ts",
113
+ "default": "./dist/radio-group.js"
114
+ },
78
115
  "./select": {
79
116
  "types": "./dist/select.d.ts",
80
117
  "default": "./dist/select.js"
@@ -87,13 +124,21 @@
87
124
  "types": "./dist/skeleton.d.ts",
88
125
  "default": "./dist/skeleton.js"
89
126
  },
127
+ "./sonner": {
128
+ "types": "./dist/sonner.d.ts",
129
+ "default": "./dist/sonner.js"
130
+ },
90
131
  "./sortable-table-head": {
91
132
  "types": "./dist/sortable-table-head.d.ts",
92
133
  "default": "./dist/sortable-table-head.js"
93
134
  },
94
- "./sonner": {
95
- "types": "./dist/sonner.d.ts",
96
- "default": "./dist/sonner.js"
135
+ "./spinner": {
136
+ "types": "./dist/spinner.d.ts",
137
+ "default": "./dist/spinner.js"
138
+ },
139
+ "./switch": {
140
+ "types": "./dist/switch.d.ts",
141
+ "default": "./dist/switch.js"
97
142
  },
98
143
  "./table": {
99
144
  "types": "./dist/table.d.ts",
@@ -123,10 +168,18 @@
123
168
  "types": "./dist/toggle-group.d.ts",
124
169
  "default": "./dist/toggle-group.js"
125
170
  },
171
+ "./tooltip": {
172
+ "types": "./dist/tooltip.d.ts",
173
+ "default": "./dist/tooltip.js"
174
+ },
126
175
  "./form/date-picker-field": {
127
176
  "types": "./dist/form/date-picker-field.d.ts",
128
177
  "default": "./dist/form/date-picker-field.js"
129
178
  },
179
+ "./form/date-time-picker-field": {
180
+ "types": "./dist/form/date-time-picker-field.d.ts",
181
+ "default": "./dist/form/date-time-picker-field.js"
182
+ },
130
183
  "./form/field-error": {
131
184
  "types": "./dist/form/field-error.d.ts",
132
185
  "default": "./dist/form/field-error.js"
@@ -0,0 +1,139 @@
1
+ ---
2
+ name: atlas-react-kit
3
+ description: Build and change React UI with the atlas-react-kit design system (Atlas) instead of hand-rolled markup or another UI library. Use this whenever the project depends on atlas-react-kit (check package.json) and the task touches UI, such as pages, forms, inputs, pickers, selects, dialogs, tables, cards, tabs, badges, toasts, empty or loading states, theming, or dark mode, even if the user never says "Atlas" or "design system". Also use it when the user mentions Atlas components, design tokens, or making UI match the kit.
4
+ ---
5
+
6
+ # Atlas React Kit
7
+
8
+ atlas-react-kit is a shared React 19 + Tailwind 4 component library with a fixed
9
+ look: square corners, semantic color tokens, uppercase tracked headings, and a
10
+ purple "selected" wash for active states. Apps compose its components; they do
11
+ not restyle or fork them. Following that keeps every app consistent and makes
12
+ kit upgrades a version bump instead of a merge.
13
+
14
+ ## 1. Read the docs for the installed version
15
+
16
+ The package ships its own guides, versioned with the code. Read both before
17
+ writing UI, because props and rules change between releases and your memory of
18
+ a previous version may be wrong:
19
+
20
+ ```sh
21
+ KIT=$(dirname "$(node -p "require.resolve('atlas-react-kit/package.json')")")
22
+ cat "$KIT/DESIGN_RULES.md" "$KIT/COMPONENTS.md"
23
+ ```
24
+
25
+ `COMPONENTS.md` lists every import subpath and its props. When a prop is not
26
+ listed there, check the type declarations in `$KIT/dist/<subpath>.d.ts` rather
27
+ than guessing.
28
+
29
+ ## 2. Check the project setup once
30
+
31
+ Components render unstyled unless the app's global stylesheet imports the theme
32
+ and tells Tailwind to scan the kit's compiled files:
33
+
34
+ ```css
35
+ @import "tailwindcss";
36
+ @import "atlas-react-kit/theme.css";
37
+ @source "../node_modules/atlas-react-kit/dist"; /* relative to this CSS file */
38
+ ```
39
+
40
+ Dark mode needs `ThemeProvider` from `atlas-react-kit/theme-provider` near the
41
+ root (or a `dark` class on an ancestor). Toasts need one `<Toaster />` from
42
+ `atlas-react-kit/sonner`; call `toast()` from the `sonner` package, which the
43
+ app should list as its own dependency. Fix missing setup rather than working
44
+ around unstyled components.
45
+
46
+ ## 3. Pick the kit component for the job
47
+
48
+ Import each component from its subpath, e.g.
49
+ `import { Button } from "atlas-react-kit/button"`. There is no root barrel, and
50
+ `dist/` or `internal/` paths are not public API.
51
+
52
+ | Need | Use |
53
+ | ----------------------------------------------- | --------------------------------------------------------------------------------------------------- |
54
+ | Short fixed list (a handful of options) | `Select` family from `select` |
55
+ | Long or searchable list (timezones, currencies) | `Combobox` from `combobox` with `{ value, label }[]` options |
56
+ | One date, or a from/to range | `DatePickerButton` from `date-picker` (`mode="range"` for ranges) |
57
+ | Date and time (not `datetime-local`) | `DateTimePickerButton` from `date-time-picker` |
58
+ | Labelled field with error text | `FormTextField`, `FormSelectField`, `FormDatePickerField`, `FormDateTimePickerField` from `form/*` |
59
+ | Form-level error (save failed) | `FormFeedbackField` from `form/feedback-field` |
60
+ | Content panels on one page | `Tabs`, `TabsList`, `TabsTrigger`, `TabsContent` from `tabs` |
61
+ | View switch that changes the URL | `TabsNav` with links: `TabsNavLink` from `next/tabs-nav-link` in Next, else `tabsTriggerVariants()` |
62
+ | Status chip (paid, pending, failed) | `Badge variant="soft"` with `tone="success"`, `"warning"`, `"destructive"`, or `"neutral"` |
63
+ | Data table | `Table` family from `table`; `SortableTableHead`; `TableBodySkeleton` while loading |
64
+ | Nothing to show yet | `EmptyPanel` from `empty-panel` |
65
+ | Grouped content | `Card` family from `card`; `<Card flush>` for an edge-to-edge table or list |
66
+ | Modal | `Dialog` family from `dialog` |
67
+ | Label for an icon button, or a short hint | `Tooltip` from `tooltip`; keep `aria-label` on icon buttons |
68
+ | Row actions or an overflow menu of commands | `DropdownMenu` family from `dropdown-menu` |
69
+ | Setting that applies immediately | `Switch` from `switch`; `Checkbox` for choices submitted with a form |
70
+ | One choice from a few visible options | `RadioGroup` and `RadioGroupItem` from `radio-group` |
71
+ | Inline message about a page or section | `Alert` from `alert` (`success`, `warning`, `destructive` variants) |
72
+ | Person or workspace image | `Avatar`, `AvatarImage`, `AvatarFallback` from `avatar` |
73
+ | Sections that expand in place | `Accordion` family from `accordion` |
74
+ | Pages of a long list | `Pagination` family from `pagination` |
75
+ | Where the page sits in a hierarchy | `Breadcrumb` family from `breadcrumb` |
76
+ | Work in progress | `Progress` from `progress` when the percent is known, else `Spinner` from `spinner` |
77
+ | Crash recovery around a section | `ErrorBoundary` from `error-boundary` |
78
+ | Conditional classes | `cn` from `utils` |
79
+
80
+ If nothing fits, build the piece in the app from kit primitives and semantic
81
+ tokens. Do not copy kit source into the app; a missing shared component belongs
82
+ in the atlas-design-system repository.
83
+
84
+ Existing app code may predate the kit (raw colors, rounded boxes, hand-styled
85
+ buttons). Don't copy that style into new UI. Move what the task touches onto kit
86
+ components and tokens, and mention the rest to the user instead of rewriting
87
+ files the task doesn't need.
88
+
89
+ ## 4. Style with tokens, not values
90
+
91
+ These rules are what make Atlas look like Atlas. `DESIGN_RULES.md` has the full
92
+ list; the ones that come up most:
93
+
94
+ - **Semantic colors only**: `bg-primary`, `text-muted-foreground`,
95
+ `border-border`, `bg-card`, `text-destructive`. Raw palette classes
96
+ (`bg-purple-600`, `text-gray-500`) and paired `dark:` colors drift from the
97
+ theme; tokens already switch with light and dark.
98
+ - **Selected and active state**: `bg-selected text-selected-foreground` for the
99
+ current nav item, chip, row, or toggle. Grey fills read as hover, not
100
+ selection.
101
+ - **Status**: `success`, `warning`, and `destructive` for good, caution, and
102
+ bad. Use them as text or soft tints (the `Badge` tones), which meet AA in
103
+ both themes. Don't override a soft badge's colors. There is no foreground
104
+ token for text on a solid status fill, so avoid that combination.
105
+ - **Brand-colored text**: `text-selected-foreground`, not `text-primary`,
106
+ which is too dark to read on the dark background.
107
+ - **Accessibility**: the kit targets WCAG 2.2 AA. Give every control a name
108
+ (`Label htmlFor`, or `aria-label` on icon-only buttons and an unlabelled
109
+ `Combobox`), keep the shipped focus rings, and don't lower contrast with raw
110
+ colors or opacity.
111
+ - **Square corners**: primitives have none. Use `rounded-full` only for real
112
+ circles (avatars, dots). Adding `rounded-md` to a card or button breaks the
113
+ system.
114
+ - **Don't restyle kit controls**: `Input`, `SelectTrigger`, `DatePickerButton`,
115
+ `DateTimePickerButton`, and `Combobox` share 40px form-control chrome.
116
+ Overriding their height, padding, or border makes one field look different
117
+ from its neighbors.
118
+ - **Layout overrides**: `CardContent className="px-0"` makes one section flush
119
+ without `!important`. Tables already scroll horizontally inside cards, so do
120
+ not wrap them in another overflow container.
121
+ - **Headings**: `CardTitle` and `DialogTitle` already apply the uppercase,
122
+ tracked heading style; use `font-heading` for other headings.
123
+
124
+ ## 5. Check before you finish
125
+
126
+ Run these over the files you changed and fix what they find (an app's own
127
+ brand colors, kept outside kit components, are the only expected hits):
128
+
129
+ ```sh
130
+ # Raw palette colors instead of tokens
131
+ grep -nE '\b(bg|text|border|ring|fill|stroke)-(slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-[0-9]{2,3}\b' <files>
132
+ # Rounded corners on primitives
133
+ grep -nE '\brounded-(sm|md|lg|xl|2xl|3xl)\b' <files>
134
+ # Non-public imports
135
+ grep -nE "from ['\"]atlas-react-kit(/dist|/internal|['\"])" <files>
136
+ ```
137
+
138
+ Then typecheck: wrong prop names usually mean the component was guessed rather
139
+ than read from `COMPONENTS.md` or the `.d.ts`.
package/theme.css CHANGED
@@ -96,18 +96,18 @@
96
96
  --secondary: oklch(0.967 0.001 286.375);
97
97
  --secondary-foreground: oklch(0.21 0.006 285.885);
98
98
  --muted: oklch(0.97 0 0);
99
- --muted-foreground: oklch(0.556 0 0);
99
+ /* 4.8:1 on --muted and 5.3:1 on the page, so muted text also works on muted fills. */
100
+ --muted-foreground: oklch(0.53 0 0);
100
101
  --accent: oklch(0.97 0 0);
101
102
  --accent-foreground: oklch(0.205 0 0);
102
103
  /* Soft brand-purple wash for selected/active chrome (nav, tabs, chips, rows). */
103
104
  --selected: color-mix(in oklch, var(--primary) 12%, var(--background));
104
105
  --selected-foreground: var(--primary);
105
- --destructive: oklch(0.577 0.245 27.325);
106
+ /* AA as text on the page (6.4:1) and on its own /10 and /15 tints. */
107
+ --destructive: oklch(0.505 0.213 27.518);
106
108
  /* Status tones: AA (4.5:1) as text on the page, on a card, and on a /10–/25
107
109
  tint of themselves; also used as solid fills (meters). No -foreground:
108
- nothing puts text on a solid fill. See DESIGN_RULES.md. --destructive
109
- (above) passes as text on the page and card but not yet on its own tint
110
- in light mode (about 3.6:1 on /15). */
110
+ nothing puts text on a solid fill. See DESIGN_RULES.md. */
111
111
  --success: oklch(0.45 0.11 160);
112
112
  --warning: oklch(0.48 0.115 70);
113
113
  --border: oklch(0.922 0 0);