@astralform/ui 0.0.0-stage → 0.3.6

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 (139) hide show
  1. package/DESIGN.md +243 -0
  2. package/LICENSE +21 -0
  3. package/README.md +128 -2
  4. package/dist/components/Alert.d.ts +7 -0
  5. package/dist/components/Alert.js +40 -0
  6. package/dist/components/Alert.js.map +1 -0
  7. package/dist/components/AvatarEditor.d.ts +13 -0
  8. package/dist/components/AvatarEditor.js +44 -0
  9. package/dist/components/AvatarEditor.js.map +1 -0
  10. package/dist/components/Badge.d.ts +16 -0
  11. package/dist/components/Badge.js +38 -0
  12. package/dist/components/Badge.js.map +1 -0
  13. package/dist/components/Button.d.ts +8 -0
  14. package/dist/components/Button.js +21 -0
  15. package/dist/components/Button.js.map +1 -0
  16. package/dist/components/Card.d.ts +10 -0
  17. package/dist/components/Card.js +22 -0
  18. package/dist/components/Card.js.map +1 -0
  19. package/dist/components/CompactResourceRow.d.ts +34 -0
  20. package/dist/components/CompactResourceRow.js +11 -0
  21. package/dist/components/CompactResourceRow.js.map +1 -0
  22. package/dist/components/ConfirmDialog.d.ts +12 -0
  23. package/dist/components/ConfirmDialog.js +7 -0
  24. package/dist/components/ConfirmDialog.js.map +1 -0
  25. package/dist/components/CookieConsent.d.ts +1 -0
  26. package/dist/components/CookieConsent.js +42 -0
  27. package/dist/components/CookieConsent.js.map +1 -0
  28. package/dist/components/DataTable.d.ts +53 -0
  29. package/dist/components/DataTable.js +79 -0
  30. package/dist/components/DataTable.js.map +1 -0
  31. package/dist/components/DevTools.d.ts +6 -0
  32. package/dist/components/DevTools.js +59 -0
  33. package/dist/components/DevTools.js.map +1 -0
  34. package/dist/components/EmptyState.d.ts +11 -0
  35. package/dist/components/EmptyState.js +6 -0
  36. package/dist/components/EmptyState.js.map +1 -0
  37. package/dist/components/ImageCropModal.d.ts +18 -0
  38. package/dist/components/ImageCropModal.js +67 -0
  39. package/dist/components/ImageCropModal.js.map +1 -0
  40. package/dist/components/InlineBanner.d.ts +20 -0
  41. package/dist/components/InlineBanner.js +53 -0
  42. package/dist/components/InlineBanner.js.map +1 -0
  43. package/dist/components/Input.d.ts +8 -0
  44. package/dist/components/Input.js +10 -0
  45. package/dist/components/Input.js.map +1 -0
  46. package/dist/components/InventoryCard.d.ts +20 -0
  47. package/dist/components/InventoryCard.js +22 -0
  48. package/dist/components/InventoryCard.js.map +1 -0
  49. package/dist/components/LoadingSpinner.d.ts +26 -0
  50. package/dist/components/LoadingSpinner.js +32 -0
  51. package/dist/components/LoadingSpinner.js.map +1 -0
  52. package/dist/components/Modal.d.ts +10 -0
  53. package/dist/components/Modal.js +38 -0
  54. package/dist/components/Modal.js.map +1 -0
  55. package/dist/components/PageHeader.d.ts +11 -0
  56. package/dist/components/PageHeader.js +5 -0
  57. package/dist/components/PageHeader.js.map +1 -0
  58. package/dist/components/PreviewModal.d.ts +24 -0
  59. package/dist/components/PreviewModal.js +69 -0
  60. package/dist/components/PreviewModal.js.map +1 -0
  61. package/dist/components/ProgressBar.d.ts +8 -0
  62. package/dist/components/ProgressBar.js +12 -0
  63. package/dist/components/ProgressBar.js.map +1 -0
  64. package/dist/components/ProviderIcon.d.ts +7 -0
  65. package/dist/components/ProviderIcon.js +34 -0
  66. package/dist/components/ProviderIcon.js.map +1 -0
  67. package/dist/components/ResizeHandle.d.ts +56 -0
  68. package/dist/components/ResizeHandle.js +166 -0
  69. package/dist/components/ResizeHandle.js.map +1 -0
  70. package/dist/components/SegmentedProgressBar.d.ts +9 -0
  71. package/dist/components/SegmentedProgressBar.js +23 -0
  72. package/dist/components/SegmentedProgressBar.js.map +1 -0
  73. package/dist/components/Select.d.ts +13 -0
  74. package/dist/components/Select.js +14 -0
  75. package/dist/components/Select.js.map +1 -0
  76. package/dist/components/SpotlightButton.d.ts +10 -0
  77. package/dist/components/SpotlightButton.js +51 -0
  78. package/dist/components/SpotlightButton.js.map +1 -0
  79. package/dist/components/StatCard.d.ts +9 -0
  80. package/dist/components/StatCard.js +8 -0
  81. package/dist/components/StatCard.js.map +1 -0
  82. package/dist/components/StatusBadge.d.ts +4 -0
  83. package/dist/components/StatusBadge.js +10 -0
  84. package/dist/components/StatusBadge.js.map +1 -0
  85. package/dist/components/TabNav.d.ts +39 -0
  86. package/dist/components/TabNav.js +25 -0
  87. package/dist/components/TabNav.js.map +1 -0
  88. package/dist/components/TableShell.d.ts +21 -0
  89. package/dist/components/TableShell.js +19 -0
  90. package/dist/components/TableShell.js.map +1 -0
  91. package/dist/components/Textarea.d.ts +7 -0
  92. package/dist/components/Textarea.js +10 -0
  93. package/dist/components/Textarea.js.map +1 -0
  94. package/dist/components/Toggle.d.ts +16 -0
  95. package/dist/components/Toggle.js +25 -0
  96. package/dist/components/Toggle.js.map +1 -0
  97. package/dist/components/Toolbar.d.ts +34 -0
  98. package/dist/components/Toolbar.js +30 -0
  99. package/dist/components/Toolbar.js.map +1 -0
  100. package/dist/components/TypeToConfirmModal.d.ts +15 -0
  101. package/dist/components/TypeToConfirmModal.js +14 -0
  102. package/dist/components/TypeToConfirmModal.js.map +1 -0
  103. package/dist/components/UnsavedChangesModal.d.ts +7 -0
  104. package/dist/components/UnsavedChangesModal.js +7 -0
  105. package/dist/components/UnsavedChangesModal.js.map +1 -0
  106. package/dist/components/UsageCard.d.ts +11 -0
  107. package/dist/components/UsageCard.js +9 -0
  108. package/dist/components/UsageCard.js.map +1 -0
  109. package/dist/components/avatar.d.ts +79 -0
  110. package/dist/components/avatar.js +88 -0
  111. package/dist/components/avatar.js.map +1 -0
  112. package/dist/components/badge-config.d.ts +16 -0
  113. package/dist/components/badge-config.js +48 -0
  114. package/dist/components/badge-config.js.map +1 -0
  115. package/dist/components/glowing-effect.d.ts +14 -0
  116. package/dist/components/glowing-effect.js +108 -0
  117. package/dist/components/glowing-effect.js.map +1 -0
  118. package/dist/index.d.ts +39 -0
  119. package/dist/index.js +42 -0
  120. package/dist/index.js.map +1 -0
  121. package/dist/lib/cookies.d.ts +36 -0
  122. package/dist/lib/cookies.js +78 -0
  123. package/dist/lib/cookies.js.map +1 -0
  124. package/dist/lib/environment.d.ts +24 -0
  125. package/dist/lib/environment.js +52 -0
  126. package/dist/lib/environment.js.map +1 -0
  127. package/dist/lib/utils.d.ts +4 -0
  128. package/dist/lib/utils.js +8 -0
  129. package/dist/lib/utils.js.map +1 -0
  130. package/dist/types.d.ts +12 -0
  131. package/dist/types.js +12 -0
  132. package/dist/types.js.map +1 -0
  133. package/package.json +89 -4
  134. package/src/styles/base.css +779 -0
  135. package/src/styles/fonts.css +25 -0
  136. package/src/styles/theme-default.css +164 -0
  137. package/src/styles/tokens/_foundations.css +28 -0
  138. package/src/styles/tokens/_semantics.css +72 -0
  139. package/themes/chat.DESIGN.md +197 -0
package/DESIGN.md ADDED
@@ -0,0 +1,243 @@
1
+ ---
2
+ version: alpha
3
+ name: Astralform Dashboard
4
+ description: Dark-first, focused control-surface design language — warm orange accent on near-black, built for dense developer tooling.
5
+
6
+ colors:
7
+ # ---- Brand accent (orange) — the single system tint ----
8
+ primary: "#fb923c"
9
+ accent-hover: "#fdba74"
10
+ accent: "#fb923c"
11
+ accent-muted: "#f97316"
12
+ accent-bold: "#ea580c"
13
+ accent-dim: "#c2410c"
14
+ accent-subtle: "#7c2d12"
15
+ accent-surface: "#431407"
16
+
17
+ # ---- Surfaces (darkest → lightest) ----
18
+ surface-base: "#000000"
19
+ surface-primary: "#09090b"
20
+ surface-secondary: "#18181b"
21
+ surface-tertiary: "#27272a"
22
+ surface-inset: "#3f3f46"
23
+
24
+ # ---- Borders ----
25
+ border-primary: "#27272a"
26
+ border-secondary: "#3f3f46"
27
+ border-focus: "#52525b"
28
+
29
+ # ---- Text (brightest → dimmest) ----
30
+ text-primary: "#ffffff"
31
+ text-secondary: "#d4d4d8"
32
+ text-tertiary: "#a1a1aa"
33
+ text-muted: "#71717a"
34
+ text-disabled: "#52525b"
35
+ text-inverse: "#000000"
36
+
37
+ # ---- Buttons ----
38
+ btn-primary-bg: "#ffffff"
39
+ btn-primary-hover: "#f4f4f5"
40
+ btn-danger-bg: "#dc2626"
41
+ btn-danger-hover: "#b91c1c"
42
+
43
+ # ---- Status (semantic — NOT the accent) ----
44
+ success: "#34d399"
45
+ success-subtle: "#064e3b"
46
+ success-dim: "#047857"
47
+ warning: "#fbbf24"
48
+ warning-subtle: "#78350f"
49
+ warning-dim: "#b45309"
50
+ danger: "#f87171"
51
+ danger-subtle: "#7f1d1d"
52
+ danger-dim: "#b91c1c"
53
+ info: "#60a5fa"
54
+ info-subtle: "#1e3a5f"
55
+ info-dim: "#1d4ed8"
56
+
57
+ typography:
58
+ page-title:
59
+ fontFamily: Outfit
60
+ fontSize: 24px
61
+ fontWeight: 700
62
+ lineHeight: 1.25
63
+ section-heading:
64
+ fontFamily: Outfit
65
+ fontSize: 18px
66
+ fontWeight: 600
67
+ lineHeight: 1.25
68
+ card-title:
69
+ fontFamily: Outfit
70
+ fontSize: 18px
71
+ fontWeight: 600
72
+ lineHeight: 1
73
+ letterSpacing: -0.025em
74
+ body:
75
+ fontFamily: Plus Jakarta Sans
76
+ fontSize: 14px
77
+ fontWeight: 400
78
+ lineHeight: 1.5
79
+ label:
80
+ fontFamily: Plus Jakarta Sans
81
+ fontSize: 14px
82
+ fontWeight: 500
83
+ lineHeight: 1.5
84
+ caption:
85
+ fontFamily: Plus Jakarta Sans
86
+ fontSize: 12px
87
+ fontWeight: 400
88
+ lineHeight: 1.5
89
+ overline:
90
+ fontFamily: Plus Jakarta Sans
91
+ fontSize: 12px
92
+ fontWeight: 600
93
+ letterSpacing: 0.05em
94
+
95
+ spacing:
96
+ xs: 4px
97
+ sm: 8px
98
+ md: 16px
99
+ lg: 24px
100
+ xl: 32px
101
+ xxl: 48px
102
+
103
+ rounded:
104
+ input: 8px
105
+ button: 8px
106
+ alert: 8px
107
+ modal: 12px
108
+ card: 20px
109
+ badge: 9999px
110
+
111
+ components:
112
+ button-primary:
113
+ backgroundColor: "{colors.btn-primary-bg}"
114
+ textColor: "{colors.text-inverse}"
115
+ rounded: "{rounded.button}"
116
+ button-danger:
117
+ backgroundColor: "{colors.btn-danger-bg}"
118
+ textColor: "{colors.text-primary}"
119
+ rounded: "{rounded.button}"
120
+ card:
121
+ backgroundColor: "{colors.surface-primary}"
122
+ rounded: "{rounded.card}"
123
+ badge-accent:
124
+ backgroundColor: "{colors.accent-subtle}"
125
+ textColor: "{colors.accent-hover}"
126
+ rounded: "{rounded.badge}"
127
+ input:
128
+ backgroundColor: "{colors.surface-secondary}"
129
+ textColor: "{colors.text-primary}"
130
+ rounded: "{rounded.input}"
131
+ ---
132
+
133
+ ## Overview
134
+
135
+ Astralform's dashboard is a **control surface for developers** — dense, dark, and
136
+ calm. It should feel like a precision instrument, not a marketing site: information
137
+ sits close together, chrome recedes, and a single warm accent draws the eye only to
138
+ the thing you can act on right now.
139
+
140
+ The personality is **quiet confidence**. Near-black surfaces let syntax, data, and
141
+ status colors carry meaning without competing. Motion is purposeful and short. The
142
+ target user is a developer configuring agents, skills, and billing — they value
143
+ legibility and predictability over delight-for-its-own-sake.
144
+
145
+ This file is the **token contract** for the brand. Components live in `@astralform/ui`
146
+ and read only the semantic tokens below; swapping this file (see `themes/chat.DESIGN.md`)
147
+ re-skins every product without touching component code.
148
+
149
+ ## Colors
150
+
151
+ The palette is a **neutral zinc greyscale** (surfaces, borders, text) plus **one brand
152
+ tint (orange)** and **four semantic status hues** (emerald / amber / red / blue).
153
+
154
+ - **Accent = system tint.** Orange is reserved for *interactive or active chrome*:
155
+ links, toggles, focus rings, selected tabs, active states. It is the iOS-style
156
+ "this is the live thing." Every accent token comes in a 7-tier scale
157
+ (`hover → default → muted → bold → dim → subtle → surface`) so the same hue serves
158
+ hover text, filled controls, borders, and deep backgrounds at the right contrast.
159
+ - **Status colors are not the accent.** Emerald/amber/red/blue carry *meaning*
160
+ (success, warning, danger, info). A category badge keeps its own semantic color —
161
+ it never adopts the accent just because it is interactive.
162
+ - **Surfaces stack darkest-first.** `surface-base` (#000) is the page; `surface-primary`
163
+ (#09090b) is the default panel; each step up the scale lifts a layer (cards, inputs,
164
+ insets) without a single shadow doing the work.
165
+
166
+ ## Typography
167
+
168
+ Two families, one job each. **Outfit** (geometric, confident) sets every heading and
169
+ title; **Plus Jakarta Sans** (humanist, legible at small sizes) carries body, labels,
170
+ captions, and overlines. Type is expressed as **roles, not sizes** — `page-title`,
171
+ `section-heading`, `card-title`, `body`, `label`, `caption`, `overline` — so a screen
172
+ reads in a consistent hierarchy regardless of which component renders it.
173
+
174
+ Sizes stay compact (14px body, 12px caption) because the surface is dense. Card titles
175
+ tighten letter-spacing (`-0.025em`) to feel engineered; overlines widen it (`0.05em`)
176
+ and uppercase to act as quiet section markers.
177
+
178
+ ## Layout
179
+
180
+ A **column-and-panel** model: a fixed sidebar (`12rem`, `3.25rem` collapsed), a 4rem
181
+ header, and a content column that breathes via the `page-content` utility
182
+ (`clamp(1rem, 4%, 4rem)` horizontal padding) instead of hardcoded gutters. Secondary
183
+ surfaces (workspace, conversation sidebar, battle-test panel) have named widths so the
184
+ shell composes predictably across screens.
185
+
186
+ Spacing follows a **4px rhythm** (`xs 4 · sm 8 · md 16 · lg 24 · xl 32 · 2xl 48`).
187
+ Containment is structural: related controls share a card; a card is the unit of
188
+ grouping, not a decorative box.
189
+
190
+ ## Elevation & Depth
191
+
192
+ **Flat-first.** Depth comes primarily from the surface scale (stacking darker-to-lighter
193
+ panels), not heavy drop shadows. Shadows are reserved, role-named, and subtle:
194
+ `shadow-card` (rest) → `shadow-button-hover` → `shadow-dropdown` → `shadow-modal`. A card
195
+ sits a hair above the page; a modal floats clearly above everything. Glows
196
+ (`pulse-glow`, white-tinted lifts) are used sparingly for celebratory or live states,
197
+ never as default chrome.
198
+
199
+ ## Shapes
200
+
201
+ Rounding signals **scale of container**. Small interactive atoms (inputs, buttons,
202
+ alerts) use an 8px radius — crisp, tappable. Modals step up to 12px. **Cards use a
203
+ generous 20px** (`rounded-card`) — the softest corner in the system, marking the primary
204
+ unit of grouping. Badges are fully pill-shaped (`9999px`). The progression
205
+ small→medium→large radius reinforces the visual hierarchy of nesting.
206
+
207
+ ## Components
208
+
209
+ Atoms in `@astralform/ui` are **token-only** — they never hardcode a hex value.
210
+
211
+ - **Button** — `primary` is a high-contrast white fill on dark (`btn-primary-bg` /
212
+ `text-inverse`); `secondary` is a bordered surface; `danger` is red; `ghost` is
213
+ chromeless. All share `rounded-button`, `btn-press` (scale-on-active), and the unified
214
+ `focus-ring`.
215
+ - **Card** — `surface-primary` fill, hairline `border-primary`, `rounded-card`,
216
+ `shadow-card`. The default grouping container. Sub-parts (`CardHeader/Title/...`) use
217
+ the `card-title` type role.
218
+ - **Badge** — category-driven: a semantic `variant` (success/warning/danger/info/accent)
219
+ resolves background+text+border from the matching status scale at ~50% subtle bg.
220
+ Pill radius. Carries an optional Phosphor icon.
221
+ - **Input / Select / Textarea / Toggle** — `surface-secondary` fill, `border-secondary`,
222
+ `rounded-input`, `focus-ring`. Toggles use the accent for the "on" track.
223
+
224
+ ## Do's and Don'ts
225
+
226
+ **Do**
227
+ - Use the **accent only** for interactive/active chrome (links, toggles, focus rings,
228
+ selected tabs). Treat it as the single system tint.
229
+ - Keep category/status colors semantic — a Capsule/Shell/Refs badge keeps its own hue.
230
+ - Reach for a **type role** (`type-page-title`, `type-card-title`, `type-label`) before
231
+ ad-hoc `text-*` sizes.
232
+ - Build depth from the **surface scale** first; add a shadow only when a layer must
233
+ clearly float (dropdown, modal).
234
+ - Use **monochrome / Phosphor icons** — professional, not colorful emoji.
235
+
236
+ **Don't**
237
+ - Don't paint static decoration with the accent — it dilutes the "this is live" signal.
238
+ - Don't add `hover:bg-*` backgrounds to clickable **list rows**; use `cursor-pointer`
239
+ and let the row stay flat.
240
+ - Don't hardcode hex values in components — every color flows from a token here.
241
+ - Don't duplicate a token's value inline; reference the scale so a re-skin stays a
242
+ one-file change.
243
+ - Don't mix font families across roles — Outfit for headings, Plus Jakarta Sans for text.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Astralform
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 CHANGED
@@ -1,3 +1,129 @@
1
- # Temporary Holding Version
1
+ # @astralform/ui
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Astralform's shared, **theme-swappable** React component system — one set of ~35
4
+ token-only components driven by a [DESIGN.md](./DESIGN.md) token contract.
5
+
6
+ Every component reads only **semantic tokens** (`bg-accent`, `text-text-primary`,
7
+ `rounded-card`, `type-card-title`). The brand — colors, fonts — lives entirely in a
8
+ theme CSS file generated from a `DESIGN.md`. Swap the theme, re-skin the product; the
9
+ component code never changes. The dashboard ships orange-on-black; AstralChat ships
10
+ indigo-on-warm-dark — from the same package.
11
+
12
+ ```
13
+ DESIGN.md ──(values)──► theme-*.css ─┐
14
+ ├─► your app
15
+ base.css ──(structure)──────────────►─┘ same components, your brand
16
+ @astralform/ui components (token-only) ─┘
17
+ ```
18
+
19
+ ## Install
20
+
21
+ This package is published **privately to GitHub Packages** under the `astralform-ai`
22
+ org. Point the `@astralform-ai` scope at the GitHub registry once per consuming repo
23
+ (commit this `.npmrc`):
24
+
25
+ ```ini
26
+ # .npmrc
27
+ @astralform-ai:registry=https://npm.pkg.github.com
28
+ //npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}
29
+ ```
30
+
31
+ Then set `GITHUB_TOKEN` to a token with `read:packages` (local dev: a classic PAT;
32
+ CI: the built-in `secrets.GITHUB_TOKEN`), and install:
33
+
34
+ ```bash
35
+ npm install @astralform/ui
36
+ ```
37
+
38
+ Peer deps you likely already have (React 19, Tailwind v4). Component-specific peers are
39
+ optional and only needed if you use the component that imports them (`@tanstack/react-table`
40
+ → `DataTable`, `react-easy-crop` → `ImageCropModal`, `sonner` → toasts).
41
+
42
+ ## Wire it up (Tailwind v4)
43
+
44
+ In your app's main CSS:
45
+
46
+ ```css
47
+ /* Self-hosted brand fonts (no third-party CDN, no render-blocking round-trip) */
48
+ @import "@astralform/ui/styles/fonts.css";
49
+ @import "tailwindcss";
50
+
51
+ /* brand-agnostic structure: radius, layout, utilities, keyframes */
52
+ @import "@astralform/ui/styles/base.css";
53
+
54
+ /* ONE brand theme — the default (dashboard/orange), or your own (below) */
55
+ @import "@astralform/ui/styles/theme-default.css";
56
+
57
+ /* REQUIRED: Tailwind v4 must scan the package so its utility classes are generated */
58
+ @source "../node_modules/@astralform/ui/dist";
59
+ ```
60
+
61
+ Then use components anywhere:
62
+
63
+ ```tsx
64
+ import { Button, Card, Badge, Toggle } from "@astralform/ui";
65
+
66
+ <Card>
67
+ <Button variant="primary">Deploy</Button>
68
+ <Badge variant="success">Ready</Badge>
69
+ </Card>
70
+ ```
71
+
72
+ ## Theme your own product
73
+
74
+ The brand is **just a token file**. To create a new skin (e.g. AstralChat's indigo):
75
+
76
+ 1. Copy [`themes/chat.DESIGN.md`](./themes/chat.DESIGN.md) into your app, edit the
77
+ `colors` / `typography` front-matter to taste.
78
+ 2. Generate a Tailwind v4 `@theme` block from it:
79
+ ```bash
80
+ npx @google/design.md export --format css-tailwind chat.DESIGN.md > src/styles/theme.css
81
+ ```
82
+ 3. Import `base.css` + **your** `theme.css` (instead of `theme-default.css`).
83
+
84
+ That's the whole re-skin. No component is forked.
85
+
86
+ > The shipped `theme-default.css` keeps a richer structure than a raw export (a `--_*`
87
+ > source-var layer for runtime theming, toast colors, overlay). For a new brand you can
88
+ > start from the raw export and layer those on if you need them.
89
+
90
+ ## The DESIGN.md contract
91
+
92
+ [`DESIGN.md`](./DESIGN.md) is the source of truth for the dashboard brand: YAML
93
+ front-matter tokens (machine-readable) + prose rationale (the *why* and the rules — accent
94
+ is the single system tint, status colors stay semantic, flat-first depth, etc.). It is
95
+ validated and kept honest by the [`@google/design.md`](https://github.com/google-labs-code/design.md)
96
+ CLI.
97
+
98
+ ```bash
99
+ npm run design:lint # validate DESIGN.md (token refs, contrast, section order)
100
+ npm run design:gen # drift guard: assert DESIGN.md values == theme-default.css
101
+ ```
102
+
103
+ `design:gen` exports the spec's colors and fails if the committed CSS has drifted from
104
+ the spec — so the spec and the styles can never silently disagree.
105
+
106
+ ## Develop
107
+
108
+ ```bash
109
+ npm install
110
+ npm run build # tsup → dist/ (ESM + .d.ts)
111
+ npm run typecheck # tsc --noEmit
112
+ npm run dev # tsup --watch
113
+ ```
114
+
115
+ ## Components
116
+
117
+ Buttons & inputs: `Button` · `Input` · `Textarea` · `Select` · `Toggle`
118
+ Containers: `Card` (+ `CardHeader/Title/Description/Content`) · `Modal` · `Alert` ·
119
+ `InlineBanner` · `EmptyState` · `PageHeader` · `TabNav` · `Toolbar`
120
+ Data: `DataTable` · `TableShell` · `StatCard` · `UsageCard` · `InventoryCard` ·
121
+ `CompactResourceRow` · `ProgressBar` · `SegmentedProgressBar`
122
+ Status & identity: `Badge` · `StatusBadge` · `Avatar` · `ProviderIcon` · `LoadingSpinner`
123
+ Dialogs: `ConfirmDialog` · `TypeToConfirmModal` · `UnsavedChangesModal` · `PreviewModal` ·
124
+ `ImageCropModal`
125
+ Misc: `SpotlightButton` · `GlowingEffect` · `CookieConsent` · `DevTools` · `ResizeHandle`
126
+
127
+ ## License
128
+
129
+ MIT
@@ -0,0 +1,7 @@
1
+ import type { HTMLAttributes } from "react";
2
+ interface AlertProps extends HTMLAttributes<HTMLDivElement> {
3
+ variant?: "success" | "warning" | "error" | "info";
4
+ title?: string;
5
+ }
6
+ export declare const Alert: import("react").ForwardRefExoticComponent<AlertProps & import("react").RefAttributes<HTMLDivElement>>;
7
+ export {};
@@ -0,0 +1,40 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { forwardRef } from "react";
3
+ import { cn } from "../lib/utils";
4
+ import { CheckCircleIcon, InfoIcon, WarningIcon, XCircleIcon } from "@phosphor-icons/react";
5
+ export const Alert = forwardRef(({ className, variant = "info", title, children, ...props }, ref) => {
6
+ const variants = {
7
+ success: {
8
+ container: "bg-success-surface/50 border-success-dim/50",
9
+ icon: "text-success",
10
+ title: "text-success-hover",
11
+ text: "text-success",
12
+ Icon: CheckCircleIcon,
13
+ },
14
+ warning: {
15
+ container: "bg-warning-surface/50 border-warning-dim/50",
16
+ icon: "text-warning",
17
+ title: "text-warning-hover",
18
+ text: "text-warning",
19
+ Icon: WarningIcon,
20
+ },
21
+ error: {
22
+ container: "bg-danger-surface/50 border-danger-dim/50",
23
+ icon: "text-danger",
24
+ title: "text-danger-hover",
25
+ text: "text-danger",
26
+ Icon: XCircleIcon,
27
+ },
28
+ info: {
29
+ container: "bg-info-surface/50 border-info-dim/50",
30
+ icon: "text-info",
31
+ title: "text-info-hover",
32
+ text: "text-info",
33
+ Icon: InfoIcon,
34
+ },
35
+ };
36
+ const { container, icon, title: titleClass, text, Icon, } = variants[variant];
37
+ return (_jsx("div", { ref: ref, className: cn("rounded-alert border p-4", container, className), ...props, children: _jsxs("div", { className: "flex", children: [_jsx("div", { className: "shrink-0", children: _jsx(Icon, { size: 20, className: icon, "aria-hidden": "true" }) }), _jsxs("div", { className: "ml-3", children: [title && (_jsx("h3", { className: cn("text-sm font-medium", titleClass), children: title })), _jsx("div", { className: cn("text-sm", text, title && "mt-2"), children: children })] })] }) }));
38
+ });
39
+ Alert.displayName = "Alert";
40
+ //# sourceMappingURL=Alert.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Alert.js","sourceRoot":"","sources":["../../src/components/Alert.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAEnC,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAClC,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAO5F,MAAM,CAAC,MAAM,KAAK,GAAG,UAAU,CAC7B,CAAC,EAAE,SAAS,EAAE,OAAO,GAAG,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE;IAClE,MAAM,QAAQ,GAAG;QACf,OAAO,EAAE;YACP,SAAS,EAAE,6CAA6C;YACxD,IAAI,EAAE,cAAc;YACpB,KAAK,EAAE,oBAAoB;YAC3B,IAAI,EAAE,cAAc;YACpB,IAAI,EAAE,eAAe;SACtB;QACD,OAAO,EAAE;YACP,SAAS,EAAE,6CAA6C;YACxD,IAAI,EAAE,cAAc;YACpB,KAAK,EAAE,oBAAoB;YAC3B,IAAI,EAAE,cAAc;YACpB,IAAI,EAAE,WAAW;SAClB;QACD,KAAK,EAAE;YACL,SAAS,EAAE,2CAA2C;YACtD,IAAI,EAAE,aAAa;YACnB,KAAK,EAAE,mBAAmB;YAC1B,IAAI,EAAE,aAAa;YACnB,IAAI,EAAE,WAAW;SAClB;QACD,IAAI,EAAE;YACJ,SAAS,EAAE,uCAAuC;YAClD,IAAI,EAAE,WAAW;YACjB,KAAK,EAAE,iBAAiB;YACxB,IAAI,EAAE,WAAW;YACjB,IAAI,EAAE,QAAQ;SACf;KACF,CAAC;IAEF,MAAM,EACJ,SAAS,EACT,IAAI,EACJ,KAAK,EAAE,UAAU,EACjB,IAAI,EACJ,IAAI,GACL,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC;IAEtB,OAAO,CACL,cACE,GAAG,EAAE,GAAG,EACR,SAAS,EAAE,EAAE,CAAC,0BAA0B,EAAE,SAAS,EAAE,SAAS,CAAC,KAC3D,KAAK,YAET,eAAK,SAAS,EAAC,MAAM,aACnB,cAAK,SAAS,EAAC,UAAU,YACvB,KAAC,IAAI,IAAC,IAAI,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,iBAAc,MAAM,GAAG,GAClD,EACN,eAAK,SAAS,EAAC,MAAM,aAClB,KAAK,IAAI,CACR,aAAI,SAAS,EAAE,EAAE,CAAC,qBAAqB,EAAE,UAAU,CAAC,YAAG,KAAK,GAAM,CACnE,EACD,cAAK,SAAS,EAAE,EAAE,CAAC,SAAS,EAAE,IAAI,EAAE,KAAK,IAAI,MAAM,CAAC,YACjD,QAAQ,GACL,IACF,IACF,GACF,CACP,CAAC;AACJ,CAAC,CACF,CAAC;AAEF,KAAK,CAAC,WAAW,GAAG,OAAO,CAAC"}
@@ -0,0 +1,13 @@
1
+ import { type AvatarProps } from "./avatar";
2
+ export { ImageCropModal } from "./ImageCropModal";
3
+ export interface AvatarEditorProps extends AvatarProps {
4
+ /** Receives the cropped image blob when the user finishes cropping. */
5
+ onCropComplete?: (blob: Blob) => void;
6
+ cropTitle?: string;
7
+ }
8
+ /**
9
+ * Editable Avatar — display Avatar + hover-to-upload overlay + crop modal.
10
+ * Lives on the "@astralform/ui/avatar-editor" sub-path so display-only
11
+ * consumers of `Avatar` don't pull react-easy-crop + sonner.
12
+ */
13
+ export declare function AvatarEditor({ onCropComplete, cropTitle, size, rounded, disabled, className, ...avatarProps }: AvatarEditorProps): import("react").JSX.Element;
@@ -0,0 +1,44 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useRef, useState } from "react";
3
+ import { toast } from "sonner";
4
+ import { CameraIcon } from "@phosphor-icons/react";
5
+ import { cn } from "../lib/utils";
6
+ import { ImageCropModal } from "./ImageCropModal";
7
+ import { Avatar, SIZE_CONFIG } from "./avatar";
8
+ // Re-exported so the crop modal is reachable without the core barrel pulling
9
+ // react-easy-crop. Editor-path consumers get both from one import.
10
+ export { ImageCropModal } from "./ImageCropModal";
11
+ const ALLOWED_TYPES = ["image/png", "image/jpeg", "image/webp"];
12
+ const MAX_FILE_SIZE = 256 * 1024; // 256 KB
13
+ /**
14
+ * Editable Avatar — display Avatar + hover-to-upload overlay + crop modal.
15
+ * Lives on the "@astralform/ui/avatar-editor" sub-path so display-only
16
+ * consumers of `Avatar` don't pull react-easy-crop + sonner.
17
+ */
18
+ export function AvatarEditor({ onCropComplete, cropTitle = "Crop Image", size = "md", rounded = "rounded-full", disabled, className, ...avatarProps }) {
19
+ const fileInputRef = useRef(null);
20
+ const [cropFile, setCropFile] = useState(null);
21
+ const editable = !!onCropComplete && !disabled;
22
+ const cropShape = rounded === "rounded-full" ? "round" : "rect";
23
+ const handleFileSelect = (e) => {
24
+ const file = e.target.files?.[0];
25
+ if (!file)
26
+ return;
27
+ e.target.value = "";
28
+ if (!ALLOWED_TYPES.includes(file.type)) {
29
+ toast.error("Please select a PNG, JPEG, or WebP image");
30
+ return;
31
+ }
32
+ if (file.size > MAX_FILE_SIZE) {
33
+ toast.error("Image must be under 256 KB");
34
+ return;
35
+ }
36
+ setCropFile(file);
37
+ };
38
+ const handleCrop = (blob) => {
39
+ setCropFile(null);
40
+ onCropComplete?.(blob);
41
+ };
42
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: cn("relative group shrink-0", SIZE_CONFIG[size].container, disabled && "opacity-50", className), children: [_jsx(Avatar, { size: size, rounded: rounded, disabled: disabled, ...avatarProps }), editable && (_jsx("div", { className: cn("absolute inset-0 flex items-center justify-center gap-2", "bg-black/60 opacity-0 group-hover:opacity-100 transition-opacity", rounded), children: _jsx("button", { type: "button", onClick: () => fileInputRef.current?.click(), className: "p-1.5 rounded-full hover:bg-white/20 transition-colors cursor-pointer", title: "Upload image", children: _jsx(CameraIcon, { size: 16, className: "text-white" }) }) })), editable && (_jsx("input", { ref: fileInputRef, type: "file", accept: "image/png,image/jpeg,image/webp", className: "hidden", onChange: handleFileSelect }))] }), editable && (_jsx(ImageCropModal, { file: cropFile, onCrop: handleCrop, onCancel: () => setCropFile(null), cropShape: cropShape, title: cropTitle }))] }));
43
+ }
44
+ //# sourceMappingURL=AvatarEditor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AvatarEditor.js","sourceRoot":"","sources":["../../src/components/AvatarEditor.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import * as React from "react";
2
+ import { type VariantProps } from "class-variance-authority";
3
+ import type { Icon as PhosphorIcon } from "@phosphor-icons/react";
4
+ import { type BadgeCategoryType } from "./badge-config";
5
+ declare const badgeVariants: (props?: ({
6
+ variant?: "secondary" | "danger" | "accent" | "warning" | "default" | "success" | "info" | "outline" | null | undefined;
7
+ size?: "sm" | "default" | null | undefined;
8
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
9
+ export interface BadgeProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof badgeVariants> {
10
+ /** Semantic category — auto-resolves icon and variant color */
11
+ category?: BadgeCategoryType;
12
+ /** Manual icon — overrides category icon when both are set */
13
+ icon?: PhosphorIcon;
14
+ }
15
+ declare function Badge({ className, variant, size, category, icon: iconProp, children, ...props }: BadgeProps): React.JSX.Element;
16
+ export { Badge };
@@ -0,0 +1,38 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import * as React from "react";
3
+ import { cva } from "class-variance-authority";
4
+ import { cn } from "../lib/utils";
5
+ import { BADGE_CATEGORY_CONFIG } from "./badge-config";
6
+ /* ------------------------------------------------------------------ */
7
+ /* Variant styles */
8
+ /* ------------------------------------------------------------------ */
9
+ const badgeVariants = cva("inline-flex items-center rounded-badge border font-semibold transition-colors", {
10
+ variants: {
11
+ variant: {
12
+ default: "border-transparent bg-btn-primary-bg text-text-inverse",
13
+ secondary: "border-transparent bg-surface-tertiary text-text-secondary",
14
+ success: "bg-success-subtle/50 text-success border-success-dim",
15
+ warning: "bg-warning-subtle/50 text-warning border-warning-dim",
16
+ danger: "bg-danger-subtle/50 text-danger border-danger-dim",
17
+ info: "bg-info-subtle/50 text-info border-info-dim",
18
+ outline: "border-border-secondary text-text-secondary",
19
+ accent: "bg-accent-subtle/50 text-accent-hover border-accent-dim",
20
+ },
21
+ size: {
22
+ default: "px-2.5 py-0.5 text-xs",
23
+ sm: "px-1.5 py-0.5 text-[11px] gap-1",
24
+ },
25
+ },
26
+ defaultVariants: {
27
+ variant: "default",
28
+ size: "default",
29
+ },
30
+ });
31
+ function Badge({ className, variant, size, category, icon: iconProp, children, ...props }) {
32
+ const cat = category ? BADGE_CATEGORY_CONFIG[category] : null;
33
+ const resolvedVariant = variant ?? cat?.variant ?? undefined;
34
+ const IconComp = iconProp ?? cat?.icon ?? null;
35
+ return (_jsxs("div", { className: cn(badgeVariants({ variant: resolvedVariant, size }), className), ...props, children: [IconComp && _jsx(IconComp, { size: size === "sm" ? 12 : 14 }), children] }));
36
+ }
37
+ export { Badge };
38
+ //# sourceMappingURL=Badge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Badge.js","sourceRoot":"","sources":["../../src/components/Badge.tsx"],"names":[],"mappings":";AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,GAAG,EAAqB,MAAM,0BAA0B,CAAC;AAElE,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAClC,OAAO,EAAE,qBAAqB,EAA0B,MAAM,gBAAgB,CAAC;AAE/E,wEAAwE;AACxE,yEAAyE;AACzE,wEAAwE;AAExE,MAAM,aAAa,GAAG,GAAG,CACvB,+EAA+E,EAC/E;IACE,QAAQ,EAAE;QACR,OAAO,EAAE;YACP,OAAO,EAAE,wDAAwD;YACjE,SAAS,EAAE,4DAA4D;YACvE,OAAO,EAAE,sDAAsD;YAC/D,OAAO,EAAE,sDAAsD;YAC/D,MAAM,EAAE,mDAAmD;YAC3D,IAAI,EAAE,6CAA6C;YACnD,OAAO,EAAE,6CAA6C;YACtD,MAAM,EAAE,yDAAyD;SAClE;QACD,IAAI,EAAE;YACJ,OAAO,EAAE,uBAAuB;YAChC,EAAE,EAAE,iCAAiC;SACtC;KACF;IACD,eAAe,EAAE;QACf,OAAO,EAAE,SAAS;QAClB,IAAI,EAAE,SAAS;KAChB;CACF,CACF,CAAC;AAgBF,SAAS,KAAK,CAAC,EACb,SAAS,EACT,OAAO,EACP,IAAI,EACJ,QAAQ,EACR,IAAI,EAAE,QAAQ,EACd,QAAQ,EACR,GAAG,KAAK,EACG;IACX,MAAM,GAAG,GAAG,QAAQ,CAAC,CAAC,CAAC,qBAAqB,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC9D,MAAM,eAAe,GAAG,OAAO,IAAI,GAAG,EAAE,OAAO,IAAI,SAAS,CAAC;IAC7D,MAAM,QAAQ,GAAG,QAAQ,IAAI,GAAG,EAAE,IAAI,IAAI,IAAI,CAAC;IAE/C,OAAO,CACL,eACE,SAAS,EAAE,EAAE,CACX,aAAa,CAAC,EAAE,OAAO,EAAE,eAAe,EAAE,IAAI,EAAE,CAAC,EACjD,SAAS,CACV,KACG,KAAK,aAER,QAAQ,IAAI,KAAC,QAAQ,IAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,GAAI,EACvD,QAAQ,IACL,CACP,CAAC;AACJ,CAAC;AAED,OAAO,EAAE,KAAK,EAAE,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { ButtonHTMLAttributes } from "react";
2
+ interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
3
+ variant?: "primary" | "secondary" | "danger" | "ghost" | "gradient";
4
+ size?: "sm" | "md" | "lg";
5
+ isLoading?: boolean;
6
+ }
7
+ export declare const Button: import("react").ForwardRefExoticComponent<ButtonProps & import("react").RefAttributes<HTMLButtonElement>>;
8
+ export {};
@@ -0,0 +1,21 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { forwardRef } from "react";
3
+ import { cn } from "../lib/utils";
4
+ export const Button = forwardRef(({ className, variant = "primary", size = "md", isLoading, disabled, children, ...props }, ref) => {
5
+ const baseStyles = "btn-press inline-flex items-center justify-center font-medium rounded-button transition-all duration-[var(--duration-hover)] focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-surface-base disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none";
6
+ const variants = {
7
+ primary: "bg-btn-primary-bg text-text-inverse hover:bg-btn-primary-hover hover:shadow-lg hover:shadow-white/10 focus:ring-text-primary",
8
+ secondary: "bg-surface-secondary text-text-primary border border-border-secondary hover:bg-surface-tertiary hover:border-border-focus hover:shadow-md focus:ring-text-muted",
9
+ danger: "bg-btn-danger-bg text-text-primary hover:bg-btn-danger-hover hover:shadow-lg hover:shadow-danger-muted/25 focus:ring-danger-muted",
10
+ ghost: "text-text-secondary hover:bg-surface-tertiary hover:text-text-primary focus:ring-text-muted",
11
+ gradient: "bg-btn-primary-bg text-text-inverse shadow-lg shadow-white/10 hover:bg-btn-primary-hover focus:ring-text-primary",
12
+ };
13
+ const sizes = {
14
+ sm: "px-3 py-1.5 text-sm",
15
+ md: "px-4 py-2 text-sm",
16
+ lg: "px-6 py-3 text-base",
17
+ };
18
+ return (_jsxs("button", { ref: ref, className: cn(baseStyles, variants[variant], sizes[size], className), disabled: disabled || isLoading, ...props, children: [isLoading && (_jsxs("svg", { className: "animate-spin -ml-1 mr-2 h-4 w-4", xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", children: [_jsx("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }), _jsx("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" })] })), children] }));
19
+ });
20
+ Button.displayName = "Button";
21
+ //# sourceMappingURL=Button.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Button.js","sourceRoot":"","sources":["../../src/components/Button.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAEnC,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAQlC,MAAM,CAAC,MAAM,MAAM,GAAG,UAAU,CAC9B,CACE,EACE,SAAS,EACT,OAAO,GAAG,SAAS,EACnB,IAAI,GAAG,IAAI,EACX,SAAS,EACT,QAAQ,EACR,QAAQ,EACR,GAAG,KAAK,EACT,EACD,GAAG,EACH,EAAE;IACF,MAAM,UAAU,GACd,yRAAyR,CAAC;IAE5R,MAAM,QAAQ,GAAG;QACf,OAAO,EACL,8HAA8H;QAChI,SAAS,EACP,iKAAiK;QACnK,MAAM,EACJ,mIAAmI;QACrI,KAAK,EACH,6FAA6F;QAC/F,QAAQ,EACN,kHAAkH;KACrH,CAAC;IAEF,MAAM,KAAK,GAAG;QACZ,EAAE,EAAE,qBAAqB;QACzB,EAAE,EAAE,mBAAmB;QACvB,EAAE,EAAE,qBAAqB;KAC1B,CAAC;IAEF,OAAO,CACL,kBACE,GAAG,EAAE,GAAG,EACR,SAAS,EAAE,EAAE,CAAC,UAAU,EAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC,EACpE,QAAQ,EAAE,QAAQ,IAAI,SAAS,KAC3B,KAAK,aAER,SAAS,IAAI,CACZ,eACE,SAAS,EAAC,iCAAiC,EAC3C,KAAK,EAAC,4BAA4B,EAClC,IAAI,EAAC,MAAM,EACX,OAAO,EAAC,WAAW,aAEnB,iBACE,SAAS,EAAC,YAAY,EACtB,EAAE,EAAC,IAAI,EACP,EAAE,EAAC,IAAI,EACP,CAAC,EAAC,IAAI,EACN,MAAM,EAAC,cAAc,EACrB,WAAW,EAAC,GAAG,GACf,EACF,eACE,SAAS,EAAC,YAAY,EACtB,IAAI,EAAC,cAAc,EACnB,CAAC,EAAC,iHAAiH,GACnH,IACE,CACP,EACA,QAAQ,IACF,CACV,CAAC;AACJ,CAAC,CACF,CAAC;AAEF,MAAM,CAAC,WAAW,GAAG,QAAQ,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { HTMLAttributes } from "react";
2
+ interface CardProps extends HTMLAttributes<HTMLDivElement> {
3
+ padding?: "none" | "sm" | "md" | "lg";
4
+ }
5
+ export declare const Card: import("react").ForwardRefExoticComponent<CardProps & import("react").RefAttributes<HTMLDivElement>>;
6
+ export declare const CardHeader: import("react").ForwardRefExoticComponent<HTMLAttributes<HTMLDivElement> & import("react").RefAttributes<HTMLDivElement>>;
7
+ export declare const CardTitle: import("react").ForwardRefExoticComponent<HTMLAttributes<HTMLHeadingElement> & import("react").RefAttributes<HTMLHeadingElement>>;
8
+ export declare const CardDescription: import("react").ForwardRefExoticComponent<HTMLAttributes<HTMLParagraphElement> & import("react").RefAttributes<HTMLParagraphElement>>;
9
+ export declare const CardContent: import("react").ForwardRefExoticComponent<HTMLAttributes<HTMLDivElement> & import("react").RefAttributes<HTMLDivElement>>;
10
+ export {};
@@ -0,0 +1,22 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { forwardRef } from "react";
3
+ import { cn } from "../lib/utils";
4
+ export const Card = forwardRef(({ className, padding = "md", children, ...props }, ref) => {
5
+ const paddings = {
6
+ none: "",
7
+ sm: "p-4",
8
+ md: "p-6",
9
+ lg: "p-8",
10
+ };
11
+ return (_jsx("div", { ref: ref, className: cn("bg-surface-primary rounded-card shadow-[var(--shadow-card)] border-[0.75px] border-border-primary", paddings[padding], className), ...props, children: children }));
12
+ });
13
+ Card.displayName = "Card";
14
+ export const CardHeader = forwardRef(({ className, ...props }, ref) => (_jsx("div", { ref: ref, className: cn("flex flex-col space-y-1.5", className), ...props })));
15
+ CardHeader.displayName = "CardHeader";
16
+ export const CardTitle = forwardRef(({ className, children, ...props }, ref) => (_jsx("h3", { ref: ref, className: cn("type-card-title", className), ...props, children: children })));
17
+ CardTitle.displayName = "CardTitle";
18
+ export const CardDescription = forwardRef(({ className, ...props }, ref) => (_jsx("p", { ref: ref, className: cn("text-sm text-text-tertiary", className), ...props })));
19
+ CardDescription.displayName = "CardDescription";
20
+ export const CardContent = forwardRef(({ className, ...props }, ref) => (_jsx("div", { ref: ref, className: cn("pt-0", className), ...props })));
21
+ CardContent.displayName = "CardContent";
22
+ //# sourceMappingURL=Card.js.map