@openkeyai/ui 0.1.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import 'react';
2
- import { jsxs, jsx } from 'react/jsx-runtime';
1
+ import * as React4 from 'react';
2
+ import { jsx, jsxs } from 'react/jsx-runtime';
3
3
 
4
4
  // src/components/hub-header.tsx
5
5
  var DEFAULT_HUB_URL = "https://openkeyai.com";
@@ -86,10 +86,115 @@ function HubHeader({
86
86
  }
87
87
  );
88
88
  }
89
+ var cx = (...parts) => parts.filter(Boolean).join(" ");
90
+ var Button = React4.forwardRef(
91
+ function Button2({
92
+ variant = "primary",
93
+ size = "md",
94
+ iconOnly = false,
95
+ className,
96
+ type = "button",
97
+ ...rest
98
+ }, ref) {
99
+ return /* @__PURE__ */ jsx(
100
+ "button",
101
+ {
102
+ ref,
103
+ type,
104
+ className: cx(
105
+ "okai-btn",
106
+ `okai-btn--${variant}`,
107
+ `okai-btn--${size}`,
108
+ iconOnly && "okai-btn--icon",
109
+ className
110
+ ),
111
+ ...rest
112
+ }
113
+ );
114
+ }
115
+ );
116
+ var cx2 = (...parts) => parts.filter(Boolean).join(" ");
117
+ var Card = React4.forwardRef(function Card2({ level = 1, interactive = false, className, ...rest }, ref) {
118
+ return /* @__PURE__ */ jsx(
119
+ "div",
120
+ {
121
+ ref,
122
+ className: cx2(
123
+ "okai-card",
124
+ `okai-card--level-${level}`,
125
+ interactive && "okai-card--interactive",
126
+ className
127
+ ),
128
+ ...rest
129
+ }
130
+ );
131
+ });
132
+ var cx3 = (...parts) => parts.filter(Boolean).join(" ");
133
+ var Input = React4.forwardRef(
134
+ function Input2({ className, mono, ...rest }, ref) {
135
+ return /* @__PURE__ */ jsx(
136
+ "input",
137
+ {
138
+ ref,
139
+ className: cx3("okai-input", mono && "okai-input--mono", className),
140
+ ...rest
141
+ }
142
+ );
143
+ }
144
+ );
145
+ var Select = React4.forwardRef(
146
+ function Select2({ className, ...rest }, ref) {
147
+ return /* @__PURE__ */ jsx("select", { ref, className: cx3("okai-select", className), ...rest });
148
+ }
149
+ );
150
+ var Textarea = React4.forwardRef(
151
+ function Textarea2({ className, ...rest }, ref) {
152
+ return /* @__PURE__ */ jsx(
153
+ "textarea",
154
+ {
155
+ ref,
156
+ className: cx3("okai-textarea", className),
157
+ ...rest
158
+ }
159
+ );
160
+ }
161
+ );
162
+ var cx4 = (...parts) => parts.filter(Boolean).join(" ");
163
+ function Badge({
164
+ variant = "default",
165
+ className,
166
+ ...rest
167
+ }) {
168
+ return /* @__PURE__ */ jsx(
169
+ "span",
170
+ {
171
+ className: cx4("okai-badge", `okai-badge--${variant}`, className),
172
+ ...rest
173
+ }
174
+ );
175
+ }
176
+ var cx5 = (...parts) => parts.filter(Boolean).join(" ");
177
+ function Stat({
178
+ label,
179
+ value,
180
+ sub,
181
+ icon,
182
+ className,
183
+ ...rest
184
+ }) {
185
+ return /* @__PURE__ */ jsxs("div", { className: cx5("okai-stat", className), ...rest, children: [
186
+ /* @__PURE__ */ jsxs("span", { className: "okai-stat__label", children: [
187
+ icon ? /* @__PURE__ */ jsx("span", { "aria-hidden": true, children: icon }) : null,
188
+ label
189
+ ] }),
190
+ /* @__PURE__ */ jsx("span", { className: "okai-stat__value", children: value }),
191
+ sub ? /* @__PURE__ */ jsx("span", { className: "okai-stat__sub", children: sub }) : null
192
+ ] });
193
+ }
89
194
 
90
195
  // src/index.ts
91
- var UI_VERSION = "0.1.0";
196
+ var UI_VERSION = "1.0.0";
92
197
 
93
- export { HubHeader, UI_VERSION };
198
+ export { Badge, Button, Card, HubHeader, Input, Select, Stat, Textarea, UI_VERSION };
94
199
  //# sourceMappingURL=index.js.map
95
200
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/components/hub-header.tsx","../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import * as React from \"react\";\n\n/**\n * Mandatory shared header for every OpenKey AI tool.\n *\n * What it gives the user (consistent across every tool):\n * - The hub brand mark — clicking it returns to the hub\n * - The tool's name — so users know which tool they're in\n * - The signed-in user (avatar + display name) — clicking opens the hub\n * account page\n * - A sign-in CTA when no user is provided\n *\n * What it deliberately does NOT do (per docs/TOOL_CONTRACT.md):\n * - Provide props to hide the brand mark, tool name, or user/sign-in chip\n * - Theme the colours away from the hub palette\n * - Allow the tool to swap the brand text\n *\n * The header is fixed to the top of the viewport at `z-index:\n * var(--okai-z-header)` and is `var(--okai-header-h)` tall. Tools should\n * leave that much top padding on their root layout. The styles live in\n * `@openkeyai/ui/css` — import it once in the tool's global CSS.\n *\n * SSR-safe: pure render, no client-only APIs in the default path.\n */\nexport type HubHeaderUser = {\n /** Visible label on the chip. */\n displayName: string;\n /** Optional avatar; falls back to initials when missing. */\n avatarUrl?: string | null;\n /** Used as a tooltip and accessible label. Not displayed. */\n email?: string;\n};\n\nexport type HubHeaderProps = {\n /** Display name of the tool. Goes after the divider. */\n toolName: string;\n /** Signed-in user, or null/undefined to show the Sign in CTA. */\n user?: HubHeaderUser | null;\n /** Override the hub root URL. Defaults to https://openkeyai.com. */\n hubUrl?: string;\n /** Optional right-side slot — tool-specific controls (settings, help, …).\n * Rendered BEFORE the user chip so the chip always ends the row. */\n rightSlot?: React.ReactNode;\n /** Forwarded to the outer <header>. Avoid resetting position/z-index. */\n className?: string;\n /** Optional inline style override for the outer <header>. */\n style?: React.CSSProperties;\n};\n\nconst DEFAULT_HUB_URL = \"https://openkeyai.com\";\n\nfunction getInitials(name: string): string {\n const parts = name.trim().split(/\\s+/).filter(Boolean);\n const first = parts[0];\n if (!first) return \"?\";\n if (parts.length === 1) return first.slice(0, 2).toUpperCase();\n const last = parts[parts.length - 1] ?? first;\n return ((first[0] ?? \"\") + (last[0] ?? \"\")).toUpperCase() || \"?\";\n}\n\n/**\n * `<HubHeader />` — the mandatory header. See module doc above.\n */\nexport function HubHeader({\n toolName,\n user,\n hubUrl = DEFAULT_HUB_URL,\n rightSlot,\n className,\n style,\n}: HubHeaderProps) {\n const hubHref = hubUrl;\n const accountHref = `${hubUrl.replace(/\\/$/, \"\")}/settings/account`;\n const signInHref = `${hubUrl.replace(/\\/$/, \"\")}/login`;\n\n return (\n <header\n className={\n className ? `okai-header ${className}` : \"okai-header\"\n }\n style={style}\n role=\"banner\"\n data-okai-component=\"hub-header\"\n >\n <a\n className=\"okai-header__brand\"\n href={hubHref}\n aria-label=\"Open OpenKey AI hub\"\n >\n <span className=\"okai-header__brand-mark\" aria-hidden=\"true\" />\n <span className=\"okai-header__brand-text\">OpenKey AI</span>\n </a>\n\n <span className=\"okai-header__divider\" aria-hidden=\"true\" />\n\n <span className=\"okai-header__tool-name\" title={toolName}>\n {toolName}\n </span>\n\n <span className=\"okai-header__spacer\" aria-hidden=\"true\" />\n\n {rightSlot != null ? (\n <div className=\"okai-header__right\">{rightSlot}</div>\n ) : null}\n\n {user ? (\n <a\n className=\"okai-header__user\"\n href={accountHref}\n title={user.email ?? user.displayName}\n aria-label={`Open account for ${user.displayName}`}\n >\n {user.avatarUrl ? (\n <span\n className=\"okai-header__user-avatar\"\n role=\"img\"\n aria-label={user.displayName}\n style={{ backgroundImage: `url(${user.avatarUrl})` }}\n />\n ) : (\n <span\n className=\"okai-header__user-fallback\"\n aria-hidden=\"true\"\n >\n {getInitials(user.displayName)}\n </span>\n )}\n <span className=\"okai-header__user-name\">{user.displayName}</span>\n </a>\n ) : (\n <a\n className=\"okai-header__signin\"\n href={signInHref}\n aria-label=\"Sign in to OpenKey AI\"\n >\n Sign in\n </a>\n )}\n </header>\n );\n}\n","/**\n * `@openkeyai/ui` — public entry.\n *\n * Every OpenKey AI tool installs this package and mounts <HubHeader /> in\n * its root layout. CI in `Scott-Builds-AI/.github` enforces the mounting\n * via an AST scanner (lands in Phase 9).\n *\n * Entry paths:\n * - \"@openkeyai/ui\" → React components + their types (this file)\n * - \"@openkeyai/ui/tokens\" → Design tokens as a JS object\n * - \"@openkeyai/ui/tailwind\" → Tailwind preset\n * - \"@openkeyai/ui/css\" → The global stylesheet (CSS custom\n * properties + scoped .okai-* classes).\n * MUST be imported once by the consumer.\n *\n * See README.md for the canonical mounting pattern.\n */\n\nexport { HubHeader } from \"./components/hub-header\";\nexport type {\n HubHeaderProps,\n HubHeaderUser,\n} from \"./components/hub-header\";\n\n/** Bumped on each release. Useful for tools to log which UI version they shipped against. */\nexport const UI_VERSION = \"0.1.0\";\n"]}
1
+ {"version":3,"sources":["../src/components/hub-header.tsx","../src/components/button.tsx","../src/components/card.tsx","../src/components/input.tsx","../src/components/badge.tsx","../src/components/stat.tsx","../src/index.ts"],"names":["React2","Button","jsx","cx","React3","Card","Input","Select","Textarea","jsxs"],"mappings":"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","file":"index.js","sourcesContent":["import * as React from \"react\";\n\n/**\n * Mandatory shared header for every OpenKey AI tool.\n *\n * What it gives the user (consistent across every tool):\n * - The hub brand mark — clicking it returns to the hub\n * - The tool's name — so users know which tool they're in\n * - The signed-in user (avatar + display name) — clicking opens the hub\n * account page\n * - A sign-in CTA when no user is provided\n *\n * What it deliberately does NOT do (per docs/TOOL_CONTRACT.md):\n * - Provide props to hide the brand mark, tool name, or user/sign-in chip\n * - Theme the colours away from the hub palette\n * - Allow the tool to swap the brand text\n *\n * The header is fixed to the top of the viewport at `z-index:\n * var(--okai-z-header)` and is `var(--okai-header-h)` tall. Tools should\n * leave that much top padding on their root layout. The styles live in\n * `@openkeyai/ui/css` — import it once in the tool's global CSS.\n *\n * SSR-safe: pure render, no client-only APIs in the default path.\n */\nexport type HubHeaderUser = {\n /** Visible label on the chip. */\n displayName: string;\n /** Optional avatar; falls back to initials when missing. */\n avatarUrl?: string | null;\n /** Used as a tooltip and accessible label. Not displayed. */\n email?: string;\n};\n\nexport type HubHeaderProps = {\n /** Display name of the tool. Goes after the divider. */\n toolName: string;\n /** Signed-in user, or null/undefined to show the Sign in CTA. */\n user?: HubHeaderUser | null;\n /** Override the hub root URL. Defaults to https://openkeyai.com. */\n hubUrl?: string;\n /** Optional right-side slot — tool-specific controls (settings, help, …).\n * Rendered BEFORE the user chip so the chip always ends the row. */\n rightSlot?: React.ReactNode;\n /** Forwarded to the outer <header>. Avoid resetting position/z-index. */\n className?: string;\n /** Optional inline style override for the outer <header>. */\n style?: React.CSSProperties;\n};\n\nconst DEFAULT_HUB_URL = \"https://openkeyai.com\";\n\nfunction getInitials(name: string): string {\n const parts = name.trim().split(/\\s+/).filter(Boolean);\n const first = parts[0];\n if (!first) return \"?\";\n if (parts.length === 1) return first.slice(0, 2).toUpperCase();\n const last = parts[parts.length - 1] ?? first;\n return ((first[0] ?? \"\") + (last[0] ?? \"\")).toUpperCase() || \"?\";\n}\n\n/**\n * `<HubHeader />` — the mandatory header. See module doc above.\n */\nexport function HubHeader({\n toolName,\n user,\n hubUrl = DEFAULT_HUB_URL,\n rightSlot,\n className,\n style,\n}: HubHeaderProps) {\n const hubHref = hubUrl;\n const accountHref = `${hubUrl.replace(/\\/$/, \"\")}/settings/account`;\n const signInHref = `${hubUrl.replace(/\\/$/, \"\")}/login`;\n\n return (\n <header\n className={\n className ? `okai-header ${className}` : \"okai-header\"\n }\n style={style}\n role=\"banner\"\n data-okai-component=\"hub-header\"\n >\n <a\n className=\"okai-header__brand\"\n href={hubHref}\n aria-label=\"Open OpenKey AI hub\"\n >\n <span className=\"okai-header__brand-mark\" aria-hidden=\"true\" />\n <span className=\"okai-header__brand-text\">OpenKey AI</span>\n </a>\n\n <span className=\"okai-header__divider\" aria-hidden=\"true\" />\n\n <span className=\"okai-header__tool-name\" title={toolName}>\n {toolName}\n </span>\n\n <span className=\"okai-header__spacer\" aria-hidden=\"true\" />\n\n {rightSlot != null ? (\n <div className=\"okai-header__right\">{rightSlot}</div>\n ) : null}\n\n {user ? (\n <a\n className=\"okai-header__user\"\n href={accountHref}\n title={user.email ?? user.displayName}\n aria-label={`Open account for ${user.displayName}`}\n >\n {user.avatarUrl ? (\n <span\n className=\"okai-header__user-avatar\"\n role=\"img\"\n aria-label={user.displayName}\n style={{ backgroundImage: `url(${user.avatarUrl})` }}\n />\n ) : (\n <span\n className=\"okai-header__user-fallback\"\n aria-hidden=\"true\"\n >\n {getInitials(user.displayName)}\n </span>\n )}\n <span className=\"okai-header__user-name\">{user.displayName}</span>\n </a>\n ) : (\n <a\n className=\"okai-header__signin\"\n href={signInHref}\n aria-label=\"Sign in to OpenKey AI\"\n >\n Sign in\n </a>\n )}\n </header>\n );\n}\n","import * as React from \"react\";\n\n/**\n * `<Button>` — base interaction primitive.\n *\n * Five variants × three sizes:\n * - `primary` — solid brand fill. Default CTAs.\n * - `secondary` — outlined. Pair with a primary, never alone as the\n * only action on a surface.\n * - `tonal` — Material 3 tonal: brand-tinted container with\n * readable foreground. Use for emphasised non-primary actions.\n * - `text` — no border, no fill. Inline links inside dense UI.\n * - `destructive` — danger color. Confirm-destruction dialogs.\n *\n * Pass `iconOnly` for square aspect-ratio buttons (toolbars, icon\n * pills). The label slot still acts as the accessible name.\n *\n * Renders a real `<button>` by default — pass `asChild` to render the\n * children as the root element (e.g. `<Link>` from your router).\n */\nexport type ButtonVariant =\n | \"primary\"\n | \"secondary\"\n | \"tonal\"\n | \"text\"\n | \"destructive\";\n\nexport type ButtonSize = \"sm\" | \"md\" | \"lg\";\n\nexport type ButtonProps = Omit<\n React.ButtonHTMLAttributes<HTMLButtonElement>,\n \"className\"\n> & {\n variant?: ButtonVariant;\n size?: ButtonSize;\n /** Square button with no horizontal padding — children should be a single icon. */\n iconOnly?: boolean;\n /** Additional className stacked on top of `okai-btn` modifiers. */\n className?: string;\n};\n\nconst cx = (...parts: Array<string | false | null | undefined>) =>\n parts.filter(Boolean).join(\" \");\n\nexport const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n function Button(\n {\n variant = \"primary\",\n size = \"md\",\n iconOnly = false,\n className,\n type = \"button\",\n ...rest\n },\n ref,\n ) {\n return (\n <button\n ref={ref}\n type={type}\n className={cx(\n \"okai-btn\",\n `okai-btn--${variant}`,\n `okai-btn--${size}`,\n iconOnly && \"okai-btn--icon\",\n className,\n )}\n {...rest}\n />\n );\n },\n);\n","import * as React from \"react\";\n\n/**\n * `<Card>` — surface primitive.\n *\n * Four elevation levels matching the hub's Material 3 tonal model:\n *\n * - `0` — flat, inline with the page. No border, no shadow.\n * - `1` — default card. Single subtle shadow + surface lift.\n * - `2` — nested cards (inside another card). Stronger surface lift.\n * - `3` — floating elements (popovers, toasts). Visible shadow.\n *\n * `interactive` makes the card hover-aware (cursor pointer + background\n * shift). Wrap a router `<Link>` around it for click-through cards, or\n * use the `as` prop to render a custom element.\n */\nexport type CardLevel = 0 | 1 | 2 | 3;\n\nexport type CardProps = Omit<\n React.HTMLAttributes<HTMLDivElement>,\n \"className\"\n> & {\n level?: CardLevel;\n interactive?: boolean;\n className?: string;\n};\n\nconst cx = (...parts: Array<string | false | null | undefined>) =>\n parts.filter(Boolean).join(\" \");\n\nexport const Card = React.forwardRef<HTMLDivElement, CardProps>(function Card(\n { level = 1, interactive = false, className, ...rest },\n ref,\n) {\n return (\n <div\n ref={ref}\n className={cx(\n \"okai-card\",\n `okai-card--level-${level}`,\n interactive && \"okai-card--interactive\",\n className,\n )}\n {...rest}\n />\n );\n});\n","import * as React from \"react\";\n\n/**\n * Form controls — `<Input>`, `<Select>`, `<Textarea>`.\n *\n * Plain forwardable wrappers around the native elements with the\n * matching `okai-*` class. Use `mono` on `<Input>` for monospace fields\n * (slugs, ids, tokens).\n *\n * Validation + accessible labels are the consumer's responsibility — we\n * don't ship a `<Form />` wrapper.\n */\n\nconst cx = (...parts: Array<string | false | null | undefined>) =>\n parts.filter(Boolean).join(\" \");\n\nexport type InputProps = Omit<\n React.InputHTMLAttributes<HTMLInputElement>,\n \"className\"\n> & {\n className?: string;\n /** Use monospace face — for slugs, ids, tokens. */\n mono?: boolean;\n};\n\nexport const Input = React.forwardRef<HTMLInputElement, InputProps>(\n function Input({ className, mono, ...rest }, ref) {\n return (\n <input\n ref={ref}\n className={cx(\"okai-input\", mono && \"okai-input--mono\", className)}\n {...rest}\n />\n );\n },\n);\n\nexport type SelectProps = Omit<\n React.SelectHTMLAttributes<HTMLSelectElement>,\n \"className\"\n> & {\n className?: string;\n};\n\nexport const Select = React.forwardRef<HTMLSelectElement, SelectProps>(\n function Select({ className, ...rest }, ref) {\n return (\n <select ref={ref} className={cx(\"okai-select\", className)} {...rest} />\n );\n },\n);\n\nexport type TextareaProps = Omit<\n React.TextareaHTMLAttributes<HTMLTextAreaElement>,\n \"className\"\n> & {\n className?: string;\n};\n\nexport const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(\n function Textarea({ className, ...rest }, ref) {\n return (\n <textarea\n ref={ref}\n className={cx(\"okai-textarea\", className)}\n {...rest}\n />\n );\n },\n);\n","import * as React from \"react\";\n\n/**\n * `<Badge>` — pill for status, tags, identifiers.\n *\n * Six variants:\n * - `default` — neutral grey tint\n * - `brand` — brand-tinted (Material 3 container surface)\n * - `success` — sage. \\\"Operational\\\", \\\"Approved\\\".\n * - `warning` — amber. \\\"Monitoring\\\", \\\"Pending review\\\".\n * - `danger` — coral. \\\"Failed\\\", \\\"Suspended\\\".\n * - `monitoring` — slightly softer warning. \\\"Watching\\\".\n */\nexport type BadgeVariant =\n | \"default\"\n | \"brand\"\n | \"success\"\n | \"warning\"\n | \"danger\"\n | \"monitoring\";\n\nexport type BadgeProps = Omit<\n React.HTMLAttributes<HTMLSpanElement>,\n \"className\"\n> & {\n variant?: BadgeVariant;\n className?: string;\n};\n\nconst cx = (...parts: Array<string | false | null | undefined>) =>\n parts.filter(Boolean).join(\" \");\n\nexport function Badge({\n variant = \"default\",\n className,\n ...rest\n}: BadgeProps) {\n return (\n <span\n className={cx(\"okai-badge\", `okai-badge--${variant}`, className)}\n {...rest}\n />\n );\n}\n","import * as React from \"react\";\n\n/**\n * `<Stat>` — big numeric value + tiny uppercase label.\n *\n * Used in tool-owner dashboards, /status pages, admin counters. The\n * value renders in monospace + tabular-nums so digits don't shift\n * when the number ticks.\n *\n * <Stat label=\"Subscribers\" value=\"1,284\" />\n * <Stat label=\"Active 30d\" value=\"98\" sub=\"across 12 tools\" />\n * <Stat label=\"MRR\" value=\"£3,852\" icon={<TrendingUp />} />\n */\nexport type StatProps = Omit<\n React.HTMLAttributes<HTMLDivElement>,\n \"className\" | \"children\"\n> & {\n label: React.ReactNode;\n value: React.ReactNode;\n /** Optional smaller line under the value. */\n sub?: React.ReactNode;\n /** Optional icon rendered before the label. */\n icon?: React.ReactNode;\n className?: string;\n};\n\nconst cx = (...parts: Array<string | false | null | undefined>) =>\n parts.filter(Boolean).join(\" \");\n\nexport function Stat({\n label,\n value,\n sub,\n icon,\n className,\n ...rest\n}: StatProps) {\n return (\n <div className={cx(\"okai-stat\", className)} {...rest}>\n <span className=\"okai-stat__label\">\n {icon ? <span aria-hidden>{icon}</span> : null}\n {label}\n </span>\n <span className=\"okai-stat__value\">{value}</span>\n {sub ? <span className=\"okai-stat__sub\">{sub}</span> : null}\n </div>\n );\n}\n","/**\n * `@openkeyai/ui` — public entry.\n *\n * Every OpenKey AI tool installs this package and mounts <HubHeader /> in\n * its root layout. CI enforces the mounting via an AST scanner\n * (see Phase 9 in the hub's BUILD_PLAN.md).\n *\n * **v1.0.0** — Major version. Palette swap (cool indigo → warm coral),\n * Material 3 elevation, light + dark themes, Roboto Flex display, plus\n * base components: `<Button>`, `<Card>`, `<Input>`, `<Select>`,\n * `<Textarea>`, `<Badge>`, `<Stat>`. Existing v0.x token NAMES are\n * preserved — only color values shifted.\n *\n * Entry paths:\n * - \"@openkeyai/ui\" → React components + their types (this file)\n * - \"@openkeyai/ui/tokens\" → Design tokens as a JS object\n * (dark + light + shared)\n * - \"@openkeyai/ui/tailwind\" → Tailwind preset\n * - \"@openkeyai/ui/css\" → The global stylesheet (CSS custom\n * properties + scoped .okai-* classes).\n * MUST be imported once by the consumer.\n *\n * See README.md for the canonical mounting pattern + migration guide.\n */\n\nexport { HubHeader } from \"./components/hub-header\";\nexport type {\n HubHeaderProps,\n HubHeaderUser,\n} from \"./components/hub-header\";\n\nexport { Button } from \"./components/button\";\nexport type { ButtonProps, ButtonSize, ButtonVariant } from \"./components/button\";\n\nexport { Card } from \"./components/card\";\nexport type { CardLevel, CardProps } from \"./components/card\";\n\nexport { Input, Select, Textarea } from \"./components/input\";\nexport type {\n InputProps,\n SelectProps,\n TextareaProps,\n} from \"./components/input\";\n\nexport { Badge } from \"./components/badge\";\nexport type { BadgeProps, BadgeVariant } from \"./components/badge\";\n\nexport { Stat } from \"./components/stat\";\nexport type { StatProps } from \"./components/stat\";\n\n/** Bumped on each release. Useful for tools to log which UI version they shipped against. */\nexport const UI_VERSION = \"1.0.0\";\n"]}
package/dist/styles.css CHANGED
@@ -20,17 +20,29 @@
20
20
  * ============================================================================= */
21
21
 
22
22
  :where(:root) {
23
- /* ─── Colors ─────────────────────────────────────────────────────────── */
24
- --okai-color-bg: #0a0c1a;
25
- --okai-color-surface: #11142a;
26
- --okai-color-surface-raised: #181d3a;
27
- --okai-color-foreground: #e6e8f5;
28
- --okai-color-foreground-muted: #9097b8;
29
- --okai-color-border: rgba(150, 160, 220, 0.12);
30
- --okai-color-accent: #7c6cf5;
31
- --okai-color-accent-bright: #9b8af9;
32
- --okai-color-destructive: #ef4459;
33
- --okai-color-success: #34d399;
23
+ /* ─── Colors DARK (default) ──────────────────────────────────────────
24
+ * v1.0.0 palette swap: cool indigo-violet → warm coral. Material 3
25
+ * key-color + Claude-warm anchors. See README for migration map. */
26
+ --okai-color-bg: oklch(0.13 0.008 35);
27
+ --okai-color-surface: oklch(0.17 0.010 35);
28
+ --okai-color-surface-1: oklch(0.17 0.010 35);
29
+ --okai-color-surface-2: oklch(0.21 0.012 35);
30
+ --okai-color-surface-3: oklch(0.25 0.014 35);
31
+ --okai-color-surface-4: oklch(0.29 0.016 35);
32
+ --okai-color-surface-raised: oklch(0.21 0.012 35);
33
+ --okai-color-foreground: oklch(0.96 0.005 35);
34
+ --okai-color-foreground-muted: oklch(0.74 0.008 35);
35
+ --okai-color-foreground-subtle: oklch(0.58 0.010 35);
36
+ --okai-color-border: oklch(1 0 0 / 0.08);
37
+ --okai-color-border-strong: oklch(1 0 0 / 0.14);
38
+ --okai-color-accent: oklch(0.76 0.17 35);
39
+ --okai-color-accent-bright: oklch(0.82 0.17 35);
40
+ --okai-color-accent-container: oklch(0.30 0.13 28 / 0.25);
41
+ --okai-color-on-accent-container: oklch(0.88 0.085 35);
42
+ --okai-color-on-accent: oklch(0.13 0.008 35);
43
+ --okai-color-destructive: oklch(0.74 0.18 28);
44
+ --okai-color-success: oklch(0.84 0.08 150);
45
+ --okai-color-warning: oklch(0.88 0.12 80);
34
46
 
35
47
  /* ─── Spacing (4px base) ─────────────────────────────────────────────── */
36
48
  --okai-space-px: 1px;
@@ -47,12 +59,14 @@
47
59
  --okai-space-12: 48px;
48
60
  --okai-space-16: 64px;
49
61
 
50
- /* ─── Radius ─────────────────────────────────────────────────────────── */
51
- --okai-radius-sm: 4px;
52
- --okai-radius-md: 8px;
53
- --okai-radius-lg: 12px;
54
- --okai-radius-xl: 16px;
55
- --okai-radius-2xl: 20px;
62
+ /* ─── Radius Material 3 shape scale ──────────────────────────────────
63
+ * Generous radii, predictable family. One radius per element. */
64
+ --okai-radius-xs: 6px;
65
+ --okai-radius-sm: 10px;
66
+ --okai-radius-md: 14px;
67
+ --okai-radius-lg: 20px;
68
+ --okai-radius-xl: 28px;
69
+ --okai-radius-2xl: 36px;
56
70
  --okai-radius-full: 9999px;
57
71
 
58
72
  /* ─── Typography ─────────────────────────────────────────────────────── */
@@ -60,27 +74,39 @@
60
74
  BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
61
75
  --okai-font-mono: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, Monaco,
62
76
  Consolas, "Liberation Mono", monospace;
77
+ /* Display — Roboto Flex via consumer-loaded next/font. Falls back to
78
+ the body sans if the consumer hasn't wired --font-display. */
79
+ --okai-font-display: var(--font-display, Geist, ui-sans-serif, system-ui, sans-serif);
63
80
  --okai-fs-xs: 11px;
64
81
  --okai-fs-sm: 13px;
65
82
  --okai-fs-base: 14px;
66
83
  --okai-fs-md: 16px;
67
84
  --okai-fs-lg: 18px;
68
85
  --okai-fs-xl: 22px;
86
+ --okai-fs-2xl: 28px;
87
+ --okai-fs-3xl: 36px;
88
+ --okai-fs-4xl: 48px;
89
+ --okai-fs-display: 60px;
90
+ --okai-lh-display: 1.05;
69
91
  --okai-lh-tight: 1.2;
70
92
  --okai-lh-normal: 1.5;
93
+ --okai-lh-relaxed: 1.65;
71
94
 
72
- /* ─── Shadows ────────────────────────────────────────────────────────── */
73
- --okai-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.25);
74
- --okai-shadow-md: 0 6px 24px -8px rgba(0, 0, 0, 0.5),
75
- 0 2px 6px rgba(0, 0, 0, 0.3);
76
- --okai-shadow-lg: 0 20px 60px -20px rgba(0, 0, 0, 0.6),
77
- 0 8px 24px rgba(0, 0, 0, 0.4);
95
+ /* ─── Shadows ──────────────────────────────────────────────────────────
96
+ * Dark mode: lighter shadows; surface lift carries hierarchy. */
97
+ --okai-shadow-sm:
98
+ 0 1px 0 oklch(1 0 0 / 0.04), 0 4px 12px oklch(0 0 0 / 0.20);
99
+ --okai-shadow-md:
100
+ 0 1px 0 oklch(1 0 0 / 0.05), 0 12px 28px oklch(0 0 0 / 0.30);
101
+ --okai-shadow-lg:
102
+ 0 1px 0 oklch(1 0 0 / 0.06), 0 24px 56px oklch(0 0 0 / 0.38);
78
103
 
79
- /* ─── Motion ─────────────────────────────────────────────────────────── */
80
- --okai-ease: cubic-bezier(0.4, 0, 0.2, 1);
81
- --okai-dur-fast: 120ms;
82
- --okai-dur-normal: 180ms;
83
- --okai-dur-slow: 260ms;
104
+ /* ─── Motion Material 3 easing + duration ────────────────────────── */
105
+ --okai-ease: cubic-bezier(0.2, 0.0, 0.0, 1.0);
106
+ --okai-ease-emphasized: cubic-bezier(0.05, 0.7, 0.1, 1.0);
107
+ --okai-dur-fast: 160ms;
108
+ --okai-dur-normal: 280ms;
109
+ --okai-dur-slow: 400ms;
84
110
 
85
111
  /* ─── Z-index ────────────────────────────────────────────────────────── */
86
112
  --okai-z-base: 0;
@@ -95,6 +121,62 @@
95
121
  --okai-header-h: 56px;
96
122
  }
97
123
 
124
+ /* ─── Light theme — opt-in by setting <html data-theme="light">. ────────
125
+ *
126
+ * Warm off-white surfaces (Claude-inspired). Real soft shadows because
127
+ * surface lift no longer carries hierarchy in light mode.
128
+ */
129
+ :where(:root[data-theme="light"]) {
130
+ --okai-color-bg: oklch(0.985 0.005 35);
131
+ --okai-color-surface: oklch(1 0 0);
132
+ --okai-color-surface-1: oklch(1 0 0);
133
+ --okai-color-surface-2: oklch(0.96 0.006 35);
134
+ --okai-color-surface-3: oklch(0.93 0.008 35);
135
+ --okai-color-surface-4: oklch(0.90 0.010 35);
136
+ --okai-color-surface-raised: oklch(0.96 0.006 35);
137
+ --okai-color-foreground: oklch(0.18 0.012 35);
138
+ --okai-color-foreground-muted: oklch(0.42 0.012 50);
139
+ --okai-color-foreground-subtle: oklch(0.58 0.010 50);
140
+ --okai-color-border: oklch(0 0 0 / 0.08);
141
+ --okai-color-border-strong: oklch(0 0 0 / 0.16);
142
+ --okai-color-accent: oklch(0.64 0.21 30);
143
+ --okai-color-accent-bright: oklch(0.56 0.20 28);
144
+ --okai-color-accent-container: oklch(0.93 0.045 35);
145
+ --okai-color-on-accent-container: oklch(0.30 0.13 28);
146
+ --okai-color-on-accent: oklch(1 0 0);
147
+ --okai-color-destructive: oklch(0.55 0.21 28);
148
+ --okai-color-success: oklch(0.52 0.10 150);
149
+ --okai-color-warning: oklch(0.60 0.15 70);
150
+
151
+ /* Real shadow in light mode. */
152
+ --okai-shadow-sm:
153
+ 0 1px 2px oklch(0.13 0.008 35 / 0.06),
154
+ 0 4px 12px oklch(0.13 0.008 35 / 0.06);
155
+ --okai-shadow-md:
156
+ 0 2px 4px oklch(0.13 0.008 35 / 0.08),
157
+ 0 12px 28px oklch(0.13 0.008 35 / 0.10);
158
+ --okai-shadow-lg:
159
+ 0 4px 8px oklch(0.13 0.008 35 / 0.10),
160
+ 0 24px 56px oklch(0.13 0.008 35 / 0.14);
161
+ }
162
+
163
+ /* Reduce motion respects user preference across the system. */
164
+ @media (prefers-reduced-motion: reduce) {
165
+ :where(:root) {
166
+ --okai-dur-fast: 0ms;
167
+ --okai-dur-normal: 0ms;
168
+ --okai-dur-slow: 0ms;
169
+ }
170
+ }
171
+
172
+ /* ─── Display font utility ──────────────────────────────────────────────
173
+ * Drop on hero headlines for the Material display character. */
174
+ .okai-display {
175
+ font-family: var(--okai-font-display);
176
+ font-weight: 600;
177
+ letter-spacing: -0.02em;
178
+ }
179
+
98
180
  /* =============================================================================
99
181
  * HubHeader
100
182
  *
@@ -306,3 +388,244 @@
306
388
  padding: var(--okai-space-1);
307
389
  }
308
390
  }
391
+
392
+ /* =============================================================================
393
+ * Base components (v1.0.0)
394
+ *
395
+ * Every component renders with a single root class `okai-<comp>` + variant
396
+ * modifiers `okai-<comp>--<variant>`. Consumers can layer their own
397
+ * className on top — the base styles use low specificity so they're
398
+ * easy to override per-tool.
399
+ * ============================================================================= */
400
+
401
+ /* ─── Button ──────────────────────────────────────────────────────────── */
402
+
403
+ .okai-btn {
404
+ display: inline-flex;
405
+ align-items: center;
406
+ justify-content: center;
407
+ gap: var(--okai-space-2);
408
+ border: 1px solid transparent;
409
+ border-radius: var(--okai-radius-md);
410
+ padding: 0 var(--okai-space-4);
411
+ height: 38px;
412
+ font-family: var(--okai-font-sans);
413
+ font-size: var(--okai-fs-sm);
414
+ font-weight: 600;
415
+ line-height: 1;
416
+ cursor: pointer;
417
+ text-decoration: none;
418
+ white-space: nowrap;
419
+ transition:
420
+ background-color var(--okai-dur-fast) var(--okai-ease),
421
+ border-color var(--okai-dur-fast) var(--okai-ease),
422
+ color var(--okai-dur-fast) var(--okai-ease),
423
+ transform var(--okai-dur-fast) var(--okai-ease);
424
+ }
425
+ .okai-btn:focus-visible {
426
+ outline: 2px solid var(--okai-color-accent-bright);
427
+ outline-offset: 2px;
428
+ }
429
+ .okai-btn:disabled,
430
+ .okai-btn[aria-disabled="true"] {
431
+ opacity: 0.55;
432
+ cursor: not-allowed;
433
+ }
434
+
435
+ /* Sizes */
436
+ .okai-btn--sm { height: 30px; padding: 0 var(--okai-space-3); font-size: var(--okai-fs-xs); }
437
+ .okai-btn--md { /* default */ }
438
+ .okai-btn--lg { height: 46px; padding: 0 var(--okai-space-5); font-size: var(--okai-fs-base); }
439
+
440
+ /* Variants */
441
+ .okai-btn--primary {
442
+ background: var(--okai-color-accent);
443
+ color: var(--okai-color-on-accent);
444
+ }
445
+ .okai-btn--primary:hover { background: var(--okai-color-accent-bright); }
446
+
447
+ .okai-btn--secondary {
448
+ background: transparent;
449
+ color: var(--okai-color-foreground);
450
+ border-color: var(--okai-color-border-strong);
451
+ }
452
+ .okai-btn--secondary:hover {
453
+ background: color-mix(in srgb, var(--okai-color-foreground) 6%, transparent);
454
+ }
455
+
456
+ .okai-btn--tonal {
457
+ background: var(--okai-color-accent-container);
458
+ color: var(--okai-color-on-accent-container);
459
+ }
460
+ .okai-btn--tonal:hover {
461
+ background: color-mix(in srgb, var(--okai-color-accent) 18%, transparent);
462
+ }
463
+
464
+ .okai-btn--text {
465
+ background: transparent;
466
+ color: var(--okai-color-foreground-muted);
467
+ }
468
+ .okai-btn--text:hover {
469
+ background: color-mix(in srgb, var(--okai-color-foreground) 6%, transparent);
470
+ color: var(--okai-color-foreground);
471
+ }
472
+
473
+ .okai-btn--destructive {
474
+ background: var(--okai-color-destructive);
475
+ color: oklch(1 0 0);
476
+ }
477
+ .okai-btn--destructive:hover {
478
+ filter: brightness(1.06);
479
+ }
480
+
481
+ /* Icon-only square button — square ratio, padding zeroed. */
482
+ .okai-btn--icon { padding: 0; aspect-ratio: 1 / 1; }
483
+
484
+
485
+ /* ─── Card ────────────────────────────────────────────────────────────── */
486
+
487
+ .okai-card {
488
+ background: var(--okai-color-surface-1);
489
+ color: var(--okai-color-foreground);
490
+ border: 1px solid var(--okai-color-border);
491
+ border-radius: var(--okai-radius-lg);
492
+ padding: var(--okai-space-5);
493
+ box-shadow: none;
494
+ }
495
+ .okai-card--level-0 { background: transparent; border-color: transparent; padding: 0; }
496
+ .okai-card--level-1 { box-shadow: var(--okai-shadow-sm); }
497
+ .okai-card--level-2 {
498
+ background: var(--okai-color-surface-2);
499
+ box-shadow: var(--okai-shadow-md);
500
+ }
501
+ .okai-card--level-3 {
502
+ background: var(--okai-color-surface-3);
503
+ box-shadow: var(--okai-shadow-lg);
504
+ }
505
+
506
+ /* Optional interactive card variant — hover lifts. */
507
+ .okai-card--interactive {
508
+ transition:
509
+ background-color var(--okai-dur-fast) var(--okai-ease),
510
+ border-color var(--okai-dur-fast) var(--okai-ease),
511
+ transform var(--okai-dur-fast) var(--okai-ease);
512
+ cursor: pointer;
513
+ }
514
+ .okai-card--interactive:hover {
515
+ background: var(--okai-color-surface-2);
516
+ border-color: var(--okai-color-border-strong);
517
+ }
518
+
519
+
520
+ /* ─── Input / Select / Textarea ────────────────────────────────────────── */
521
+
522
+ .okai-input,
523
+ .okai-select,
524
+ .okai-textarea {
525
+ display: block;
526
+ width: 100%;
527
+ background: var(--okai-color-surface);
528
+ color: var(--okai-color-foreground);
529
+ border: 1px solid var(--okai-color-border-strong);
530
+ border-radius: var(--okai-radius-sm);
531
+ padding: var(--okai-space-2) var(--okai-space-3);
532
+ font-family: var(--okai-font-sans);
533
+ font-size: var(--okai-fs-sm);
534
+ line-height: var(--okai-lh-normal);
535
+ transition: border-color var(--okai-dur-fast) var(--okai-ease),
536
+ box-shadow var(--okai-dur-fast) var(--okai-ease);
537
+ -webkit-appearance: none;
538
+ appearance: none;
539
+ }
540
+ .okai-input::placeholder,
541
+ .okai-textarea::placeholder {
542
+ color: var(--okai-color-foreground-subtle);
543
+ }
544
+ .okai-input:focus-visible,
545
+ .okai-select:focus-visible,
546
+ .okai-textarea:focus-visible {
547
+ outline: none;
548
+ border-color: var(--okai-color-accent);
549
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--okai-color-accent) 25%, transparent);
550
+ }
551
+ .okai-input:disabled,
552
+ .okai-select:disabled,
553
+ .okai-textarea:disabled {
554
+ opacity: 0.55;
555
+ cursor: not-allowed;
556
+ }
557
+ .okai-textarea { min-height: 96px; resize: vertical; }
558
+ .okai-input--mono { font-family: var(--okai-font-mono); }
559
+
560
+
561
+ /* ─── Badge ──────────────────────────────────────────────────────────── */
562
+
563
+ .okai-badge {
564
+ display: inline-flex;
565
+ align-items: center;
566
+ gap: var(--okai-space-1);
567
+ padding: var(--okai-space-0_5) var(--okai-space-2);
568
+ border-radius: var(--okai-radius-full);
569
+ font-family: var(--okai-font-sans);
570
+ font-size: 10px;
571
+ font-weight: 600;
572
+ text-transform: uppercase;
573
+ letter-spacing: 0.06em;
574
+ line-height: 1.4;
575
+ }
576
+ .okai-badge--default {
577
+ background: color-mix(in srgb, var(--okai-color-foreground) 10%, transparent);
578
+ color: var(--okai-color-foreground-muted);
579
+ }
580
+ .okai-badge--brand {
581
+ background: var(--okai-color-accent-container);
582
+ color: var(--okai-color-on-accent-container);
583
+ }
584
+ .okai-badge--success {
585
+ background: color-mix(in srgb, var(--okai-color-success) 20%, transparent);
586
+ color: var(--okai-color-success);
587
+ }
588
+ .okai-badge--warning {
589
+ background: color-mix(in srgb, var(--okai-color-warning) 22%, transparent);
590
+ color: var(--okai-color-warning);
591
+ }
592
+ .okai-badge--danger {
593
+ background: color-mix(in srgb, var(--okai-color-destructive) 22%, transparent);
594
+ color: var(--okai-color-destructive);
595
+ }
596
+ .okai-badge--monitoring {
597
+ background: color-mix(in srgb, var(--okai-color-warning) 18%, transparent);
598
+ color: var(--okai-color-warning);
599
+ }
600
+
601
+
602
+ /* ─── Stat ────────────────────────────────────────────────────────────── */
603
+
604
+ .okai-stat {
605
+ display: flex;
606
+ flex-direction: column;
607
+ gap: var(--okai-space-1);
608
+ }
609
+ .okai-stat__label {
610
+ display: inline-flex;
611
+ align-items: center;
612
+ gap: var(--okai-space-1_5);
613
+ font-family: var(--okai-font-sans);
614
+ font-size: 10px;
615
+ font-weight: 600;
616
+ text-transform: uppercase;
617
+ letter-spacing: 0.18em;
618
+ color: var(--okai-color-foreground-muted);
619
+ }
620
+ .okai-stat__value {
621
+ font-family: var(--okai-font-mono);
622
+ font-size: var(--okai-fs-2xl);
623
+ font-weight: 600;
624
+ line-height: var(--okai-lh-tight);
625
+ font-variant-numeric: tabular-nums;
626
+ color: var(--okai-color-foreground);
627
+ }
628
+ .okai-stat__sub {
629
+ font-size: var(--okai-fs-xs);
630
+ color: var(--okai-color-foreground-subtle);
631
+ }