@omkarux/vela 0.2.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 (45) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/LICENSE +21 -0
  3. package/README.md +112 -0
  4. package/dist/components/Button/Button.d.ts +32 -0
  5. package/dist/components/Button/Button.js +51 -0
  6. package/dist/components/Button/index.d.ts +2 -0
  7. package/dist/components/ContextualAlert/ContextualAlert.d.ts +18 -0
  8. package/dist/components/ContextualAlert/ContextualAlert.js +49 -0
  9. package/dist/components/ContextualAlert/index.d.ts +2 -0
  10. package/dist/components/Input/Input.d.ts +12 -0
  11. package/dist/components/Input/Input.js +31 -0
  12. package/dist/components/Input/index.d.ts +2 -0
  13. package/dist/components/StatusIndicator/StatusIndicator.d.ts +14 -0
  14. package/dist/components/StatusIndicator/StatusIndicator.js +31 -0
  15. package/dist/components/StatusIndicator/index.d.ts +2 -0
  16. package/dist/components/Tabs/Tabs.d.ts +44 -0
  17. package/dist/components/Tabs/Tabs.js +94 -0
  18. package/dist/components/Tabs/index.d.ts +2 -0
  19. package/dist/components/Toggle/Toggle.d.ts +23 -0
  20. package/dist/components/Toggle/Toggle.js +55 -0
  21. package/dist/components/Toggle/index.d.ts +2 -0
  22. package/dist/index.d.ts +24 -0
  23. package/dist/index.js +16 -0
  24. package/dist/lib/cn.d.ts +5 -0
  25. package/dist/lib/cn.js +6 -0
  26. package/dist/lib/icons.d.ts +7 -0
  27. package/dist/lib/icons.js +32 -0
  28. package/dist/tokens.css +610 -0
  29. package/dist/vela.css +897 -0
  30. package/guidelines/components/button.md +104 -0
  31. package/guidelines/components/contextual-alert.md +59 -0
  32. package/guidelines/components/input.md +65 -0
  33. package/guidelines/components/status-indicator.md +48 -0
  34. package/guidelines/components/tabs.md +71 -0
  35. package/guidelines/components/toggle.md +53 -0
  36. package/guidelines/foundations/color.md +67 -0
  37. package/guidelines/foundations/radius.md +17 -0
  38. package/guidelines/foundations/sizing.md +35 -0
  39. package/guidelines/foundations/spacing.md +28 -0
  40. package/guidelines/foundations/typography.md +34 -0
  41. package/guidelines/llms.txt +33 -0
  42. package/guidelines/overview.md +48 -0
  43. package/guidelines/setup.md +64 -0
  44. package/guidelines/tokens.md +69 -0
  45. package/package.json +84 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,57 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here.
4
+ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
5
+
6
+ ## [0.2.0] — 2026-09-09
7
+
8
+ ### Changed
9
+ - **Re-hued the entire primitive palette** — 52 raw values replaced. Vela now runs a deep
10
+ sea-teal brand (`--vela-primary-500: #0a6c7d`) on a cool slate neutral ramp.
11
+
12
+ Nothing else moved. Not one semantic mapping, not one component file, not one test. That is
13
+ the property the two-layer architecture exists to provide, and this release is the proof:
14
+ a complete visual re-skin is a diff confined to the primitive block.
15
+
16
+ All 76 contrast pairs passed on the first run of the new palette in both themes.
17
+
18
+ ### Fixed
19
+ - Corrected the asserted-pair count in the docs from 78 to 76 (38 pairs × 2 themes).
20
+
21
+ ### Note on versioning
22
+ Every colour in the system changed, so this is a minor bump rather than a patch even though no
23
+ API changed — under 0.x, a visual break is still a break. Consumers pinned to `0.1.x` keep the
24
+ old palette.
25
+
26
+ ## [0.1.0] — 2026-09-09
27
+
28
+ Initial release.
29
+
30
+ ### Added
31
+ - **Components:** Button, Toggle, Input, ContextualAlert, StatusIndicator, Tabs.
32
+ - **Tokens:** 239 CSS custom properties in two layers (primitives → semantics), covering
33
+ colour, spacing, sizing, radius and typography.
34
+ - **Themes:** light and dark. The OS preference applies by default; `data-theme="light"` or
35
+ `data-theme="dark"` on the root element overrides it in either direction.
36
+ - **Taxonomies:** severity (6 levels), risk (3 levels) and status (5 states) as separate,
37
+ non-interchangeable token families, each with a complete role row.
38
+ - **Type-enforced constraints:** `ButtonProps` is a discriminated union; `Toggle` requires a
39
+ `label`; `Tabs.List` requires an `aria-label`; every variant/size/severity/status set is
40
+ closed.
41
+ - **`guidelines/`** shipped inside the package — per-component specifications with closed
42
+ prop tables, token bindings, hard constraints and anti-patterns, plus `llms.txt` as an
43
+ agent entry point.
44
+ - **Contrast audit** (`npm run contrast`) covering 76 pairs across both themes, backed by
45
+ tests so a regression fails CI.
46
+ - **Publish gate** (`npm run verify`) that packs the tarball, installs it into a throwaway
47
+ app, and asserts both that valid usage typechecks and that eight documented rule
48
+ violations are rejected by the compiler.
49
+
50
+ ### Accessibility decisions worth recording
51
+ - `--vela-btn-destructive-bg` binds `red-700`, not `red-500`: red-500 behind a white label is
52
+ 3.93:1 and fails AA.
53
+ - `--vela-text-de-emphasized` binds `grey-600`, not `grey-500` (3.04:1 on white).
54
+ - `--vela-text-severity-success` and `--vela-text-risk-low` bind `green-800`, not `green-700`.
55
+ - Status dots bind `yellow-700` and `orange-600` rather than the 500s to clear 3:1 on white.
56
+ - `Button` keeps its label and stays focusable while `loading`, setting `aria-busy` and
57
+ ignoring clicks, rather than swapping the label out or disabling itself.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Omkar Khadamkar
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,112 @@
1
+ # Vela
2
+
3
+ A token-first React component kit. Six components, two themes, zero runtime dependencies —
4
+ and every design rule in the documentation is enforced by the TypeScript compiler.
5
+
6
+ ```bash
7
+ npm install @omkarux/vela
8
+ ```
9
+
10
+ ```tsx
11
+ import '@omkarux/vela/styles.css'
12
+ import { Button, ContextualAlert, Tabs } from '@omkarux/vela'
13
+
14
+ <div className="vela-root">
15
+ <ContextualAlert severity="major" title="Scan failed">
16
+ Three assets could not be reached.
17
+ </ContextualAlert>
18
+ <Button variant="primary" appearance="filled">Retry Scan</Button>
19
+ </div>
20
+ ```
21
+
22
+ ## What is actually in here
23
+
24
+ | | |
25
+ |---|---|
26
+ | **Components** | Button · Toggle · Input · ContextualAlert · StatusIndicator · Tabs |
27
+ | **Tokens** | 239 CSS custom properties, two layers (primitives → semantics) |
28
+ | **Themes** | Light + dark. OS preference by default, `data-theme` to override |
29
+ | **Dependencies** | None. `react` / `react-dom` are peers (18.3 or 19) |
30
+ | **Format** | ESM, `preserveModules` for tree-shaking, full `.d.ts` |
31
+ | **Tests** | 116, including 76 asserted contrast pairs across both themes |
32
+ | **Packed size** | 26.7 kB, 43 files |
33
+
34
+ ## The three ideas worth stealing
35
+
36
+ **1. Dark mode redefines no primitive.** The entire dark theme is semantic tokens re-pointed
37
+ onto a different part of the same scale. A test fails the build if a raw hex ever appears in
38
+ the dark block. That property is what makes a third theme cheap instead of a rewrite.
39
+
40
+ **2. Severity, Risk and Status are separate taxonomies.** Six levels, three levels, five
41
+ states — never interchangeable. There is no "high severity" and no "critical risk". The
42
+ types reject both. Most design systems collapse these into one `status` prop and then spend
43
+ years untangling it.
44
+
45
+ **3. Design constraints are compiler errors, not review comments.** `ButtonProps` is a
46
+ discriminated union, so the rule "text-link is primary-only, small sizes only, never with an
47
+ icon" is not a paragraph someone has to remember:
48
+
49
+ ```tsx
50
+ <Button variant="primary" appearance="text-link" size="tiny">Learn More</Button> // ✅
51
+ <Button variant="destructive" appearance="text-link">Delete</Button> // ✗ won't compile
52
+ <Button variant="primary" appearance="text-link" size="huge">Go</Button> // ✗ won't compile
53
+ <Button variant="primary" appearance="text-link" icon={<Icon />}>Go</Button> // ✗ won't compile
54
+ <ContextualAlert severity="high">…</ContextualAlert> // ✗ severity has no "high"
55
+ <StatusIndicator status="critical" /> // ✗ that's severity, not status
56
+ <Tabs.List>…</Tabs.List> // ✗ aria-label is required
57
+ <Toggle /> // ✗ label is required
58
+ ```
59
+
60
+ `npm run verify` asserts every one of those rejections against the *published tarball*.
61
+
62
+ ## Accessibility is asserted, not claimed
63
+
64
+ `npm run contrast` prints every foreground/background pair in both themes against WCAG 2.1
65
+ AA — 4.5:1 for text, 3:1 for UI boundaries and non-text signals. The same data backs a test,
66
+ so a token change that breaks contrast fails CI.
67
+
68
+ The first run of that audit found 9 failures in the inherited light palette. All 9 are fixed,
69
+ and the fixes are documented in `guidelines/foundations/color.md` so nobody "corrects" them
70
+ back — most notably `--vela-btn-destructive-bg`, which binds `red-700` rather than `red-500`
71
+ because red-500 behind a white label is 3.93:1.
72
+
73
+ ## Built for agents as well as people
74
+
75
+ `guidelines/` ships **inside the package**. It is the specification, not a rendered copy of
76
+ the source: closed prop tables, token-binding tables, hard constraints, and an anti-patterns
77
+ block per component. Point your coding agent at `guidelines/llms.txt`, or import a single
78
+ file:
79
+
80
+ ```ts
81
+ import buttonSpec from '@omkarux/vela/guidelines/components/button.md?raw'
82
+ ```
83
+
84
+ `AGENTS.md` at the repo root carries the same rules for anyone working *on* the system.
85
+
86
+ ## Theming
87
+
88
+ ```tsx
89
+ document.documentElement.setAttribute('data-theme', 'dark') // force dark
90
+ document.documentElement.setAttribute('data-theme', 'light') // force light
91
+ document.documentElement.removeAttribute('data-theme') // follow the OS
92
+ ```
93
+
94
+ ## Scripts
95
+
96
+ ```bash
97
+ npm run dev # demo showcase at localhost:5174
98
+ npm test # 116 tests
99
+ npm run contrast # WCAG report for both themes
100
+ npm run verify # typecheck + tests + build + pack-and-consume gate
101
+ ```
102
+
103
+ ## Scope
104
+
105
+ Six components, chosen to exercise every foundation and both status taxonomies. Not yet
106
+ specced: Checkbox, Radio, Dropdown List, Select List, Token Pill, Number Stepper, Text Area,
107
+ Modal, Tooltip, Icon Button, Actions Dropdown, View Switcher. The guidelines name these
108
+ explicitly so an agent flags the gap instead of inventing one.
109
+
110
+ ## License
111
+
112
+ MIT
@@ -0,0 +1,32 @@
1
+ import * as React from 'react';
2
+ export type ButtonVariant = 'primary' | 'standard' | 'destructive';
3
+ export type ButtonSize = 'tiny' | 'regular' | 'large' | 'huge';
4
+ interface ButtonCommon extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'type' | 'children'> {
5
+ /** Label. Verb-first, Title Case: "Save Changes", not "Information". */
6
+ children: React.ReactNode;
7
+ disabled?: boolean;
8
+ /** Shows an indicator and sets aria-busy. Clicks are ignored while true. */
9
+ loading?: boolean;
10
+ type?: 'button' | 'submit';
11
+ }
12
+ export interface SolidButtonProps extends ButtonCommon {
13
+ variant?: ButtonVariant;
14
+ appearance?: 'filled' | 'hollow';
15
+ size?: ButtonSize;
16
+ /** Optional icon, always rendered LEFT of the label. There is no trailing slot. */
17
+ icon?: React.ReactNode;
18
+ }
19
+ /**
20
+ * The text-link appearance is deliberately narrower than the others.
21
+ * `icon?: never` and the closed `size` union mean the compiler — not a code
22
+ * review — rejects `<Button appearance="text-link" variant="destructive" />`.
23
+ */
24
+ export interface TextLinkButtonProps extends ButtonCommon {
25
+ variant: 'primary';
26
+ appearance: 'text-link';
27
+ size?: 'tiny' | 'regular';
28
+ icon?: never;
29
+ }
30
+ export type ButtonProps = SolidButtonProps | TextLinkButtonProps;
31
+ export declare const Button: React.ForwardRefExoticComponent<ButtonProps & React.RefAttributes<HTMLButtonElement>>;
32
+ export {};
@@ -0,0 +1,51 @@
1
+ import { jsxs as m, jsx as n } from "react/jsx-runtime";
2
+ import * as v from "react";
3
+ import { cn as h } from "../../lib/cn.js";
4
+ const g = v.forwardRef(
5
+ function(r, s) {
6
+ const {
7
+ variant: i = "standard",
8
+ appearance: o = "filled",
9
+ size: c = "regular",
10
+ disabled: d = !1,
11
+ loading: a = !1,
12
+ type: u = "button",
13
+ icon: t,
14
+ children: b,
15
+ className: p,
16
+ onClick: e,
17
+ ...f
18
+ } = r;
19
+ return /* @__PURE__ */ m(
20
+ "button",
21
+ {
22
+ ref: s,
23
+ type: u,
24
+ disabled: d,
25
+ "aria-busy": a || void 0,
26
+ onClick: (l) => {
27
+ if (a) {
28
+ l.preventDefault();
29
+ return;
30
+ }
31
+ e == null || e(l);
32
+ },
33
+ className: h(
34
+ "vela-btn",
35
+ `vela-btn--${i}`,
36
+ `vela-btn--${o}`,
37
+ `vela-btn--${c}`,
38
+ p
39
+ ),
40
+ ...f,
41
+ children: [
42
+ a ? /* @__PURE__ */ n("span", { className: "vela-btn__spinner", "aria-hidden": "true" }) : t ? /* @__PURE__ */ n("span", { className: "vela-btn__icon", "aria-hidden": "true", children: t }) : null,
43
+ /* @__PURE__ */ n("span", { className: "vela-btn__label", children: b })
44
+ ]
45
+ }
46
+ );
47
+ }
48
+ );
49
+ export {
50
+ g as Button
51
+ };
@@ -0,0 +1,2 @@
1
+ export { Button } from './Button';
2
+ export type { ButtonProps, ButtonVariant, ButtonSize, SolidButtonProps, TextLinkButtonProps } from './Button';
@@ -0,0 +1,18 @@
1
+ import * as React from 'react';
2
+ /**
3
+ * The six severity levels. Describes events and alerts.
4
+ * There is no "high severity" — that is the Risk taxonomy, and the two are
5
+ * kept apart on purpose. See guidelines/foundations/color.md.
6
+ */
7
+ export type Severity = 'success' | 'info' | 'warning' | 'minor' | 'major' | 'critical';
8
+ export interface ContextualAlertProps {
9
+ severity: Severity;
10
+ /** Optional bold lead-in. The body is always required. */
11
+ title?: string;
12
+ children: React.ReactNode;
13
+ /** Renders a dismiss control. Omit for alerts the user must not clear. */
14
+ onDismiss?: () => void;
15
+ dismissLabel?: string;
16
+ className?: string;
17
+ }
18
+ export declare function ContextualAlert({ severity, title, children, onDismiss, dismissLabel, className, }: ContextualAlertProps): React.JSX.Element;
@@ -0,0 +1,49 @@
1
+ import { jsxs as n, jsx as a } from "react/jsx-runtime";
2
+ import { cn as v } from "../../lib/cn.js";
3
+ import { CriticalOctagonIcon as I, WarningTriangleIcon as l, InfoCircleIcon as d, CheckCircleIcon as C, CloseIcon as _ } from "../../lib/icons.js";
4
+ const u = {
5
+ success: C,
6
+ info: d,
7
+ warning: l,
8
+ minor: l,
9
+ major: l,
10
+ critical: I
11
+ }, f = /* @__PURE__ */ new Set(["major", "critical"]);
12
+ function g({
13
+ severity: e,
14
+ title: r,
15
+ children: o,
16
+ onDismiss: c,
17
+ dismissLabel: i = "Dismiss",
18
+ className: s
19
+ }) {
20
+ const m = u[e], t = f.has(e);
21
+ return /* @__PURE__ */ n(
22
+ "div",
23
+ {
24
+ className: v("vela-alert", `vela-alert--${e}`, s),
25
+ role: t ? "alert" : "status",
26
+ "aria-live": t ? "assertive" : "polite",
27
+ children: [
28
+ /* @__PURE__ */ a(m, { className: "vela-alert__icon" }),
29
+ /* @__PURE__ */ n("div", { className: "vela-alert__body", children: [
30
+ r && /* @__PURE__ */ a("strong", { className: "vela-alert__title", children: r }),
31
+ o
32
+ ] }),
33
+ c && /* @__PURE__ */ a(
34
+ "button",
35
+ {
36
+ type: "button",
37
+ className: "vela-alert__dismiss",
38
+ onClick: c,
39
+ "aria-label": i,
40
+ children: /* @__PURE__ */ a(_, {})
41
+ }
42
+ )
43
+ ]
44
+ }
45
+ );
46
+ }
47
+ export {
48
+ g as ContextualAlert
49
+ };
@@ -0,0 +1,2 @@
1
+ export { ContextualAlert } from './ContextualAlert';
2
+ export type { ContextualAlertProps, Severity } from './ContextualAlert';
@@ -0,0 +1,12 @@
1
+ import * as React from 'react';
2
+ export interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'> {
3
+ /** Required — an input without a visible label is a support ticket. */
4
+ label: string;
5
+ /** Helper text below the field. Announced via aria-describedby. */
6
+ hint?: string;
7
+ /** Presence puts the field in the error state; the string is the message. */
8
+ error?: string;
9
+ size?: 'tiny' | 'regular' | 'large';
10
+ className?: string;
11
+ }
12
+ export declare const Input: React.ForwardRefExoticComponent<InputProps & React.RefAttributes<HTMLInputElement>>;
@@ -0,0 +1,31 @@
1
+ import { jsxs as s, jsx as l } from "react/jsx-runtime";
2
+ import * as t from "react";
3
+ import { cn as c } from "../../lib/cn.js";
4
+ const j = t.forwardRef(
5
+ function({ label: o, hint: i, error: e, size: u = "regular", id: m, required: n, className: p, ...f }, v) {
6
+ const h = t.useId(), a = m ?? h, r = `${a}-hint`, d = `${a}-error`, _ = [i ? r : null, e ? d : null].filter(Boolean).join(" ") || void 0;
7
+ return /* @__PURE__ */ s("div", { className: c("vela-field", p), children: [
8
+ /* @__PURE__ */ s("label", { className: "vela-field__label", htmlFor: a, children: [
9
+ o,
10
+ n && /* @__PURE__ */ l("span", { className: "vela-field__required", "aria-hidden": "true", children: "*" })
11
+ ] }),
12
+ /* @__PURE__ */ l(
13
+ "input",
14
+ {
15
+ ref: v,
16
+ id: a,
17
+ required: n,
18
+ "aria-invalid": e ? !0 : void 0,
19
+ "aria-describedby": _,
20
+ className: c("vela-input", `vela-input--${u}`),
21
+ ...f
22
+ }
23
+ ),
24
+ i && /* @__PURE__ */ l("span", { id: r, className: "vela-field__hint", children: i }),
25
+ e && /* @__PURE__ */ l("span", { id: d, className: "vela-field__error", role: "alert", children: e })
26
+ ] });
27
+ }
28
+ );
29
+ export {
30
+ j as Input
31
+ };
@@ -0,0 +1,2 @@
1
+ export { Input } from './Input';
2
+ export type { InputProps } from './Input';
@@ -0,0 +1,14 @@
1
+ /**
2
+ * The five health states. This is NOT severity and NOT risk — those are
3
+ * separate taxonomies with their own tokens. There is no "critical" status
4
+ * and no "high" status; if you reach for one, you want a different component.
5
+ */
6
+ export type Status = 'unknown' | 'healthy' | 'warning' | 'medium' | 'unhealthy';
7
+ export interface StatusIndicatorProps {
8
+ status: Status;
9
+ /** Visible text. Strongly preferred — see the aria-label fallback below. */
10
+ label?: string;
11
+ size?: 'small' | 'regular';
12
+ className?: string;
13
+ }
14
+ export declare function StatusIndicator({ status, label, size, className, }: StatusIndicatorProps): import("react").JSX.Element;
@@ -0,0 +1,31 @@
1
+ import { jsxs as r, jsx as n } from "react/jsx-runtime";
2
+ import { cn as l } from "../../lib/cn.js";
3
+ const i = {
4
+ unknown: "Status unknown",
5
+ healthy: "Healthy",
6
+ warning: "Warning",
7
+ medium: "Degraded",
8
+ unhealthy: "Unhealthy"
9
+ };
10
+ function d({
11
+ status: t,
12
+ label: a,
13
+ size: e = "regular",
14
+ className: s
15
+ }) {
16
+ return /* @__PURE__ */ r(
17
+ "span",
18
+ {
19
+ className: l("vela-status", `vela-status--${t}`, `vela-status--${e}`, s),
20
+ role: "img",
21
+ "aria-label": a ? void 0 : i[t],
22
+ children: [
23
+ /* @__PURE__ */ n("span", { className: "vela-status__dot", "aria-hidden": "true" }),
24
+ a && /* @__PURE__ */ n("span", { className: "vela-status__label", children: a })
25
+ ]
26
+ }
27
+ );
28
+ }
29
+ export {
30
+ d as StatusIndicator
31
+ };
@@ -0,0 +1,2 @@
1
+ export { StatusIndicator } from './StatusIndicator';
2
+ export type { StatusIndicatorProps, Status } from './StatusIndicator';
@@ -0,0 +1,44 @@
1
+ import * as React from 'react';
2
+ export interface TabsProps {
3
+ /** Controlled active value. Omit for uncontrolled. */
4
+ value?: string;
5
+ defaultValue: string;
6
+ onValueChange?: (value: string) => void;
7
+ size?: 'regular' | 'large';
8
+ children: React.ReactNode;
9
+ className?: string;
10
+ }
11
+ /**
12
+ * Navigate between DIFFERENT content panels.
13
+ * If the panels show the SAME dataset rendered differently (grid vs. chart),
14
+ * that is a View Switcher, not Tabs.
15
+ */
16
+ export declare function Tabs({ value, defaultValue, onValueChange, size, children, className, }: TabsProps): React.JSX.Element;
17
+ export declare namespace Tabs {
18
+ var List: typeof TabList;
19
+ var Trigger: typeof TabTrigger;
20
+ var Panel: typeof TabPanel;
21
+ }
22
+ export interface TabListProps {
23
+ /** Required: a tablist needs a name to be navigable by screen reader. */
24
+ 'aria-label': string;
25
+ children: React.ReactNode;
26
+ className?: string;
27
+ }
28
+ declare function TabList({ children, className, ...rest }: TabListProps): React.JSX.Element;
29
+ export interface TabTriggerProps {
30
+ value: string;
31
+ children: React.ReactNode;
32
+ /** Optional numeric badge, e.g. an alert count. */
33
+ count?: number;
34
+ disabled?: boolean;
35
+ className?: string;
36
+ }
37
+ declare function TabTrigger({ value, children, count, disabled, className }: TabTriggerProps): React.JSX.Element;
38
+ export interface TabPanelProps {
39
+ value: string;
40
+ children: React.ReactNode;
41
+ className?: string;
42
+ }
43
+ declare function TabPanel({ value, children, className }: TabPanelProps): React.JSX.Element | null;
44
+ export {};
@@ -0,0 +1,94 @@
1
+ import { jsx as u, jsxs as k } from "react/jsx-runtime";
2
+ import * as c from "react";
3
+ import { cn as x } from "../../lib/cn.js";
4
+ const p = c.createContext(null);
5
+ function T(e) {
6
+ const s = c.useContext(p);
7
+ if (!s)
8
+ throw new Error(`<Tabs.${e}> must be rendered inside <Tabs>.`);
9
+ return s;
10
+ }
11
+ function y({
12
+ value: e,
13
+ defaultValue: s,
14
+ onValueChange: r,
15
+ size: a = "regular",
16
+ children: o,
17
+ className: n
18
+ }) {
19
+ const d = e !== void 0, [t, l] = c.useState(s), i = d ? e : t, b = c.useId(), f = c.useCallback(
20
+ (g) => {
21
+ d || l(g), r == null || r(g);
22
+ },
23
+ [d, r]
24
+ ), m = c.useMemo(
25
+ () => ({ value: i, setValue: f, idPrefix: b }),
26
+ [i, f, b]
27
+ );
28
+ return /* @__PURE__ */ u(p.Provider, { value: m, children: /* @__PURE__ */ u("div", { className: x("vela-tabs", `vela-tabs--${a}`, n), children: o }) });
29
+ }
30
+ function v({ children: e, className: s, ...r }) {
31
+ const a = c.useRef(null);
32
+ return /* @__PURE__ */ u(
33
+ "div",
34
+ {
35
+ ref: a,
36
+ role: "tablist",
37
+ onKeyDown: (n) => {
38
+ var b, f, m;
39
+ if (!["ArrowLeft", "ArrowRight", "Home", "End"].includes(n.key)) return;
40
+ const t = Array.from(
41
+ ((b = a.current) == null ? void 0 : b.querySelectorAll('[role="tab"]:not(:disabled)')) ?? []
42
+ );
43
+ if (t.length === 0) return;
44
+ const l = t.indexOf(document.activeElement);
45
+ let i = l;
46
+ n.key === "ArrowRight" ? i = (l + 1) % t.length : n.key === "ArrowLeft" ? i = (l - 1 + t.length) % t.length : n.key === "Home" ? i = 0 : n.key === "End" && (i = t.length - 1), n.preventDefault(), (f = t[i]) == null || f.focus(), (m = t[i]) == null || m.click();
47
+ },
48
+ className: x("vela-tabs__list", s),
49
+ ...r,
50
+ children: e
51
+ }
52
+ );
53
+ }
54
+ function $({ value: e, children: s, count: r, disabled: a, className: o }) {
55
+ const { value: n, setValue: d, idPrefix: t } = T("Trigger"), l = n === e;
56
+ return /* @__PURE__ */ k(
57
+ "button",
58
+ {
59
+ type: "button",
60
+ role: "tab",
61
+ id: `${t}-tab-${e}`,
62
+ "aria-selected": l,
63
+ "aria-controls": `${t}-panel-${e}`,
64
+ tabIndex: l ? 0 : -1,
65
+ disabled: a,
66
+ onClick: () => d(e),
67
+ className: x("vela-tabs__trigger", o),
68
+ children: [
69
+ s,
70
+ r !== void 0 && /* @__PURE__ */ u("span", { className: "vela-tabs__count", children: r })
71
+ ]
72
+ }
73
+ );
74
+ }
75
+ function w({ value: e, children: s, className: r }) {
76
+ const { value: a, idPrefix: o } = T("Panel");
77
+ return a !== e ? null : /* @__PURE__ */ u(
78
+ "div",
79
+ {
80
+ role: "tabpanel",
81
+ id: `${o}-panel-${e}`,
82
+ "aria-labelledby": `${o}-tab-${e}`,
83
+ tabIndex: 0,
84
+ className: x("vela-tabs__panel", r),
85
+ children: s
86
+ }
87
+ );
88
+ }
89
+ y.List = v;
90
+ y.Trigger = $;
91
+ y.Panel = w;
92
+ export {
93
+ y as Tabs
94
+ };
@@ -0,0 +1,2 @@
1
+ export { Tabs } from './Tabs';
2
+ export type { TabsProps, TabListProps, TabTriggerProps, TabPanelProps } from './Tabs';
@@ -0,0 +1,23 @@
1
+ import * as React from 'react';
2
+ export interface ToggleProps {
3
+ /** Controlled state. Omit for uncontrolled. */
4
+ checked?: boolean;
5
+ defaultChecked?: boolean;
6
+ onChange?: (checked: boolean) => void;
7
+ disabled?: boolean;
8
+ /**
9
+ * Required. A switch with no accessible name is unusable with a screen
10
+ * reader, so the API does not allow one to exist.
11
+ */
12
+ label: string;
13
+ labelPosition?: 'right' | 'left';
14
+ size?: 'tiny' | 'regular';
15
+ id?: string;
16
+ name?: string;
17
+ className?: string;
18
+ }
19
+ /**
20
+ * A binary setting that takes effect IMMEDIATELY.
21
+ * If the change only takes effect after a Save click, use Checkbox instead.
22
+ */
23
+ export declare const Toggle: React.ForwardRefExoticComponent<ToggleProps & React.RefAttributes<HTMLButtonElement>>;