atlas-react-kit 0.13.0 → 1.1.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 (103) hide show
  1. package/COMPONENTS.md +144 -120
  2. package/DESIGN_RULES.md +53 -32
  3. package/README.md +12 -53
  4. package/dist/accordion.d.ts +1 -0
  5. package/dist/avatar.d.ts +1 -0
  6. package/dist/badge.d.ts +2 -3
  7. package/dist/breadcrumb.d.ts +2 -3
  8. package/dist/button.d.ts +2 -3
  9. package/dist/calendar.d.ts +2 -0
  10. package/dist/card.d.ts +3 -0
  11. package/dist/chart.d.ts +152 -0
  12. package/dist/chart.js +177 -0
  13. package/dist/color-legend.d.ts +8 -0
  14. package/dist/combobox.d.ts +13 -4
  15. package/dist/combobox.js +1 -8
  16. package/dist/date-picker.d.ts +26 -3
  17. package/dist/date-picker.js +37 -31
  18. package/dist/date-time-picker.d.ts +5 -2
  19. package/dist/date-time-picker.js +25 -38
  20. package/dist/dialog.d.ts +1 -0
  21. package/dist/dialog.js +4 -2
  22. package/dist/dropdown-menu.d.ts +9 -8
  23. package/dist/empty-panel.d.ts +8 -2
  24. package/dist/empty-panel.js +2 -2
  25. package/dist/error-boundary.d.ts +6 -1
  26. package/dist/field.d.ts +3 -0
  27. package/dist/field.js +1 -1
  28. package/dist/form/checkbox-field.d.ts +45 -0
  29. package/dist/form/checkbox-field.js +39 -0
  30. package/dist/form/combobox-field.d.ts +13 -0
  31. package/dist/form/combobox-field.js +14 -0
  32. package/dist/form/control-field.d.ts +6 -7
  33. package/dist/form/date-picker-field.d.ts +12 -21
  34. package/dist/form/date-picker-field.js +5 -4
  35. package/dist/form/date-time-picker-field.d.ts +7 -22
  36. package/dist/form/date-time-picker-field.js +4 -3
  37. package/dist/form/feedback-field.d.ts +2 -0
  38. package/dist/form/field-error.d.ts +35 -3
  39. package/dist/form/field-shell.d.ts +35 -6
  40. package/dist/form/field-shell.js +1 -1
  41. package/dist/form/focus-first-invalid.d.ts +28 -0
  42. package/dist/form/focus-first-invalid.js +29 -0
  43. package/dist/form/markdown-field.d.ts +4 -16
  44. package/dist/form/select-field.d.ts +13 -7
  45. package/dist/form/select-field.js +3 -2
  46. package/dist/form/text-field.d.ts +29 -10
  47. package/dist/form/text-field.js +4 -0
  48. package/dist/form/toggle-group-field.d.ts +30 -0
  49. package/dist/form/toggle-group-field.js +19 -0
  50. package/dist/info-tip.d.ts +10 -1
  51. package/dist/internal/as-child.d.ts +8 -0
  52. package/dist/internal/as-child.js +1 -0
  53. package/dist/internal/form-control.d.ts +3 -2
  54. package/dist/internal/form-control.js +3 -2
  55. package/dist/internal/kanban-layout.d.ts +55 -0
  56. package/dist/internal/kanban-layout.js +80 -0
  57. package/dist/internal/kanban-plugins.d.ts +17 -0
  58. package/dist/internal/kanban-plugins.js +17 -0
  59. package/dist/internal/listbox-navigation.d.ts +0 -11
  60. package/dist/internal/listbox-navigation.js +2 -2
  61. package/dist/internal/listbox-option.d.ts +0 -15
  62. package/dist/internal/listbox-option.js +1 -1
  63. package/dist/internal/use-drag-scroll.d.ts +17 -0
  64. package/dist/internal/use-drag-scroll.js +98 -0
  65. package/dist/internal/use-media-query.d.ts +4 -2
  66. package/dist/internal/use-media-query.js +11 -15
  67. package/dist/kanban.d.ts +84 -0
  68. package/dist/kanban.js +197 -0
  69. package/dist/markdown.d.ts +2 -0
  70. package/dist/meter.d.ts +7 -0
  71. package/dist/nav-link.d.ts +15 -6
  72. package/dist/nav-link.js +14 -4
  73. package/dist/next/nav-link.d.ts +6 -3
  74. package/dist/next/nav-link.js +8 -6
  75. package/dist/next/period-stepper.d.ts +11 -0
  76. package/dist/next/period-stepper.js +16 -0
  77. package/dist/next/tabs-nav-link.d.ts +6 -0
  78. package/dist/next/tabs-nav-link.js +8 -3
  79. package/dist/page-header.d.ts +19 -0
  80. package/dist/page-header.js +11 -0
  81. package/dist/pagination.d.ts +2 -0
  82. package/dist/period-stepper.d.ts +32 -0
  83. package/dist/period-stepper.js +27 -0
  84. package/dist/picker-shell.d.ts +33 -18
  85. package/dist/picker-shell.js +12 -10
  86. package/dist/progress.d.ts +1 -0
  87. package/dist/search-picker.d.ts +12 -0
  88. package/dist/select.d.ts +1 -0
  89. package/dist/skip-link.d.ts +7 -0
  90. package/dist/skip-link.js +10 -0
  91. package/dist/slider.d.ts +11 -0
  92. package/dist/sortable-table-head.d.ts +2 -0
  93. package/dist/stat-tile.d.ts +5 -0
  94. package/dist/table-body-skeleton.d.ts +2 -0
  95. package/dist/table.d.ts +1 -0
  96. package/dist/tabs.d.ts +27 -12
  97. package/dist/tabs.js +23 -10
  98. package/dist/toast.d.ts +7 -1
  99. package/dist/toggle-group.d.ts +9 -2
  100. package/dist/toggle-group.js +2 -1
  101. package/package.json +72 -6
  102. package/skills/atlas-react-kit/SKILL.md +46 -40
  103. package/theme.css +13 -0
package/DESIGN_RULES.md CHANGED
@@ -32,6 +32,36 @@ its own soft tint in both themes. Status tones have no paired foreground token;
32
32
  avoid placing text on solid status fills. Application-specific identity colors
33
33
  belong to the application.
34
34
 
35
+ ### Public tokens and slots
36
+
37
+ These are public API from 1.0, so apps may use them and a rename or removal
38
+ is a breaking change:
39
+
40
+ - Color tokens and their `--color-*` utilities: `background`, `foreground`,
41
+ `card`, `popover`, `primary`, `secondary`, `muted`, `accent`, `selected`
42
+ (each with its `-foreground` pair where one exists), `destructive`,
43
+ `success`, `warning`, `border`, `input`, `ring`, `chart-1` to `chart-5`,
44
+ and the `sidebar` set.
45
+ - `--radius` and the `--radius-*` scale, and `--font-heading`.
46
+ - Every `data-slot` listed under "Styling parts" in
47
+ [COMPONENTS.md](./COMPONENTS.md#styling-parts).
48
+
49
+ Everything else in `theme.css` is internal and may change in a minor
50
+ release: the scroll-shadow variables (`--scroll-fade`, `--scroll-fill`,
51
+ `--table-scroll-fade`, `--tabs-scroll-fade`) and any `data-slot` not listed
52
+ in COMPONENTS.md.
53
+
54
+ ### Prop names
55
+
56
+ - A controlled value is `value` with `onValueChange`; open state is `open`
57
+ with `onOpenChange`; a checked state is `checked` with `onCheckedChange`.
58
+ - Sizes run `sm`, `default`, `lg`; `Button` adds `xs` and `icon-*` sizes.
59
+ - `variant="default"` is the plain look; other variants name what they mean
60
+ (`outline`, `ghost`, `destructive`).
61
+
62
+ Deprecated aliases (`onChange` on the date pickers, `EmptyPanel`
63
+ `size="md"`) keep working until 2.0.
64
+
35
65
  ## Accessibility
36
66
 
37
67
  The target is WCAG 2.2 AA. CI runs axe-core's WCAG 2.2 A and AA rules against
@@ -99,13 +129,10 @@ or the viewport minus gutters on small screens.
99
129
 
100
130
  Use `EmptyPanel` for empty states, `TableBodySkeleton` for loading table bodies,
101
131
  and `<Card flush>` for an edge-to-edge list or table. `Table` scrolls wide
102
- columns inside itself (`min-w-0 overflow-x-auto`) and tightens cell padding
103
- below `sm`. `CardContent className="px-0"`
104
- overrides body padding without `!px-0` when only the body should go flush. Cards
105
- keep `min-h-min shrink-0` so they do not collapse inside a bounded flex
106
- column, and `min-w-0` so nested tables scroll instead of widening the card.
107
- `Dialog` already scrolls inside the viewport on small screens; avoid
108
- additional height limits.
132
+ columns inside itself, `Card` neither collapses in a bounded flex column nor
133
+ widens around a nested table, and `Dialog` already scrolls inside the
134
+ viewport on small screens; avoid additional height limits. The classes each
135
+ one uses are in its COMPONENTS.md row.
109
136
 
110
137
  Apps decide how wide and tall containers and overlays are. The kit sets
111
138
  intrinsic control sizes (the button, input, tab and row heights, icons,
@@ -114,12 +141,10 @@ clamps overlays to the viewport, but no design widths or heights on
114
141
  dialogs, popovers, menus, or lists. Set them with `className`: give each
115
142
  `DialogContent` a width such as `sm:max-w-md`, and each `PopoverContent`
116
143
  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`.
144
+ reaches, a `*ClassName` prop names it, with its default where there is one
145
+ (COMPONENTS.md, "Styling parts"). The few parts that keep a reading width or
146
+ a starting height (`Tooltip`, `Textarea`) take `className` to override it;
147
+ toasts keep their width.
123
148
 
124
149
  Parents do not force sizes on their children. A vertical `Field` stretches
125
150
  its controls through the flex column, so a control's own width class (such
@@ -142,32 +167,28 @@ triggers as outline buttons in application code.
142
167
  error sits below the control. It takes no space until it shows, so a row of
143
168
  fields keeps its controls on one top edge only with `items-start`
144
169
  (`grid grid-cols-2 items-start gap-3`); the default `stretch` is fine for a
145
- single column. Wrap a control the kit does not ship (a combobox, a segmented
170
+ single column. Wrap a control the kit has no form field for (a colour
146
171
  picker) in `FormControlField` instead of `Field` and `FieldLabel`, whose
147
- `gap-3` spacing does not match. A field shows its error once
148
- `meta.isTouched` is true or `submitted` is set, never on a field the person
149
- has not reached yet.
172
+ `gap-3` spacing does not match. A field shows its error once the person has
173
+ touched it (`meta.isTouched`), never on a field they have not reached yet.
174
+ `submitted` is for form libraries that do not mark every field touched on
175
+ submit; TanStack Form does, so its users pass `meta` only.
150
176
 
151
177
  Use `Tabs` with actual `TabsContent` panels. For URL view switches, use a labelled
152
- `TabsNav` with links and `aria-current="page"` on the active link. The optional
153
- Next adapter provides `TabsNavLink` with this behavior. App-shell destinations
178
+ `TabsNav` of `TabsNavLink`s with `active` from the app. App-shell destinations
154
179
  (Calendar, Tasks, Settings) use `NavLink` from `nav-link`: a pill with `active`
155
- from the app, not tabs. Pass `asChild` for a router link, or use the optional
156
- Next adapter `next/nav-link`. `TabsNav` and `TabsList`
157
- scroll overflowing labels inside themselves (`overflow-x-auto overflow-y-hidden`,
158
- with padding for the active border and focus ring counted in the scroller
159
- height, and an edge fade like `Table`). Do not wrap them in another
160
- `overflow-x-auto` — that pairing forces `overflow-y: auto` and a stray
161
- vertical scrollbar.
162
-
163
- Use `FormTextField`, `FormSelectField`, `FormDatePickerField`,
164
- `FormDateTimePickerField`, `FormMarkdownField`, and `FormFeedbackField` for
165
- portable form controls, and `FieldError` for field messages. `Combobox` (popover)
180
+ from the app, not tabs. Both links take `asChild` for a router link, or come
181
+ as Next adapters (`next/tabs-nav-link`, `next/nav-link`); `isNavActive`
182
+ compares the pathname with a link's `href`. `TabsNav` and `TabsList` scroll overflowing
183
+ labels inside themselves, like `Table`. Do not wrap them in another
184
+ horizontal scroller: that pairing adds a stray vertical scrollbar.
185
+
186
+ Use the `form/*` fields (one row each in COMPONENTS.md) for portable form
187
+ controls, and `FieldError` for field messages. `Combobox` (popover)
166
188
  and `SearchPicker` (always-visible list), `DatePickerButton` (including
167
189
  `mode="range"`), `DateTimePickerButton`, `Slider`, `Meter` (scale with ticks;
168
190
  `Progress` is a plain 0–100 job bar), `InfoTip` (longer than `Tooltip`),
169
191
  `Markdown`, `ColorLegend`, `StatTile`, `ThemeProvider`, `ErrorBoundary`, and
170
192
  `SortableTableHead` are first-class kit pieces. Domain data and application
171
193
  shells stay with the application: currency or timezone option lists,
172
- query-library error views, WYSIWYG editors, kanban boards, and dashboard
173
- widgets.
194
+ query-library error views, and WYSIWYG editors.
package/README.md CHANGED
@@ -1,14 +1,14 @@
1
1
  # Atlas React Kit
2
2
 
3
3
  Shared React components and a Tailwind theme with square corners, semantic
4
- colors, and distinct selected states. 0.6.0 added generic pieces apps were
5
- keeping locally (`InfoTip`, `ColorLegend`, `SearchPicker`, `Markdown`,
6
- `StatTile`, `Meter`, `Slider`) plus the ToggleGroup selected-state fix.
4
+ colors, and distinct selected states. From 1.0 the API is stable: breaking
5
+ changes to stable components only ship in a major release (see
6
+ [CONTRIBUTING.md](./CONTRIBUTING.md#versions-and-releases)).
7
7
  React 19, Tailwind 4, and Node >=20.19 are required. Next 16 is needed only
8
- when using `next/nav-link` or `next/tabs-nav-link`.
8
+ when using `next/nav-link`, `next/period-stepper`, or `next/tabs-nav-link`.
9
9
 
10
10
  ```sh
11
- npm install --save-exact atlas-react-kit@0.12.0
11
+ npm install --save-exact atlas-react-kit@1.1.0
12
12
  ```
13
13
 
14
14
  Import components by subpath:
@@ -42,8 +42,9 @@ this example assumes it is one directory below the project root.
42
42
  ```
43
43
 
44
44
  The theme includes animations and shared light/dark tokens. Wrap the tree with
45
- `ThemeProvider` from `atlas-react-kit/theme-provider`, or set the `dark` class
46
- on an ancestor to activate dark colors. Supply your own font configuration;
45
+ `ThemeProvider` from `atlas-react-kit/theme-provider` (install
46
+ `next-themes@^0.4.6` first), or set the `dark` class on an ancestor to activate
47
+ dark colors. Supply your own font configuration;
47
48
  `font-heading` uses the configured sans font. Application-specific tokens stay
48
49
  with the application.
49
50
 
@@ -54,7 +55,8 @@ with the application.
54
55
  - [Design rules](./DESIGN_RULES.md): tokens, accessibility, writing, and
55
56
  composition. This guide ships in the npm package.
56
57
  - [Contributing](./CONTRIBUTING.md): component changes, checks, and releases.
57
- - [Roadmap to 1.0](./ROADMAP-1.0.md): decisions needed before the 1.0 release.
58
+ - [Decisions behind 1.0 (record)](./ROADMAP-1.0.md): the closed roadmap that
59
+ led to 1.0.0.
58
60
  - [Changelog](./CHANGELOG.md): published and upcoming changes.
59
61
 
60
62
  ## AI coding assistants
@@ -85,51 +87,8 @@ and package entry points for search and indexing tools.
85
87
 
86
88
  ## Development and releases
87
89
 
88
- ```sh
89
- npm ci
90
- npm run check
91
- npm test
92
- npm pack
93
- npm ci --prefix site
94
- npm run build --prefix site
95
- npm run preview --prefix site
96
- ```
97
-
98
- `tsc` emits individual ESM files and TypeScript declarations. CSS is shipped
99
- without compilation and processed by the consuming application's Tailwind build.
100
- The public site is Storybook in `site/`. `npm run build --prefix site`
101
- typechecks the stories, builds Storybook into `site/dist`, then checks the
102
- static output. Vercel serves that directory; `/playground` and `/storybook`
103
- redirect to `/`. The site and Storybook dependencies are not part of the npm
104
- package. Optional feature libraries (markdown, dates, toasts, theme, Next) are
105
- peer dependencies and are required only when those entry points are imported.
106
- The package does not bundle React.
107
-
108
- Run `npm run dev --prefix site` (or `npm run storybook --prefix site`) for the
109
- playground on port 6006. Existing examples live in `stories/` and are imported
110
- by CSF story files in `site/stories/`, so component tests and previews share the
111
- same examples. Add Storybook entries there when adding an example; the
112
- static-output check reports any existing demo that has no Storybook entry.
113
- Interactive stories use Storybook args so changing a control or interacting with
114
- the component updates the same value. Configuration is in `site/.storybook/`.
115
-
116
- The Storybook preview disables the kit's theme shortcut and uses the theme
117
- toolbar instead. The accessibility panel checks the rendered story; it does not
118
- replace keyboard or assistive-technology testing.
119
-
120
- Bumping the version in `package.json` and merging to `main` (path-filtered
121
- to package files) or running `workflow_dispatch` publishes through npm OIDC
122
- trusted publishing after check, test, and pack gates. Versions already on npm
123
- are skipped.
124
-
125
- If trusted publishing is not configured, pack and publish a validated
126
- artifact with the package owner's npm login:
127
-
128
- ```sh
129
- mkdir -p release-artifacts
130
- npm pack --pack-destination release-artifacts
131
- npm publish ./release-artifacts/atlas-react-kit-0.12.0.tgz --access public
132
- ```
90
+ Setup, checks, the component checklist, and releases are in
91
+ [CONTRIBUTING.md](./CONTRIBUTING.md).
133
92
 
134
93
  Pin an exact package version in consuming applications. Roll back an upgrade by
135
94
  restoring the previous version and lockfile. License: MIT; upstream notices are
@@ -5,6 +5,7 @@ declare function Accordion({ ...props }: React.ComponentProps<typeof AccordionPr
5
5
  declare function AccordionItem({ className, ...props }: React.ComponentProps<typeof AccordionPrimitive.Item>): React.JSX.Element;
6
6
  /** `headerClassName` styles the heading wrapper (`data-slot="accordion-header"`). */
7
7
  declare function AccordionTrigger({ className, headerClassName, children, ...props }: React.ComponentProps<typeof AccordionPrimitive.Trigger> & {
8
+ /** Classes for the heading wrapper around the trigger. */
8
9
  headerClassName?: string;
9
10
  }): React.JSX.Element;
10
11
  /**
package/dist/avatar.d.ts CHANGED
@@ -6,6 +6,7 @@ import { Avatar as AvatarPrimitive } from "radix-ui";
6
6
  * replaces it.
7
7
  */
8
8
  declare function Avatar({ className, size, ...props }: React.ComponentProps<typeof AvatarPrimitive.Root> & {
9
+ /** `sm` 24px, `default` 32px, `lg` 40px. Default `default`. */
9
10
  size?: "sm" | "default" | "lg";
10
11
  }): React.JSX.Element;
11
12
  declare function AvatarImage({ className, ...props }: React.ComponentProps<typeof AvatarPrimitive.Image>): React.JSX.Element;
package/dist/badge.d.ts CHANGED
@@ -1,10 +1,9 @@
1
1
  import * as React from "react";
2
2
  import { type VariantProps } from "class-variance-authority";
3
+ import type { AsChildProps } from "./internal/as-child.js";
3
4
  declare const badgeVariants: (props?: ({
4
5
  variant?: "default" | "destructive" | "ghost" | "link" | "outline" | "secondary" | "soft" | null | undefined;
5
6
  tone?: "destructive" | "neutral" | "success" | "warning" | null | undefined;
6
7
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
7
- declare function Badge({ className, variant, tone, asChild, ...props }: React.ComponentProps<"span"> & VariantProps<typeof badgeVariants> & {
8
- asChild?: boolean;
9
- }): React.JSX.Element;
8
+ declare function Badge({ className, variant, tone, asChild, ...props }: React.ComponentProps<"span"> & VariantProps<typeof badgeVariants> & AsChildProps): React.JSX.Element;
10
9
  export { Badge, badgeVariants };
@@ -1,4 +1,5 @@
1
1
  import * as React from "react";
2
+ import type { AsChildProps } from "./internal/as-child.js";
2
3
  /**
3
4
  * Where the current page sits in a hierarchy. The last item is
4
5
  * `BreadcrumbPage` (plain text with `aria-current="page"`), not a link.
@@ -7,9 +8,7 @@ declare function Breadcrumb({ ...props }: React.ComponentProps<"nav">): React.JS
7
8
  declare function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">): React.JSX.Element;
8
9
  declare function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">): React.JSX.Element;
9
10
  /** 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;
11
+ declare function BreadcrumbLink({ asChild, className, ...props }: React.ComponentProps<"a"> & AsChildProps): React.JSX.Element;
13
12
  declare function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">): React.JSX.Element;
14
13
  declare function BreadcrumbSeparator({ children, className, ...props }: React.ComponentProps<"li">): React.JSX.Element;
15
14
  declare function BreadcrumbEllipsis({ className, ...props }: React.ComponentProps<"span">): React.JSX.Element;
package/dist/button.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import * as React from "react";
2
2
  import { type VariantProps } from "class-variance-authority";
3
+ import type { AsChildProps } from "./internal/as-child.js";
3
4
  /**
4
5
  * Outline and ghost hover/focus: selected-foreground border, selected wash, and
5
6
  * `selected-foreground` text (primary in light mode, a lighter purple that
@@ -11,7 +12,5 @@ declare const buttonVariants: (props?: ({
11
12
  variant?: "default" | "destructive" | "ghost" | "link" | "outline" | "secondary" | null | undefined;
12
13
  size?: "default" | "icon" | "icon-lg" | "icon-sm" | "icon-xs" | "lg" | "sm" | "xs" | null | undefined;
13
14
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
14
- declare function Button({ className, variant, size, asChild, ...props }: React.ComponentProps<"button"> & VariantProps<typeof buttonVariants> & {
15
- asChild?: boolean;
16
- }): React.JSX.Element;
15
+ declare function Button({ className, variant, size, asChild, ...props }: React.ComponentProps<"button"> & VariantProps<typeof buttonVariants> & AsChildProps): React.JSX.Element;
17
16
  export { Button, buttonVariants, quietButtonInteraction };
@@ -2,9 +2,11 @@ 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
4
  declare function Calendar({ className, classNames, showOutsideDays, captionLayout, buttonVariant, locale, formatters, labels, components, ...props }: React.ComponentProps<typeof DayPicker> & {
5
+ /** Button variant for the previous / next month buttons. Default `ghost`. */
5
6
  buttonVariant?: React.ComponentProps<typeof Button>["variant"];
6
7
  }): React.JSX.Element;
7
8
  declare function CalendarDayButton({ className, day, modifiers, locale, ...props }: React.ComponentProps<typeof DayButton> & {
9
+ /** Locale for the day's `data-day` date string. */
8
10
  locale?: Partial<Locale>;
9
11
  }): React.JSX.Element;
10
12
  export { Calendar, CalendarDayButton };
package/dist/card.d.ts CHANGED
@@ -8,11 +8,14 @@ import * as React from "react";
8
8
  * scroll instead of widening the card. `overflow-hidden` still clips media.
9
9
  */
10
10
  declare function Card({ className, size, flush, role, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, ...props }: React.ComponentProps<"div"> & {
11
+ /** `sm` tightens padding and gaps. Default `default`. */
11
12
  size?: "default" | "sm";
13
+ /** Remove the padding so lists and tables run edge to edge. Default `false`. */
12
14
  flush?: boolean;
13
15
  }): React.JSX.Element;
14
16
  declare function CardHeader({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
15
17
  declare function CardTitle({ as: Component, className, ...props }: React.ComponentProps<"div"> & {
18
+ /** Element to render, e.g. a heading level for the outline. Default `div`. */
16
19
  as?: "div" | "h1" | "h2" | "h3" | "h4";
17
20
  }): React.JSX.Element;
18
21
  declare function CardDescription({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
@@ -0,0 +1,152 @@
1
+ import * as React from "react";
2
+ /**
3
+ * Building blocks for small SVG charts: a plot frame in viewBox units, axis
4
+ * math, a gridline axis, a tooltip, a legend, and pointer/arrow-key
5
+ * scrubbing. The chart itself (bars, lines, what they mean) stays in the
6
+ * app; these pieces keep every chart's axis, readout, and keyboard use alike.
7
+ */
8
+ /** Space around the plot, in viewBox units, for axis labels. */
9
+ export type ChartPadding = {
10
+ /** Above the plot. Default 10. */
11
+ top: number;
12
+ /** Right of the plot. Default 12. */
13
+ right: number;
14
+ /** Below the baseline, for x labels. Default 20. */
15
+ bottom: number;
16
+ /** Left of the plot, for y labels. Default 40. */
17
+ left: number;
18
+ };
19
+ /** A chart's viewBox and the plot box inside it, in viewBox units. */
20
+ export type ChartFrame = {
21
+ /** The whole chart's width. */
22
+ width: number;
23
+ /** The whole chart's height. */
24
+ height: number;
25
+ /** The `viewBox` attribute for the chart's `<svg>`. */
26
+ viewBox: string;
27
+ /** The plot's left edge on x. */
28
+ left: number;
29
+ /** The plot's right edge on x. */
30
+ right: number;
31
+ /** The plot's top edge on y. */
32
+ top: number;
33
+ /** The plot's bottom edge on y: the baseline, where 0 sits. */
34
+ bottom: number;
35
+ /** `right - left`. */
36
+ plotWidth: number;
37
+ /** `bottom - top`. */
38
+ plotHeight: number;
39
+ };
40
+ /**
41
+ * Frame for a chart `height` units tall. Charts that share a `width` and
42
+ * padding line up when stacked. The SVG scales to its box, so these are
43
+ * viewBox units, not pixels.
44
+ */
45
+ export declare function chartFrame(height: number, { width, padding, }?: {
46
+ /** The chart's width in viewBox units. Default 640. */
47
+ width?: number;
48
+ /** Overrides for any side of the default padding. */
49
+ padding?: Partial<ChartPadding>;
50
+ }): ChartFrame;
51
+ /**
52
+ * Round `max` up to a clean axis ceiling (1, 2, 2.5, 4, or 5 × 10^n). The 4
53
+ * step keeps a value just past 25 off a half-empty 50 axis.
54
+ */
55
+ export declare function niceCeil(max: number): number;
56
+ /** `count` even steps from 0 to `niceCeil(max)`, both ends included. */
57
+ export declare function niceTicks(max: number, count?: number): number[];
58
+ /** Linear scale from [0, domainMax] onto [0, rangeMax]; a zero domain maps to 0. */
59
+ export declare function scaleLinear(domainMax: number, rangeMax: number): (value: number) => number;
60
+ /** SVG path through the points (`M x y L x y …`); empty for no points. */
61
+ export declare function linePath(points: ReadonlyArray<{
62
+ x: number;
63
+ y: number;
64
+ }>): string;
65
+ /**
66
+ * A bar with a rounded data end and a square baseline. (x, y) is the top
67
+ * left; the bar grows down to y + h. Pass `r = 0` for a square bar.
68
+ */
69
+ export declare function roundedTopBarPath(x: number, y: number, w: number, h: number, r?: number): string;
70
+ /** Short axis label: 1500 → "1.5k", 2000000 → "2M". */
71
+ export declare function compactNumber(n: number): string;
72
+ type IndexRange = {
73
+ /** The first data index the plot can pick. */
74
+ min: number;
75
+ /** The last data index the plot can pick; may stop short of the axis end. */
76
+ max: number;
77
+ };
78
+ /** The index range plus how many intervals the plot's width spans. */
79
+ type PlotSteps = IndexRange & {
80
+ /** Intervals across the plot width (data points minus one on a full axis). */
81
+ steps: number;
82
+ };
83
+ /**
84
+ * Data index at viewBox x `viewX`. The plot spans `steps` intervals from
85
+ * `min`; the result is clamped to [min, max], so `max` can stop short of the
86
+ * axis (days not reached yet).
87
+ */
88
+ export declare function indexAtPlotX(viewX: number, frame: ChartFrame, { steps, min, max }: PlotSteps): number;
89
+ /** Arrow-key move by `delta`, clamped; with nothing picked it starts at `min`. */
90
+ export declare function stepIndex(current: number | null, delta: 1 | -1, { min, max }: IndexRange): number;
91
+ /**
92
+ * Crosshair state: the pointer or the arrow keys pick a data index in
93
+ * [min, max], and Escape or leaving the plot clears it. Put `svgRef` and the
94
+ * handlers on the chart's `<svg>`, which needs `tabIndex={0}`.
95
+ */
96
+ export declare function useChartScrub({ frame, steps, min, max, }: PlotSteps & {
97
+ /** The chart's frame, for mapping pointer x onto the plot. */
98
+ frame: ChartFrame;
99
+ }): {
100
+ svgRef: React.RefObject<SVGSVGElement | null>;
101
+ index: number | null;
102
+ clear: () => void;
103
+ onPointerMove: (event: React.PointerEvent<SVGSVGElement>) => void;
104
+ onKeyDown: (event: React.KeyboardEvent) => void;
105
+ };
106
+ /**
107
+ * Horizontal gridlines from 0 to `niceCeil(yMax)` with labels in the left
108
+ * padding. Render inside the chart's `<svg>`.
109
+ */
110
+ export declare function ChartYAxisGrid({ frame, yMax, tickCount, format, }: {
111
+ /** The chart's frame; lines span the plot, labels sit in the left padding. */
112
+ frame: ChartFrame;
113
+ /** The largest data value; the axis tops out at `niceCeil(yMax)`. */
114
+ yMax: number;
115
+ /** How many even steps from 0 to the top. Default 4. */
116
+ tickCount?: number;
117
+ /** Label text for a tick value. Default `compactNumber`. */
118
+ format?: (value: number) => string;
119
+ }): React.JSX.Element;
120
+ /**
121
+ * Readout for the hovered or focused point, placed in percent of the chart
122
+ * box (its wrapper must be `relative`). It sits right of the point and
123
+ * flips left past 55% so it does not clip at the edge.
124
+ */
125
+ export declare function ChartTooltip({ xPct, yPct, className, style, children, ...props }: {
126
+ /** The point's x, in percent of the chart box's width. */
127
+ xPct: number;
128
+ /** The point's y, in percent of the chart box's height; capped at 60. */
129
+ yPct: number;
130
+ } & React.ComponentProps<"div">): React.JSX.Element;
131
+ /** A series' line key, shared by tooltip rows and legend keys. */
132
+ type SeriesKeyProps = {
133
+ /** The series name. */
134
+ label: React.ReactNode;
135
+ /** The series stroke, e.g. `stroke-primary`. */
136
+ swatchClassName: string;
137
+ /** Draws the swatch dashed, for a dashed series line. */
138
+ dashed?: boolean;
139
+ };
140
+ /** Tooltip row: the value leads, the label follows, keyed by the series stroke. */
141
+ export declare function ChartTooltipRow({ label, value, swatchClassName, dashed, }: SeriesKeyProps & {
142
+ /** The series' value at the point, shown first in bold. */
143
+ value: React.ReactNode;
144
+ }): React.JSX.Element;
145
+ /** Row of legend keys under a chart. */
146
+ export declare function ChartLegend({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
147
+ /** One legend entry: a solid or dashed line swatch and its label. */
148
+ export declare function ChartLegendKey({ label, swatchClassName, dashed, className, }: SeriesKeyProps & {
149
+ /** Classes for the key's wrapper. */
150
+ className?: string;
151
+ }): React.JSX.Element;
152
+ export {};
package/dist/chart.js ADDED
@@ -0,0 +1,177 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { clamp } from "./internal/math.js";
5
+ import { overlaySurface } from "./internal/overlay-styles.js";
6
+ import { cn } from "./utils.js";
7
+ /** Room for compact y labels on the left and x labels underneath. */
8
+ const defaultPadding = {
9
+ top: 10,
10
+ right: 12,
11
+ bottom: 20,
12
+ left: 40,
13
+ };
14
+ /**
15
+ * Frame for a chart `height` units tall. Charts that share a `width` and
16
+ * padding line up when stacked. The SVG scales to its box, so these are
17
+ * viewBox units, not pixels.
18
+ */
19
+ export function chartFrame(height, { width = 640, padding, } = {}) {
20
+ const pad = { ...defaultPadding, ...padding };
21
+ const plotWidth = width - pad.left - pad.right;
22
+ const plotHeight = height - pad.top - pad.bottom;
23
+ return {
24
+ width,
25
+ height,
26
+ viewBox: `0 0 ${width} ${height}`,
27
+ left: pad.left,
28
+ right: pad.left + plotWidth,
29
+ top: pad.top,
30
+ bottom: pad.top + plotHeight,
31
+ plotWidth,
32
+ plotHeight,
33
+ };
34
+ }
35
+ /**
36
+ * Round `max` up to a clean axis ceiling (1, 2, 2.5, 4, or 5 × 10^n). The 4
37
+ * step keeps a value just past 25 off a half-empty 50 axis.
38
+ */
39
+ export function niceCeil(max) {
40
+ if (max <= 0)
41
+ return 1;
42
+ const pow = 10 ** Math.floor(Math.log10(max));
43
+ for (const step of [1, 2, 2.5, 4, 5]) {
44
+ if (max <= step * pow)
45
+ return step * pow;
46
+ }
47
+ return 10 * pow;
48
+ }
49
+ /** `count` even steps from 0 to `niceCeil(max)`, both ends included. */
50
+ export function niceTicks(max, count = 4) {
51
+ const top = niceCeil(max);
52
+ return Array.from({ length: count + 1 }, (_, i) => (top / count) * i);
53
+ }
54
+ /** Linear scale from [0, domainMax] onto [0, rangeMax]; a zero domain maps to 0. */
55
+ export function scaleLinear(domainMax, rangeMax) {
56
+ return (value) => domainMax === 0 ? 0 : (value / domainMax) * rangeMax;
57
+ }
58
+ function round2(n) {
59
+ return Math.round(n * 100) / 100;
60
+ }
61
+ /** SVG path through the points (`M x y L x y …`); empty for no points. */
62
+ export function linePath(points) {
63
+ return points
64
+ .map((p, i) => `${i === 0 ? "M" : "L"} ${round2(p.x)} ${round2(p.y)}`)
65
+ .join(" ");
66
+ }
67
+ /**
68
+ * A bar with a rounded data end and a square baseline. (x, y) is the top
69
+ * left; the bar grows down to y + h. Pass `r = 0` for a square bar.
70
+ */
71
+ export function roundedTopBarPath(x, y, w, h, r = 4) {
72
+ const rad = Math.min(r, w / 2, h);
73
+ return [
74
+ `M ${round2(x)} ${round2(y + h)}`,
75
+ `L ${round2(x)} ${round2(y + rad)}`,
76
+ `Q ${round2(x)} ${round2(y)} ${round2(x + rad)} ${round2(y)}`,
77
+ `L ${round2(x + w - rad)} ${round2(y)}`,
78
+ `Q ${round2(x + w)} ${round2(y)} ${round2(x + w)} ${round2(y + rad)}`,
79
+ `L ${round2(x + w)} ${round2(y + h)}`,
80
+ "Z",
81
+ ].join(" ");
82
+ }
83
+ function trimZero(n) {
84
+ const fixed = n.toFixed(1);
85
+ return fixed.endsWith(".0") ? fixed.slice(0, -2) : fixed;
86
+ }
87
+ /** Short axis label: 1500 → "1.5k", 2000000 → "2M". */
88
+ export function compactNumber(n) {
89
+ const abs = Math.abs(n);
90
+ if (abs >= 1_000_000)
91
+ return `${trimZero(n / 1_000_000)}M`;
92
+ if (abs >= 1_000)
93
+ return `${trimZero(n / 1_000)}k`;
94
+ return String(Math.round(n));
95
+ }
96
+ /**
97
+ * Data index at viewBox x `viewX`. The plot spans `steps` intervals from
98
+ * `min`; the result is clamped to [min, max], so `max` can stop short of the
99
+ * axis (days not reached yet).
100
+ */
101
+ export function indexAtPlotX(viewX, frame, { steps, min, max }) {
102
+ const index = Math.round(((viewX - frame.left) / frame.plotWidth) * steps) + min;
103
+ return clamp(index, min, max);
104
+ }
105
+ /** Arrow-key move by `delta`, clamped; with nothing picked it starts at `min`. */
106
+ export function stepIndex(current, delta, { min, max }) {
107
+ if (current === null)
108
+ return min;
109
+ return clamp(current + delta, min, max);
110
+ }
111
+ /**
112
+ * Crosshair state: the pointer or the arrow keys pick a data index in
113
+ * [min, max], and Escape or leaving the plot clears it. Put `svgRef` and the
114
+ * handlers on the chart's `<svg>`, which needs `tabIndex={0}`.
115
+ */
116
+ export function useChartScrub({ frame, steps, min, max, }) {
117
+ const svgRef = React.useRef(null);
118
+ const [index, setIndex] = React.useState(null);
119
+ const clear = React.useCallback(() => setIndex(null), []);
120
+ const onPointerMove = React.useCallback((event) => {
121
+ const rect = svgRef.current?.getBoundingClientRect();
122
+ if (!rect)
123
+ return;
124
+ const viewX = ((event.clientX - rect.left) / rect.width) * frame.width;
125
+ setIndex(indexAtPlotX(viewX, frame, { steps, min, max }));
126
+ }, [frame, steps, min, max]);
127
+ const onKeyDown = React.useCallback((event) => {
128
+ if (event.key === "ArrowRight" || event.key === "ArrowLeft") {
129
+ const delta = event.key === "ArrowRight" ? 1 : -1;
130
+ setIndex((current) => stepIndex(current, delta, { min, max }));
131
+ event.preventDefault();
132
+ }
133
+ else if (event.key === "Escape") {
134
+ setIndex(null);
135
+ }
136
+ }, [min, max]);
137
+ return { svgRef, index, clear, onPointerMove, onKeyDown };
138
+ }
139
+ /**
140
+ * Horizontal gridlines from 0 to `niceCeil(yMax)` with labels in the left
141
+ * padding. Render inside the chart's `<svg>`.
142
+ */
143
+ export function ChartYAxisGrid({ frame, yMax, tickCount = 4, format = compactNumber, }) {
144
+ const ticks = niceTicks(yMax, tickCount);
145
+ const top = ticks[ticks.length - 1] ?? 1;
146
+ const y = (value) => frame.bottom - (value / top) * frame.plotHeight;
147
+ return (_jsx("g", { "data-slot": "chart-y-axis", children: ticks.map((tick) => (_jsxs("g", { children: [_jsx("line", { x1: frame.left, y1: y(tick), x2: frame.right, y2: y(tick), strokeWidth: 1, className: "stroke-border" }), _jsx("text", { x: frame.left - 6, y: y(tick) + 3, textAnchor: "end", className: "fill-muted-foreground text-[10px] tabular-nums", children: format(tick) })] }, tick))) }));
148
+ }
149
+ /**
150
+ * Readout for the hovered or focused point, placed in percent of the chart
151
+ * box (its wrapper must be `relative`). It sits right of the point and
152
+ * flips left past 55% so it does not clip at the edge.
153
+ */
154
+ export function ChartTooltip({ xPct, yPct, className, style, children, ...props }) {
155
+ const flip = xPct > 55;
156
+ return (_jsx("div", { role: "status", "data-slot": "chart-tooltip", className: cn(overlaySurface, "pointer-events-none absolute z-10 min-w-32 px-2.5 py-2 text-xs", className), style: {
157
+ left: `${xPct}%`,
158
+ top: `${Math.min(yPct, 60)}%`,
159
+ transform: flip ? "translateX(calc(-100% - 12px))" : "translateX(12px)",
160
+ ...style,
161
+ }, ...props, children: children }));
162
+ }
163
+ function SeriesSwatch({ width, strokeWidth, dashed, className, }) {
164
+ return (_jsx("svg", { width: width, height: "4", "aria-hidden": true, className: "shrink-0", children: _jsx("line", { x1: "0", y1: "2", x2: width, y2: "2", strokeWidth: strokeWidth, strokeDasharray: dashed ? "3 2" : undefined, className: className }) }));
165
+ }
166
+ /** Tooltip row: the value leads, the label follows, keyed by the series stroke. */
167
+ export function ChartTooltipRow({ label, value, swatchClassName, dashed, }) {
168
+ return (_jsxs("div", { "data-slot": "chart-tooltip-row", className: "flex items-center gap-2 py-0.5", children: [_jsx(SeriesSwatch, { width: 14, strokeWidth: 2.5, dashed: dashed, className: swatchClassName }), _jsx("span", { className: "font-semibold tabular-nums", children: value }), _jsx("span", { className: "text-muted-foreground", children: label })] }));
169
+ }
170
+ /** Row of legend keys under a chart. */
171
+ export function ChartLegend({ className, ...props }) {
172
+ return (_jsx("div", { "data-slot": "chart-legend", className: cn("flex flex-wrap gap-x-4 gap-y-1 text-xs text-muted-foreground", className), ...props }));
173
+ }
174
+ /** One legend entry: a solid or dashed line swatch and its label. */
175
+ export function ChartLegendKey({ label, swatchClassName, dashed, className, }) {
176
+ return (_jsxs("span", { "data-slot": "chart-legend-key", className: cn("inline-flex items-center gap-1.5", className), children: [_jsx(SeriesSwatch, { width: 16, strokeWidth: 2, dashed: dashed, className: swatchClassName }), label] }));
177
+ }