@astralform/ui 0.0.0-stage → 0.3.7-next.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/DESIGN.md +243 -0
- package/LICENSE +21 -0
- package/README.md +128 -2
- package/dist/components/Alert.d.ts +7 -0
- package/dist/components/Alert.js +40 -0
- package/dist/components/Alert.js.map +1 -0
- package/dist/components/AvatarEditor.d.ts +13 -0
- package/dist/components/AvatarEditor.js +44 -0
- package/dist/components/AvatarEditor.js.map +1 -0
- package/dist/components/Badge.d.ts +16 -0
- package/dist/components/Badge.js +38 -0
- package/dist/components/Badge.js.map +1 -0
- package/dist/components/Button.d.ts +8 -0
- package/dist/components/Button.js +21 -0
- package/dist/components/Button.js.map +1 -0
- package/dist/components/Card.d.ts +10 -0
- package/dist/components/Card.js +22 -0
- package/dist/components/Card.js.map +1 -0
- package/dist/components/CompactResourceRow.d.ts +34 -0
- package/dist/components/CompactResourceRow.js +11 -0
- package/dist/components/CompactResourceRow.js.map +1 -0
- package/dist/components/ConfirmDialog.d.ts +12 -0
- package/dist/components/ConfirmDialog.js +7 -0
- package/dist/components/ConfirmDialog.js.map +1 -0
- package/dist/components/CookieConsent.d.ts +1 -0
- package/dist/components/CookieConsent.js +42 -0
- package/dist/components/CookieConsent.js.map +1 -0
- package/dist/components/DataTable.d.ts +53 -0
- package/dist/components/DataTable.js +79 -0
- package/dist/components/DataTable.js.map +1 -0
- package/dist/components/DevTools.d.ts +6 -0
- package/dist/components/DevTools.js +59 -0
- package/dist/components/DevTools.js.map +1 -0
- package/dist/components/EmptyState.d.ts +11 -0
- package/dist/components/EmptyState.js +6 -0
- package/dist/components/EmptyState.js.map +1 -0
- package/dist/components/ImageCropModal.d.ts +18 -0
- package/dist/components/ImageCropModal.js +67 -0
- package/dist/components/ImageCropModal.js.map +1 -0
- package/dist/components/InlineBanner.d.ts +20 -0
- package/dist/components/InlineBanner.js +53 -0
- package/dist/components/InlineBanner.js.map +1 -0
- package/dist/components/Input.d.ts +8 -0
- package/dist/components/Input.js +10 -0
- package/dist/components/Input.js.map +1 -0
- package/dist/components/InventoryCard.d.ts +20 -0
- package/dist/components/InventoryCard.js +22 -0
- package/dist/components/InventoryCard.js.map +1 -0
- package/dist/components/LoadingSpinner.d.ts +26 -0
- package/dist/components/LoadingSpinner.js +32 -0
- package/dist/components/LoadingSpinner.js.map +1 -0
- package/dist/components/Modal.d.ts +10 -0
- package/dist/components/Modal.js +38 -0
- package/dist/components/Modal.js.map +1 -0
- package/dist/components/PageHeader.d.ts +11 -0
- package/dist/components/PageHeader.js +5 -0
- package/dist/components/PageHeader.js.map +1 -0
- package/dist/components/PreviewModal.d.ts +24 -0
- package/dist/components/PreviewModal.js +69 -0
- package/dist/components/PreviewModal.js.map +1 -0
- package/dist/components/ProgressBar.d.ts +8 -0
- package/dist/components/ProgressBar.js +12 -0
- package/dist/components/ProgressBar.js.map +1 -0
- package/dist/components/ProviderIcon.d.ts +7 -0
- package/dist/components/ProviderIcon.js +34 -0
- package/dist/components/ProviderIcon.js.map +1 -0
- package/dist/components/ResizeHandle.d.ts +56 -0
- package/dist/components/ResizeHandle.js +166 -0
- package/dist/components/ResizeHandle.js.map +1 -0
- package/dist/components/SegmentedProgressBar.d.ts +9 -0
- package/dist/components/SegmentedProgressBar.js +23 -0
- package/dist/components/SegmentedProgressBar.js.map +1 -0
- package/dist/components/Select.d.ts +13 -0
- package/dist/components/Select.js +14 -0
- package/dist/components/Select.js.map +1 -0
- package/dist/components/SpotlightButton.d.ts +10 -0
- package/dist/components/SpotlightButton.js +51 -0
- package/dist/components/SpotlightButton.js.map +1 -0
- package/dist/components/StatCard.d.ts +9 -0
- package/dist/components/StatCard.js +8 -0
- package/dist/components/StatCard.js.map +1 -0
- package/dist/components/StatusBadge.d.ts +4 -0
- package/dist/components/StatusBadge.js +10 -0
- package/dist/components/StatusBadge.js.map +1 -0
- package/dist/components/TabNav.d.ts +39 -0
- package/dist/components/TabNav.js +25 -0
- package/dist/components/TabNav.js.map +1 -0
- package/dist/components/TableShell.d.ts +21 -0
- package/dist/components/TableShell.js +19 -0
- package/dist/components/TableShell.js.map +1 -0
- package/dist/components/Textarea.d.ts +7 -0
- package/dist/components/Textarea.js +10 -0
- package/dist/components/Textarea.js.map +1 -0
- package/dist/components/Toggle.d.ts +16 -0
- package/dist/components/Toggle.js +25 -0
- package/dist/components/Toggle.js.map +1 -0
- package/dist/components/Toolbar.d.ts +34 -0
- package/dist/components/Toolbar.js +30 -0
- package/dist/components/Toolbar.js.map +1 -0
- package/dist/components/TypeToConfirmModal.d.ts +15 -0
- package/dist/components/TypeToConfirmModal.js +14 -0
- package/dist/components/TypeToConfirmModal.js.map +1 -0
- package/dist/components/UnsavedChangesModal.d.ts +7 -0
- package/dist/components/UnsavedChangesModal.js +7 -0
- package/dist/components/UnsavedChangesModal.js.map +1 -0
- package/dist/components/UsageCard.d.ts +11 -0
- package/dist/components/UsageCard.js +9 -0
- package/dist/components/UsageCard.js.map +1 -0
- package/dist/components/avatar.d.ts +79 -0
- package/dist/components/avatar.js +88 -0
- package/dist/components/avatar.js.map +1 -0
- package/dist/components/badge-config.d.ts +16 -0
- package/dist/components/badge-config.js +48 -0
- package/dist/components/badge-config.js.map +1 -0
- package/dist/components/glowing-effect.d.ts +14 -0
- package/dist/components/glowing-effect.js +108 -0
- package/dist/components/glowing-effect.js.map +1 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.js +42 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/cookies.d.ts +36 -0
- package/dist/lib/cookies.js +78 -0
- package/dist/lib/cookies.js.map +1 -0
- package/dist/lib/environment.d.ts +24 -0
- package/dist/lib/environment.js +52 -0
- package/dist/lib/environment.js.map +1 -0
- package/dist/lib/utils.d.ts +4 -0
- package/dist/lib/utils.js +8 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/types.d.ts +12 -0
- package/dist/types.js +12 -0
- package/dist/types.js.map +1 -0
- package/package.json +89 -4
- package/src/styles/base.css +779 -0
- package/src/styles/fonts.css +25 -0
- package/src/styles/theme-default.css +164 -0
- package/src/styles/theme-default.generated.css +79 -0
- package/src/styles/tokens/_foundations.css +28 -0
- package/src/styles/tokens/_semantics.css +72 -0
- 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
|
-
#
|
|
1
|
+
# @astralform/ui
|
|
2
2
|
|
|
3
|
-
|
|
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
|