@phcdevworks/spectre-ui 0.0.2 → 0.0.4

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/README.md CHANGED
@@ -1,10 +1,12 @@
1
1
  # @phcdevworks/spectre-ui
2
2
 
3
- Core styling layer for the Spectre design system. `@phcdevworks/spectre-ui` ships the precompiled CSS, tailwind preset, and recipe helpers that power every Spectre integration (WordPress blocks, Astro, 11ty, and more).
3
+ Framework-agnostic styling layer that powers Spectre Blocks, Spectre Astro, Spectre 11ty, and every Spectre integration.
4
+
5
+ > 📋 **[View Roadmap](https://github.com/phcdevworks/spectre-ui/blob/main/ROADMAP.md)** | 🤝 **[Contributing Guide](CONTRIBUTING.md)** | 📝 **[Changelog](CHANGELOG.md)**
4
6
 
5
7
  ## Overview
6
8
 
7
- This package is the single source of truth for Spectre's design language. It exposes CSS entry points, typed recipes, and token-driven utilities that downstream frameworks can consume without duplicating logic.
9
+ `@phcdevworks/spectre-ui` is the core styling layer of the Spectre design system. It consumes `@phcdevworks/spectre-tokens` and ships precompiled CSS, type-safe recipe helpers, and a Tailwind preset so downstream frameworks can stay in sync without duplicating logic. One design system runs the entire Spectre Suite; this package handles the implementation.
8
10
 
9
11
  - ✅ Token-powered styles built on `@phcdevworks/spectre-tokens`
10
12
  - ✅ Precompiled `base`, `components`, and `utilities` CSS bundles
@@ -22,21 +24,20 @@ npm install @phcdevworks/spectre-ui
22
24
 
23
25
  ### 1. Import Spectre CSS
24
26
 
25
- You can import the full bundle or use the namespaced entry points anywhere in your app, layout, or build pipeline.
27
+ Import the CSS bundles anywhere in your app, layout, or build pipeline.
26
28
 
27
29
  ```css
28
- /* Full bundle */
29
- @import "@phcdevworks/spectre-ui/dist/base.css";
30
- @import "@phcdevworks/spectre-ui/dist/components.css";
31
- @import "@phcdevworks/spectre-ui/dist/utilities.css";
30
+ @import "@phcdevworks/spectre-ui/base.css";
31
+ @import "@phcdevworks/spectre-ui/components.css";
32
+ @import "@phcdevworks/spectre-ui/utilities.css";
32
33
  ```
33
34
 
34
- ### 2. Configure Tailwind
35
+ ### 2. Tailwind integration
35
36
 
36
- Spectre ships an opinionated Tailwind preset that mirrors the tokens exactly.
37
+ Spectre ships an opinionated Tailwind preset that mirrors the design tokens exactly.
37
38
 
38
39
  ```ts
39
- // tailwind.config.mjs
40
+ // tailwind.config.ts
40
41
  import { spectrePreset } from "@phcdevworks/spectre-ui";
41
42
 
42
43
  export default {
@@ -45,23 +46,7 @@ export default {
45
46
  };
46
47
  ```
47
48
 
48
- Need custom tokens? Generate a tailored theme:
49
-
50
- ```ts
51
- import {
52
- spectreTokens,
53
- createSpectreTailwindTheme,
54
- } from "@phcdevworks/spectre-ui";
55
-
56
- const theme = createSpectreTailwindTheme({
57
- tokens: spectreTokens,
58
- overrides: {
59
- colors: {
60
- brand: "#7928CA",
61
- },
62
- },
63
- });
64
- ```
49
+ Works with Tailwind 3.x and 4.x through the classic config API.
65
50
 
66
51
  ### 3. Use Spectre recipes
67
52
 
@@ -92,6 +77,118 @@ const inputClasses = getInputClasses({
92
77
  // "sp-input sp-input--error sp-input--sm sp-input--full"
93
78
  ```
94
79
 
80
+ ## Component Surfaces
81
+
82
+ ### Button variants
83
+
84
+ ```ts
85
+ getButtonClasses({ variant: "primary" }); // CTA baseline
86
+ getButtonClasses({ variant: "secondary" }); // Outlined
87
+ getButtonClasses({ variant: "ghost" }); // Low-emphasis
88
+ getButtonClasses({ variant: "danger" }); // Destructive
89
+ ```
90
+
91
+ Each variant ships with full state coverage: `default`, `hover`, `active`, `disabled`, and tone modifiers (`success`, `warning`, `danger`).
92
+
93
+ ```css
94
+ .cta-button {
95
+ background: var(--sp-component-button-primary-bg);
96
+ color: var(--sp-component-button-primary-text);
97
+ }
98
+ .cta-button:hover {
99
+ background: var(--sp-component-button-primary-bg-hover);
100
+ }
101
+ ```
102
+
103
+ ### Input states
104
+
105
+ ```ts
106
+ getInputClasses({ state: "default" });
107
+ getInputClasses({ state: "error" });
108
+ getInputClasses({ state: "success" });
109
+ ```
110
+
111
+ ```css
112
+ .input:focus {
113
+ border-color: var(--sp-component-input-border-focus);
114
+ outline: var(--sp-focus-ring-width) var(--sp-focus-ring-style)
115
+ var(--sp-component-input-ring-focus);
116
+ }
117
+ .input.error {
118
+ border-color: var(--sp-component-input-border-error);
119
+ background: var(--sp-component-input-bg-error);
120
+ }
121
+ ```
122
+
123
+ ### Card variants
124
+
125
+ ```ts
126
+ getCardClasses({ variant: "elevated" }); // Default shadow
127
+ getCardClasses({ variant: "outline" }); // Bordered
128
+ getCardClasses({ variant: "ghost" }); // Transparent
129
+ ```
130
+
131
+ ## Surface & Typography Roles
132
+
133
+ Spectre exposes semantic layers that decouple structural styles from raw palette values. Override these roles at any scope (root, layout, or component wrapper) to restyle whole experiences without editing CSS.
134
+
135
+ - `surface.page`, `surface.card`, `surface.input`, `surface.overlay`: semantic backgrounds for the app canvas, containers/tiles, form fields, and modal/dropdown layers.
136
+ - `text.onPage.*` vs `text.onSurface.*`: use `onPage` for copy sitting directly on the page canvas; use `onSurface` for text inside cards, tiles, inputs, overlays, and other elevated surfaces.
137
+ - `component.card.text`/`textMuted`, `component.input.text`/`placeholder`, and `component.button.textDefault`/`textOnPrimary` alias the underlying `text.onSurface` roles to keep component defaults aligned.
138
+
139
+ ### Tailwind utilities
140
+
141
+ The Tailwind preset exposes semantic helpers:
142
+
143
+ - `bg-surface-page`, `bg-surface-card`, `bg-surface-input`
144
+ - `text-on-page`, `text-on-surface`
145
+
146
+ Use them to mix utility-first UIs with Spectre's semantic palette.
147
+
148
+ ### Mode infrastructure
149
+
150
+ Spectre reserves the `data-sp-mode` attribute (or any wrapper selector) for future automatic light/dark modes. Override the semantic variables inside those selectors today and you're ready for upcoming multi-mode token drops.
151
+
152
+ ## Usage Examples
153
+
154
+ ### Surface-aware components
155
+
156
+ ```css
157
+ .hero-panel {
158
+ --sp-surface-card: var(--sp-color-neutral-900);
159
+ --sp-text-on-surface-default: var(--sp-color-neutral-50);
160
+ --sp-text-on-surface-muted: var(--sp-color-neutral-200);
161
+ --sp-component-button-primary-bg: var(--sp-color-accent-400);
162
+ --sp-component-button-primary-text: var(--sp-color-neutral-900);
163
+ --sp-component-card-border: transparent;
164
+ --sp-component-input-border: var(--sp-color-accent-400);
165
+ }
166
+ ```
167
+
168
+ ```html
169
+ <section class="hero-panel">
170
+ <button class="sp-btn sp-btn--primary">Surface Button</button>
171
+ <div class="sp-card sp-card--elevated">
172
+ <p>Cards inherit the contextual surface + text roles automatically.</p>
173
+ </div>
174
+ <input class="sp-input" placeholder="Email address" />
175
+ </section>
176
+ ```
177
+
178
+ ### Tailwind semantic utilities
179
+
180
+ ```jsx
181
+ export function SignupCard() {
182
+ return (
183
+ <div className="bg-surface-card text-on-surface sp-card sp-card--padded">
184
+ <h2 className="text-on-page font-semibold">Join the beta</h2>
185
+ <input className="sp-input mt-4" placeholder="you@spectre.dev" />
186
+ <button className="sp-btn sp-btn--primary mt-4">Request access</button>
187
+ </div>
188
+ );
189
+ }
190
+ ```
191
+
95
192
  ## CSS Path Constants
96
193
 
97
194
  Utilities for referencing the published CSS files programmatically:
@@ -104,14 +201,42 @@ import {
104
201
  spectreStyles,
105
202
  } from "@phcdevworks/spectre-ui";
106
203
 
107
- // spectreStyles.base → "@phcdevworks/spectre-ui/dist/base.css"
108
- // spectreStyles.components → "@phcdevworks/spectre-ui/dist/components.css"
109
- // spectreStyles.utilities → "@phcdevworks/spectre-ui/dist/utilities.css"
204
+ // spectreStyles.base → "@phcdevworks/spectre-ui/base.css"
205
+ // spectreStyles.components → "@phcdevworks/spectre-ui/components.css"
206
+ // spectreStyles.utilities → "@phcdevworks/spectre-ui/utilities.css"
110
207
  ```
111
208
 
112
- ## Tokens & TypeScript Support
209
+ ## Repository Layout
113
210
 
114
- All exports ship full TypeScript definitions, including:
211
+ | Folder | Responsibility |
212
+ | ------------- | ------------------------------------------------------------------------------------------------------------- |
213
+ | `src/` | TypeScript source: recipes, Tailwind preset, token re-exports, CSS constants. |
214
+ | `src/styles/` | Raw CSS files (`base.css`, `components.css`, `utilities.css`) copied to `dist/` during build. |
215
+ | `dist/` | Generated artifacts: `index.js`, `index.cjs`, `index.d.ts`, and CSS files. Regenerated via `npm run build`. |
216
+
217
+ Designers update tokens in `@phcdevworks/spectre-tokens`. Engineering evolves recipes, presets, and CSS in this package.
218
+
219
+ ## Build & Release
220
+
221
+ ```bash
222
+ npm run build
223
+ ```
224
+
225
+ `tsup` compiles the TypeScript library (ESM, CJS, `.d.ts`) and copies CSS files to `dist/`. Because `dist/` is generated, releases are reproducible from `src/`.
226
+
227
+ For release history and version notes, see the **[Changelog](CHANGELOG.md)**.
228
+
229
+ ## Design Principles
230
+
231
+ 1. **Single source of truth** – All Spectre products consume these styles and recipes.
232
+ 2. **No style duplication** – Downstream frameworks never re-encode Spectre logic.
233
+ 3. **Token-first** – The Tailwind preset, CSS, and recipes are generated from tokens.
234
+ 4. **Framework agnostic** – Works with any bundler, CMS, or runtime.
235
+ 5. **Type-safe ergonomics** – Every helper exports strict types for confident usage.
236
+
237
+ ## TypeScript Support
238
+
239
+ Type definitions are bundled automatically:
115
240
 
116
241
  ```ts
117
242
  import type {
@@ -120,28 +245,37 @@ import type {
120
245
  ButtonVariant,
121
246
  InputState,
122
247
  CardVariant,
248
+ ButtonRecipeOptions,
249
+ CardRecipeOptions,
250
+ InputRecipeOptions,
123
251
  } from "@phcdevworks/spectre-ui";
124
252
  ```
125
253
 
126
- Use helpers such as `generateSpectreCssVariables`, `createSpectreCssVariableMap`, or `getInputClasses` to keep your implementation type-safe and in sync with the design system.
254
+ ## Part of the Spectre Suite
127
255
 
128
- ## Design Principles
129
-
130
- 1. **Single source of truth** – all Spectre products consume these tokens and CSS files.
131
- 2. **No style duplication** – downstream frameworks never re-encode Spectre logic.
132
- 3. **Token-first** – the Tailwind preset, CSS, and recipes are generated from tokens.
133
- 4. **Framework agnostic** – works with any bundler, CMS, or runtime.
134
- 5. **Type-safe ergonomics** – every helper exports strict types for confident usage.
256
+ - **Spectre Tokens** – Design-token foundation
257
+ - **Spectre UI** – Core styling layer (this package)
258
+ - **Spectre Blocks** – WordPress block library
259
+ - **Spectre Astro** – Astro integration
260
+ - **Spectre 11ty** – Eleventy integration
135
261
 
136
- ## Requirements
137
-
138
- - **Tailwind CSS**: ^3.4.0 or ^4.0.0 (if you consume the preset)
139
- - **Build tooling**: ESM-compatible bundler capable of importing CSS from npm
262
+ For the project's future direction, see the **[Roadmap](https://github.com/phcdevworks/spectre-ui/blob/main/ROADMAP.md)**.
140
263
 
141
264
  ## Contributing
142
265
 
143
- Contributions are welcome—open an issue or submit a pull request on GitHub with context about the change you’re proposing.
266
+ Issues and pull requests are welcome. If you are proposing style or recipe changes, update `src/` and include regenerated builds.
267
+
268
+ For detailed contribution guidelines, see **[CONTRIBUTING.md](CONTRIBUTING.md)**.
144
269
 
145
270
  ## License
146
271
 
147
- MIT © PHCDevworks
272
+ MIT © PHCDevworks — See **[LICENSE](LICENSE)** for details.
273
+
274
+ ---
275
+
276
+ ## ❤️ Support Spectre
277
+
278
+ If Spectre UI helps your workflow, consider sponsoring:
279
+
280
+ - [GitHub Sponsors](https://github.com/sponsors/phcdevworks)
281
+ - [Buy Me a Coffee](https://buymeacoffee.com/phcdevworks)
package/dist/base.css CHANGED
@@ -1,5 +1,12 @@
1
1
  @layer base {
2
2
 
3
+ :root {
4
+ --sp-surface-page: var(--sp-color-neutral-50, #f8fafc);
5
+ --sp-surface-card: #ffffff;
6
+ --sp-surface-input: var(--sp-form-default-bg, #ffffff);
7
+ --sp-surface-overlay: rgba(15, 23, 42, var(--sp-opacity-overlay, 0.5));
8
+ }
9
+
3
10
  *,
4
11
  *::before,
5
12
  *::after {
@@ -17,8 +24,8 @@
17
24
  font-family: var(--sp-font-family-sans, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif);
18
25
  font-size: var(--sp-font-md-size, 1rem);
19
26
  line-height: var(--sp-font-md-line-height, 1.5);
20
- color: var(--sp-color-neutral-900, #0f172a);
21
- background-color: var(--sp-color-neutral-50, #f8fafc);
27
+ color: var(--sp-text-on-page-default);
28
+ background-color: var(--sp-surface-page);
22
29
  }
23
30
 
24
31
  img,
@@ -45,7 +52,7 @@
45
52
  }
46
53
 
47
54
  a {
48
- color: var(--sp-link-color, var(--sp-color-primary-600, #4f46e5));
55
+ color: var(--sp-color-primary, inherit);
49
56
  text-decoration: none;
50
57
  }
51
58
 
@@ -54,12 +61,12 @@
54
61
  }
55
62
 
56
63
  :focus-visible {
57
- outline: 2px solid var(--sp-focus-ring-color, #4f46e5);
64
+ outline: 2px solid var(--sp-color-primary, currentColor);
58
65
  outline-offset: 2px;
59
66
  }
60
67
 
61
68
  ::selection {
62
- background-color: var(--sp-selection-bg, rgba(79, 70, 229, 0.15));
69
+ background-color: var(--sp-color-primary-soft, rgba(79, 70, 229, 0.15));
63
70
  color: inherit;
64
71
  }
65
72
  }
@@ -1,5 +1,76 @@
1
1
  @layer components {
2
2
 
3
+ :root {
4
+ /* button roles */
5
+ --sp-component-button-border-base: var(--sp-component-button-ghost-bg, var(--sp-button-ghost-bg));
6
+ --sp-component-button-shadow: var(--sp-shadow-sm);
7
+ --sp-component-button-primary-bg: var(--sp-button-primary-bg);
8
+ --sp-component-button-primary-bg-hover: var(--sp-button-primary-bghover);
9
+ --sp-component-button-primary-bg-active: var(--sp-button-primary-bgactive);
10
+ --sp-component-button-primary-bg-disabled: var(--sp-button-primary-bgdisabled);
11
+ --sp-component-button-primary-text: var(--sp-button-primary-text);
12
+ --sp-component-button-primary-text-disabled: var(--sp-button-primary-textdisabled);
13
+ --sp-component-button-secondary-bg: var(--sp-button-secondary-bg);
14
+ --sp-component-button-secondary-bg-hover: var(--sp-button-secondary-bghover);
15
+ --sp-component-button-secondary-bg-active: var(--sp-button-secondary-bgactive);
16
+ --sp-component-button-secondary-bg-disabled: var(--sp-button-secondary-bgdisabled);
17
+ --sp-component-button-secondary-text: var(--sp-button-secondary-text);
18
+ --sp-component-button-secondary-text-disabled: var(--sp-button-secondary-textdisabled);
19
+ --sp-component-button-secondary-border: var(--sp-button-secondary-border);
20
+ --sp-component-button-secondary-border-disabled: var(--sp-button-secondary-borderdisabled);
21
+ --sp-component-button-ghost-bg: var(--sp-button-ghost-bg);
22
+ --sp-component-button-ghost-bg-hover: var(--sp-button-ghost-bghover);
23
+ --sp-component-button-ghost-bg-active: var(--sp-button-ghost-bgactive);
24
+ --sp-component-button-ghost-bg-disabled: var(--sp-button-ghost-bgdisabled);
25
+ --sp-component-button-ghost-text: var(--sp-button-ghost-text);
26
+ --sp-component-button-ghost-text-disabled: var(--sp-button-ghost-textdisabled);
27
+ --sp-component-button-danger-bg: var(--sp-button-danger-bg);
28
+ --sp-component-button-danger-bg-hover: var(--sp-button-danger-bghover);
29
+ --sp-component-button-danger-bg-active: var(--sp-button-danger-bgactive);
30
+ --sp-component-button-danger-bg-disabled: var(--sp-button-danger-bgdisabled);
31
+ --sp-component-button-danger-text: var(--sp-button-danger-text);
32
+ --sp-component-button-danger-text-disabled: var(--sp-button-danger-textdisabled);
33
+ --sp-component-button-success-bg: var(--sp-button-success-bg);
34
+ --sp-component-button-success-bg-hover: var(--sp-button-success-bghover);
35
+ --sp-component-button-success-bg-active: var(--sp-button-success-bgactive);
36
+ --sp-component-button-success-bg-disabled: var(--sp-button-success-bgdisabled);
37
+ --sp-component-button-success-text: var(--sp-button-success-text);
38
+ --sp-component-button-success-text-disabled: var(--sp-button-success-textdisabled);
39
+
40
+ /* card roles */
41
+ --sp-component-card-bg: var(--sp-surface-card, var(--sp-color-neutral-50));
42
+ --sp-component-card-text: var(--sp-text-on-surface-default, var(--sp-color-neutral-900));
43
+ --sp-component-card-text-muted: var(--sp-text-on-surface-muted, var(--sp-color-neutral-600));
44
+ --sp-component-card-border: var(--sp-color-neutral-200);
45
+ --sp-component-card-border-base: var(--sp-component-card-ghost-border, var(--sp-component-card-bg));
46
+ --sp-component-card-shadow: var(--sp-shadow-sm);
47
+ --sp-component-card-shadow-elevated: var(--sp-shadow-lg);
48
+ --sp-component-card-shadow-flat: var(--sp-shadow-none);
49
+ --sp-component-card-shadow-outline: var(--sp-shadow-none);
50
+ --sp-component-card-shadow-ghost: var(--sp-shadow-none);
51
+ --sp-component-card-outline-bg: var(--sp-component-card-bg);
52
+ --sp-component-card-outline-border: var(--sp-component-card-border);
53
+ --sp-component-card-ghost-bg: var(--sp-component-button-ghost-bg, var(--sp-button-ghost-bg));
54
+ --sp-component-card-ghost-border: var(--sp-component-card-ghost-bg);
55
+
56
+ /* input roles */
57
+ --sp-component-input-border: var(--sp-form-default-border);
58
+ --sp-component-input-bg: var(--sp-surface-input, var(--sp-form-default-bg));
59
+ --sp-component-input-text: var(--sp-text-on-surface-default, var(--sp-form-default-text));
60
+ --sp-component-input-placeholder: var(--sp-text-on-surface-muted, var(--sp-form-default-placeholder));
61
+ --sp-component-input-border-focus: var(--sp-form-focus-border);
62
+ --sp-component-input-ring: var(--sp-form-focus-ring, var(--sp-color-focus-primary));
63
+ --sp-component-input-border-invalid: var(--sp-form-invalid-border);
64
+ --sp-component-input-bg-invalid: var(--sp-form-invalid-bg);
65
+ --sp-component-input-text-invalid: var(--sp-form-invalid-text);
66
+ --sp-component-input-border-valid: var(--sp-form-valid-border);
67
+ --sp-component-input-bg-valid: var(--sp-form-valid-bg);
68
+ --sp-component-input-text-valid: var(--sp-form-valid-text);
69
+ --sp-component-input-bg-disabled: var(--sp-form-disabled-bg);
70
+ --sp-component-input-text-disabled: var(--sp-form-disabled-text);
71
+ --sp-component-input-border-disabled: var(--sp-form-disabled-border);
72
+ }
73
+
3
74
  /* BUTTONS -------------------------------------------------------------- */
4
75
  .sp-btn {
5
76
  display: inline-flex;
@@ -8,11 +79,16 @@
8
79
  gap: var(--sp-space-2xs, 0.25rem);
9
80
  padding: var(--sp-space-2xs, 0.25rem) var(--sp-space-md, 1rem);
10
81
  border-radius: var(--sp-radius-md, 4px);
11
- border: 1px solid transparent;
82
+ border: 1px solid var(
83
+ --sp-component-button-border-base,
84
+ var(--sp-component-button-ghost-bg, var(--sp-button-ghost-bg))
85
+ );
12
86
  font-family: var(--sp-font-family-sans, system-ui);
13
87
  font-size: var(--sp-font-md-size, 1rem);
14
88
  line-height: 1;
15
89
  font-weight: var(--sp-font-md-weight, 500);
90
+ text-decoration: none;
91
+ appearance: none;
16
92
  transition:
17
93
  background-color var(--sp-duration-fast, 150ms) var(--sp-easing-out, ease),
18
94
  color var(--sp-duration-fast, 150ms) var(--sp-easing-out, ease),
@@ -23,8 +99,25 @@
23
99
  }
24
100
 
25
101
  .sp-btn:disabled,
26
- .sp-btn[aria-disabled="true"] {
102
+ .sp-btn[aria-disabled="true"],
103
+ .sp-btn.sp-btn--disabled {
27
104
  cursor: not-allowed;
105
+ opacity: var(--sp-opacity-disabled, 0.5);
106
+ pointer-events: none;
107
+ }
108
+
109
+ .sp-btn--loading {
110
+ pointer-events: none;
111
+ opacity: var(--sp-opacity-loading, 0.7);
112
+ }
113
+
114
+ .sp-btn--full {
115
+ width: 100%;
116
+ }
117
+
118
+ .sp-btn--icon {
119
+ padding-inline: var(--sp-space-sm, 0.75rem);
120
+ padding-block: var(--sp-space-3xs, 0.125rem);
28
121
  }
29
122
 
30
123
  /* sizes */
@@ -48,128 +141,157 @@
48
141
 
49
142
  /* primary */
50
143
  .sp-btn--primary {
51
- background-color: var(--sp-button-primary-bg, #4f46e5);
52
- color: var(--sp-button-primary-text, #ffffff);
53
- box-shadow: var(--sp-shadow-sm, 0 1px 2px rgba(15, 23, 42, 0.08));
144
+ background-color: var(--sp-component-button-primary-bg);
145
+ color: var(--sp-component-button-primary-text);
146
+ box-shadow: var(
147
+ --sp-component-button-shadow,
148
+ var(--sp-shadow-sm)
149
+ );
54
150
  }
55
151
 
56
152
  .sp-btn--primary.sp-btn--hover,
57
153
  .sp-btn--primary:hover {
58
- background-color: var(--sp-button-primary-bghover, #4338ca);
154
+ background-color: var(--sp-component-button-primary-bg-hover);
59
155
  }
60
156
 
61
157
  .sp-btn--primary.sp-btn--active,
62
158
  .sp-btn--primary:active {
63
- background-color: var(--sp-button-primary-bgactive, #3730a3);
159
+ background-color: var(--sp-component-button-primary-bg-active);
64
160
  }
65
161
 
66
162
  .sp-btn--primary.sp-btn--disabled,
67
163
  .sp-btn--primary:disabled {
68
- background-color: var(--sp-button-primary-bgdisabled, #e0e7ff);
69
- color: var(--sp-button-primary-textdisabled, #9ca3af);
164
+ background-color: var(--sp-component-button-primary-bg-disabled);
165
+ color: var(--sp-component-button-primary-text-disabled);
70
166
  box-shadow: none;
71
167
  }
72
168
 
73
169
  /* secondary */
74
170
  .sp-btn--secondary {
75
- background-color: var(--sp-button-secondary-bg, #ffffff);
76
- color: var(--sp-button-secondary-text, #4f46e5);
77
- border-color: var(--sp-button-secondary-border, #4f46e5);
171
+ background-color: var(--sp-component-button-secondary-bg);
172
+ color: var(--sp-component-button-secondary-text);
173
+ border-color: var(--sp-component-button-secondary-border);
78
174
  }
79
175
 
80
176
  .sp-btn--secondary.sp-btn--hover,
81
177
  .sp-btn--secondary:hover {
82
- background-color: var(--sp-button-secondary-bghover, #eef2ff);
178
+ background-color: var(--sp-component-button-secondary-bg-hover);
83
179
  }
84
180
 
85
181
  .sp-btn--secondary.sp-btn--active,
86
182
  .sp-btn--secondary:active {
87
- background-color: var(--sp-button-secondary-bgactive, #e0e7ff);
183
+ background-color: var(--sp-component-button-secondary-bg-active);
88
184
  }
89
185
 
90
186
  .sp-btn--secondary.sp-btn--disabled,
91
187
  .sp-btn--secondary:disabled {
92
- background-color: var(--sp-button-secondary-bgdisabled, #f9fafb);
93
- color: var(--sp-button-secondary-textdisabled, #9ca3af);
94
- border-color: var(--sp-button-secondary-borderdisabled, #e5e7eb);
188
+ background-color: var(--sp-component-button-secondary-bg-disabled);
189
+ color: var(--sp-component-button-secondary-text-disabled);
190
+ border-color: var(--sp-component-button-secondary-border-disabled);
95
191
  }
96
192
 
97
193
  /* ghost */
98
194
  .sp-btn--ghost {
99
- background-color: transparent;
100
- color: var(--sp-button-ghost-text, #4f46e5);
195
+ background-color: var(--sp-component-button-ghost-bg);
196
+ color: var(--sp-component-button-ghost-text);
101
197
  }
102
198
 
103
199
  .sp-btn--ghost.sp-btn--hover,
104
200
  .sp-btn--ghost:hover {
105
- background-color: var(--sp-button-ghost-bghover, rgba(79, 70, 229, 0.06));
201
+ background-color: var(--sp-component-button-ghost-bg-hover);
106
202
  }
107
203
 
108
204
  .sp-btn--ghost.sp-btn--active,
109
205
  .sp-btn--ghost:active {
110
- background-color: var(--sp-button-ghost-bgactive, rgba(79, 70, 229, 0.12));
206
+ background-color: var(--sp-component-button-ghost-bg-active);
111
207
  }
112
208
 
113
209
  .sp-btn--ghost.sp-btn--disabled,
114
210
  .sp-btn--ghost:disabled {
115
- color: var(--sp-button-ghost-textdisabled, #9ca3af);
116
- background-color: transparent;
211
+ color: var(--sp-component-button-ghost-text-disabled);
212
+ background-color: var(--sp-component-button-ghost-bg-disabled);
117
213
  }
118
214
 
119
215
  /* danger */
120
216
  .sp-btn--danger {
121
- background-color: var(--sp-button-danger-bg, #dc2626);
122
- color: var(--sp-button-danger-text, #ffffff);
217
+ background-color: var(--sp-component-button-danger-bg);
218
+ color: var(--sp-component-button-danger-text);
123
219
  }
124
220
 
125
221
  .sp-btn--danger.sp-btn--hover,
126
222
  .sp-btn--danger:hover {
127
- background-color: var(--sp-button-danger-bghover, #b91c1c);
223
+ background-color: var(--sp-component-button-danger-bg-hover);
128
224
  }
129
225
 
130
226
  .sp-btn--danger.sp-btn--active,
131
227
  .sp-btn--danger:active {
132
- background-color: var(--sp-button-danger-bgactive, #991b1b);
228
+ background-color: var(--sp-component-button-danger-bg-active);
133
229
  }
134
230
 
135
231
  .sp-btn--danger.sp-btn--disabled,
136
232
  .sp-btn--danger:disabled {
137
- background-color: var(--sp-button-danger-bgdisabled, #fee2e2);
138
- color: var(--sp-button-danger-textdisabled, #9ca3af);
233
+ background-color: var(--sp-component-button-danger-bg-disabled);
234
+ color: var(--sp-component-button-danger-text-disabled);
139
235
  }
140
236
 
141
237
  /* success */
142
238
  .sp-btn--success {
143
- background-color: var(--sp-button-success-bg, #16a34a);
144
- color: var(--sp-button-success-text, #ffffff);
239
+ background-color: var(--sp-component-button-success-bg);
240
+ color: var(--sp-component-button-success-text);
145
241
  }
146
242
 
147
243
  .sp-btn--success.sp-btn--hover,
148
244
  .sp-btn--success:hover {
149
- background-color: var(--sp-button-success-bghover, #15803d);
245
+ background-color: var(--sp-component-button-success-bg-hover);
150
246
  }
151
247
 
152
248
  .sp-btn--success.sp-btn--active,
153
249
  .sp-btn--success:active {
154
- background-color: var(--sp-button-success-bgactive, #166534);
250
+ background-color: var(--sp-component-button-success-bg-active);
155
251
  }
156
252
 
157
253
  .sp-btn--success.sp-btn--disabled,
158
254
  .sp-btn--success:disabled {
159
- background-color: var(--sp-button-success-bgdisabled, #bbf7d0);
160
- color: var(--sp-button-success-textdisabled, #065f46);
255
+ background-color: var(--sp-component-button-success-bg-disabled);
256
+ color: var(--sp-component-button-success-text-disabled);
161
257
  }
162
258
 
163
259
  /* INPUTS --------------------------------------------------------------- */
164
260
 
261
+ .sp-input-wrapper {
262
+ display: grid;
263
+ gap: var(--sp-space-4xs, 0.125rem);
264
+ width: 100%;
265
+ }
266
+
267
+ .sp-label {
268
+ color: var(--sp-component-input-text, var(--sp-text-on-surface-default, var(--sp-form-default-text)));
269
+ font-size: var(--sp-font-sm-size, 0.875rem);
270
+ font-weight: var(--sp-font-sm-weight, 500);
271
+ line-height: var(--sp-font-sm-line-height, 1.25rem);
272
+ }
273
+
274
+ .sp-helper-text {
275
+ color: var(--sp-text-on-surface-muted);
276
+ font-size: var(--sp-font-xs-size, 0.75rem);
277
+ line-height: var(--sp-font-xs-line-height, 1rem);
278
+ }
279
+
280
+ .sp-error-message {
281
+ color: var(--sp-color-danger, #dc2626);
282
+ font-size: var(--sp-font-xs-size, 0.75rem);
283
+ line-height: var(--sp-font-xs-line-height, 1rem);
284
+ }
285
+
165
286
  .sp-input {
166
287
  width: 100%;
167
288
  display: block;
289
+ appearance: none;
168
290
  padding: var(--sp-space-2xs, 0.25rem) var(--sp-space-md, 1rem);
169
291
  border-radius: var(--sp-radius-md, 4px);
170
- border: 1px solid var(--sp-form-default-border, #cbd5f5);
171
- background-color: var(--sp-form-default-bg, #ffffff);
172
- color: var(--sp-form-default-text, #0f172a);
292
+ border: 1px solid var(--sp-component-input-border, var(--sp-form-default-border));
293
+ background-color: var(--sp-component-input-bg, var(--sp-surface-input, var(--sp-form-default-bg)));
294
+ color: var(--sp-component-input-text, var(--sp-text-on-surface-default, var(--sp-form-default-text)));
173
295
  font-family: var(--sp-font-family-sans, system-ui);
174
296
  font-size: var(--sp-font-md-size, 1rem);
175
297
  line-height: var(--sp-font-md-line-height, 1.5rem);
@@ -180,32 +302,61 @@
180
302
  }
181
303
 
182
304
  .sp-input::placeholder {
183
- color: var(--sp-form-default-placeholder, #9ca3af);
305
+ color: var(--sp-component-input-placeholder, var(--sp-text-on-surface-muted, var(--sp-form-default-placeholder)));
306
+ }
307
+
308
+ .sp-input--sm {
309
+ padding: var(--sp-space-3xs, 0.125rem) var(--sp-space-sm, 0.75rem);
310
+ font-size: var(--sp-font-sm-size, 0.875rem);
311
+ line-height: var(--sp-font-sm-line-height, 1.25rem);
312
+ }
313
+
314
+ .sp-input--md {
315
+ padding: var(--sp-space-2xs, 0.25rem) var(--sp-space-md, 1rem);
316
+ font-size: var(--sp-font-md-size, 1rem);
317
+ line-height: var(--sp-font-md-line-height, 1.5rem);
318
+ }
319
+
320
+ .sp-input--lg {
321
+ padding: var(--sp-space-xs, 0.5rem) var(--sp-space-lg, 1.5rem);
322
+ font-size: var(--sp-font-lg-size, 1.125rem);
323
+ line-height: var(--sp-font-lg-line-height, 1.75rem);
324
+ }
325
+
326
+ .sp-input--full {
327
+ width: 100%;
184
328
  }
185
329
 
186
330
  .sp-input:focus,
187
331
  .sp-input--focus {
188
- border-color: var(--sp-form-focus-border, #4f46e5);
189
- box-shadow: 0 0 0 var(--sp-focus-ring-width, 2px) var(--sp-form-focus-ring, rgba(79, 70, 229, 0.5));
332
+ border-color: var(
333
+ --sp-component-input-border-focus,
334
+ var(--sp-component-input-border, var(--sp-form-focus-border))
335
+ );
336
+ box-shadow: 0 0 0 var(--sp-focus-ring-width, 2px) var(
337
+ --sp-component-input-ring,
338
+ var(--sp-form-focus-ring, var(--sp-color-focus-primary))
339
+ );
190
340
  outline: none;
191
341
  }
192
342
 
193
343
  .sp-input--error {
194
- border-color: var(--sp-form-invalid-border, #dc2626);
195
- background-color: var(--sp-form-invalid-bg, #fef2f2);
196
- color: var(--sp-form-invalid-text, #7f1d1d);
344
+ border-color: var(--sp-component-input-border-invalid, var(--sp-form-invalid-border));
345
+ background-color: var(--sp-component-input-bg-invalid, var(--sp-form-invalid-bg));
346
+ color: var(--sp-component-input-text-invalid, var(--sp-form-invalid-text));
197
347
  }
198
348
 
199
349
  .sp-input--success {
200
- border-color: var(--sp-form-valid-border, #16a34a);
201
- background-color: var(--sp-form-valid-bg, #ecfdf3);
202
- color: var(--sp-form-valid-text, #14532d);
350
+ border-color: var(--sp-component-input-border-valid, var(--sp-form-valid-border));
351
+ background-color: var(--sp-component-input-bg-valid, var(--sp-form-valid-bg));
352
+ color: var(--sp-component-input-text-valid, var(--sp-form-valid-text));
203
353
  }
204
354
 
205
355
  .sp-input--disabled {
206
- background-color: var(--sp-form-disabled-bg, #f9fafb);
207
- color: var(--sp-form-disabled-text, #9ca3af);
208
- border-color: var(--sp-form-disabled-border, #e5e7eb);
356
+ background-color: var(--sp-component-input-bg-disabled, var(--sp-form-disabled-bg));
357
+ color: var(--sp-component-input-text-disabled, var(--sp-form-disabled-text));
358
+ border-color: var(--sp-component-input-border-disabled, var(--sp-form-disabled-border));
359
+ cursor: not-allowed;
209
360
  }
210
361
 
211
362
  .sp-input--disabled,
@@ -216,32 +367,67 @@
216
367
  /* CARDS ---------------------------------------------------------------- */
217
368
 
218
369
  .sp-card {
219
- background-color: var(--sp-card-bg, #ffffff);
220
- color: var(--sp-card-text, #0f172a);
370
+ background-color: var(--sp-component-card-bg);
371
+ color: var(--sp-component-card-text);
221
372
  border-radius: var(--sp-radius-lg, 8px);
222
373
  padding: var(--sp-space-lg, 1.5rem);
223
- box-shadow: var(--sp-shadow-sm, 0 1px 2px rgba(15, 23, 42, 0.08));
224
- border: 1px solid transparent;
374
+ box-shadow: var(--sp-component-card-shadow, var(--sp-shadow-sm));
375
+ border: 1px solid var(
376
+ --sp-component-card-border-base,
377
+ var(--sp-component-card-ghost-border, var(--sp-component-card-bg))
378
+ );
379
+ }
380
+
381
+ .sp-card p,
382
+ .sp-card span {
383
+ color: var(--sp-component-card-text-muted);
384
+ }
385
+
386
+ .sp-card h3,
387
+ .sp-card h4 {
388
+ color: var(--sp-component-card-text);
225
389
  }
226
390
 
227
391
  .sp-card--elevated {
228
- box-shadow: var(--sp-shadow-lg, 0 8px 20px rgba(15, 23, 42, 0.18));
392
+ box-shadow: var(--sp-component-card-shadow-elevated, var(--sp-shadow-lg));
229
393
  }
230
394
 
231
395
  .sp-card--flat {
232
- box-shadow: var(--sp-shadow-none, none);
233
- border-color: var(--sp-color-neutral-200, #e5e7eb);
396
+ box-shadow: var(--sp-component-card-shadow-flat, var(--sp-shadow-none));
397
+ border-color: var(--sp-component-card-border);
234
398
  }
235
399
 
236
400
  .sp-card--outline {
237
- background-color: var(--sp-card-outline-bg, #ffffff);
238
- border-color: var(--sp-card-outline-border, #e5e7eb);
239
- box-shadow: var(--sp-shadow-none, none);
401
+ background-color: var(--sp-component-card-outline-bg, var(--sp-component-card-bg));
402
+ border-color: var(--sp-component-card-outline-border, var(--sp-component-card-border));
403
+ box-shadow: var(--sp-component-card-shadow-outline, var(--sp-shadow-none));
240
404
  }
241
405
 
242
406
  .sp-card--ghost {
243
- background-color: transparent;
244
- border-color: transparent;
245
- box-shadow: none;
407
+ background-color: var(--sp-component-card-ghost-bg);
408
+ border-color: var(--sp-component-card-ghost-border);
409
+ box-shadow: var(--sp-component-card-shadow-ghost, var(--sp-shadow-none));
410
+ }
411
+
412
+ .sp-card--padded {
413
+ padding: var(--sp-space-xl, 2rem);
414
+ }
415
+
416
+ .sp-card--full {
417
+ height: 100%;
418
+ }
419
+
420
+ .sp-card--interactive {
421
+ cursor: pointer;
422
+ transition:
423
+ transform var(--sp-duration-fast, 150ms) var(--sp-easing-out, ease),
424
+ box-shadow var(--sp-duration-fast, 150ms) var(--sp-easing-out, ease);
425
+ }
426
+
427
+ .sp-card--interactive:hover,
428
+ .sp-card--interactive:focus-visible,
429
+ .sp-card--interactive:focus-within {
430
+ transform: translateY(-1px);
431
+ box-shadow: var(--sp-component-card-shadow-elevated, var(--sp-shadow-lg));
246
432
  }
247
433
  }
package/dist/index.cjs CHANGED
@@ -17,29 +17,40 @@ function createSpectreTailwindTheme(options) {
17
17
  const { tokens, overrides } = options;
18
18
  const mergedTokens = {
19
19
  ...tokens,
20
- ...overrides ?? {}
20
+ ...overrides
21
21
  };
22
+ const colors = {
23
+ page: mergedTokens.surface?.page,
24
+ card: mergedTokens.surface?.card,
25
+ input: mergedTokens.surface?.input,
26
+ text: {
27
+ page: mergedTokens.text?.onPage?.default,
28
+ "page-muted": mergedTokens.text?.onPage?.muted,
29
+ surface: mergedTokens.text?.onSurface?.default,
30
+ "surface-muted": mergedTokens.text?.onSurface?.muted
31
+ },
32
+ primary: mergedTokens.buttons?.primary?.bg ?? mergedTokens.colors?.primary
33
+ };
34
+ const spacing = mergedTokens.spacing ?? {};
35
+ const borderRadius = mergedTokens.radii ?? {};
36
+ const boxShadow = mergedTokens.shadows ?? {};
37
+ const fontFamily = mergedTokens.typography?.families ?? {};
22
38
  const theme2 = {
23
- // Safely map core token groups into Tailwind theme fields.
24
- // Use `as any` where necessary to avoid overfitting types right now.
25
- colors: mergedTokens.colors ?? {},
26
- spacing: mergedTokens.spacing ?? {},
27
- borderRadius: mergedTokens.radii ?? {},
28
- boxShadow: mergedTokens.shadows ?? {},
29
- fontFamily: mergedTokens.typography?.families ?? {}
39
+ colors,
40
+ spacing,
41
+ borderRadius,
42
+ boxShadow,
43
+ fontFamily
30
44
  };
31
45
  return { theme: theme2 };
32
46
  }
33
47
 
34
48
  // src/tailwind/preset.ts
35
- var { theme } = createSpectreTailwindTheme({
36
- tokens: spectreTokens.tokens
37
- });
49
+ var { theme } = createSpectreTailwindTheme({ tokens: spectreTokens.tokens });
38
50
  var spectrePreset = {
39
- // Required for Tailwind's Config type with exactOptionalPropertyTypes
40
51
  content: [],
41
- theme: theme ?? {},
42
- plugins: []
52
+ theme: theme ?? {}
53
+ // ensure theme is never undefined
43
54
  };
44
55
 
45
56
  // src/recipes/button.ts
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/css-constants.ts","../src/tailwind/theme.ts","../src/tailwind/preset.ts","../src/recipes/button.ts","../src/recipes/card.ts","../src/recipes/input.ts"],"names":["theme","spectreTokens"],"mappings":";;;;;AAAO,IAAM,qBAAA,GAAwB;AAC9B,IAAM,2BAAA,GAA8B;AACpC,IAAM,0BAAA,GAA6B;AAEnC,IAAM,aAAA,GAAgB;AAAA,EAC3B,IAAA,EAAM,qBAAA;AAAA,EACN,UAAA,EAAY,2BAAA;AAAA,EACZ,SAAA,EAAW;AACb;;;ACIO,SAAS,2BACd,OAAA,EACsB;AACtB,EAAA,MAAM,EAAE,MAAA,EAAQ,SAAA,EAAU,GAAI,OAAA;AAG9B,EAAA,MAAM,YAAA,GAA8B;AAAA,IAClC,GAAG,MAAA;AAAA,IACH,GAAI,aAAa;AAAC,GACpB;AAEA,EAAA,MAAMA,MAAAA,GAAiC;AAAA;AAAA;AAAA,IAGrC,MAAA,EAAS,YAAA,CAAqB,MAAA,IAAU,EAAC;AAAA,IACzC,OAAA,EAAU,YAAA,CAAqB,OAAA,IAAW,EAAC;AAAA,IAC3C,YAAA,EAAe,YAAA,CAAqB,KAAA,IAAS,EAAC;AAAA,IAC9C,SAAA,EAAY,YAAA,CAAqB,OAAA,IAAW,EAAC;AAAA,IAC7C,UAAA,EAAa,YAAA,CAAqB,UAAA,EAAY,QAAA,IAAY;AAAC,GAC7D;AAEA,EAAA,OAAO,EAAE,OAAAA,MAAAA,EAAM;AACjB;;;AC9BA,IAAM,EAAE,KAAA,EAAM,GAAI,0BAAA,CAA2B;AAAA,EAC3C,MAAA,EAAQC;AACV,CAAC,CAAA;AAEM,IAAM,aAAA,GAAgC;AAAA;AAAA,EAE3C,SAAS,EAAC;AAAA,EACV,KAAA,EAAO,SAAS,EAAC;AAAA,EACjB,SAAS;AACX;;;ACmBO,SAAS,gBAAA,CAAiB,IAAA,GAA4B,EAAC,EAAW;AACvE,EAAA,MAAM;AAAA,IACJ,OAAA,GAAU,SAAA;AAAA,IACV,IAAA,GAAO,IAAA;AAAA,IACP,IAAA,GAAO,SAAA;AAAA,IACP,SAAA,GAAY,KAAA;AAAA,IACZ,OAAA,GAAU,KAAA;AAAA,IACV,QAAA,GAAW,KAAA;AAAA,IACX,QAAA,GAAW;AAAA,GACb,GAAI,IAAA;AAEJ,EAAA,MAAM,UAAoB,EAAC;AAG3B,EAAA,OAAA,CAAQ,KAAK,QAAQ,CAAA;AAGrB,EAAA,MAAM,UAAA,GAA4C;AAAA,IAChD,OAAA,EAAS,iBAAA;AAAA,IACT,SAAA,EAAW,mBAAA;AAAA,IACX,KAAA,EAAO,eAAA;AAAA,IACP,MAAA,EAAQ;AAAA,GACV;AACA,EAAA,OAAA,CAAQ,IAAA,CAAK,UAAA,CAAW,OAAO,CAAC,CAAA;AAGhC,EAAA,MAAM,OAAA,GAAsC;AAAA,IAC1C,EAAA,EAAI,YAAA;AAAA,IACJ,EAAA,EAAI,YAAA;AAAA,IACJ,EAAA,EAAI;AAAA,GACN;AACA,EAAA,OAAA,CAAQ,IAAA,CAAK,OAAA,CAAQ,IAAI,CAAC,CAAA;AAG1B,EAAA,IAAI,SAAS,SAAA,EAAW;AACtB,IAAA,MAAM,OAAA,GAA0D;AAAA,MAC9D,OAAA,EAAS,sBAAA;AAAA,MACT,OAAA,EAAS,sBAAA;AAAA,MACT,MAAA,EAAQ;AAAA,KACV;AACA,IAAA,OAAA,CAAQ,IAAA,CAAK,OAAA,CAAQ,IAAsC,CAAC,CAAA;AAAA,EAC9D;AAGA,EAAA,IAAI,SAAA,EAAW,OAAA,CAAQ,IAAA,CAAK,cAAc,CAAA;AAC1C,EAAA,IAAI,OAAA,EAAS,OAAA,CAAQ,IAAA,CAAK,iBAAiB,CAAA;AAC3C,EAAA,IAAI,QAAA,EAAU,OAAA,CAAQ,IAAA,CAAK,kBAAkB,CAAA;AAC7C,EAAA,IAAI,QAAA,EAAU,OAAA,CAAQ,IAAA,CAAK,cAAc,CAAA;AAGzC,EAAA,OAAO,QAAQ,MAAA,CAAO,OAAO,EAAE,IAAA,CAAK,GAAG,EAAE,IAAA,EAAK;AAChD;;;AC7DO,SAAS,cAAA,CAAe,IAAA,GAA0B,EAAC,EAAW;AACnE,EAAA,MAAM;AAAA,IACJ,OAAA,GAAU,UAAA;AAAA,IACV,WAAA,GAAc,KAAA;AAAA,IACd,MAAA,GAAS,KAAA;AAAA,IACT,UAAA,GAAa;AAAA,GACf,GAAI,IAAA;AAEJ,EAAA,MAAM,UAAoB,EAAC;AAG3B,EAAA,OAAA,CAAQ,KAAK,SAAS,CAAA;AAGtB,EAAA,MAAM,UAAA,GAA0C;AAAA,IAC9C,QAAA,EAAU,mBAAA;AAAA,IACV,OAAA,EAAS,kBAAA;AAAA,IACT,KAAA,EAAO;AAAA,GACT;AACA,EAAA,OAAA,CAAQ,IAAA,CAAK,UAAA,CAAW,OAAO,CAAC,CAAA;AAGhC,EAAA,IAAI,WAAA,EAAa,OAAA,CAAQ,IAAA,CAAK,sBAAsB,CAAA;AACpD,EAAA,IAAI,MAAA,EAAQ,OAAA,CAAQ,IAAA,CAAK,iBAAiB,CAAA;AAC1C,EAAA,IAAI,UAAA,EAAY,OAAA,CAAQ,IAAA,CAAK,eAAe,CAAA;AAE5C,EAAA,OAAO,QAAQ,MAAA,CAAO,OAAO,EAAE,IAAA,CAAK,GAAG,EAAE,IAAA,EAAK;AAChD;;;ACzBO,SAAS,eAAA,CAAgB,IAAA,GAA2B,EAAC,EAAW;AACrE,EAAA,MAAM;AAAA,IACJ,KAAA,GAAQ,SAAA;AAAA,IACR,IAAA,GAAO,IAAA;AAAA,IACP,SAAA,GAAY;AAAA,GACd,GAAI,IAAA;AAEJ,EAAA,MAAM,UAAoB,EAAC;AAG3B,EAAA,OAAA,CAAQ,KAAK,UAAU,CAAA;AAGvB,EAAA,IAAI,UAAU,OAAA,EAAS;AACrB,IAAA,OAAA,CAAQ,KAAK,iBAAiB,CAAA;AAAA,EAChC,CAAA,MAAA,IAAW,UAAU,SAAA,EAAW;AAC9B,IAAA,OAAA,CAAQ,KAAK,mBAAmB,CAAA;AAAA,EAClC;AAGA,EAAA,MAAM,OAAA,GAAqC;AAAA,IACzC,EAAA,EAAI,cAAA;AAAA,IACJ,EAAA,EAAI,cAAA;AAAA,IACJ,EAAA,EAAI;AAAA,GACN;AACA,EAAA,OAAA,CAAQ,IAAA,CAAK,OAAA,CAAQ,IAAI,CAAC,CAAA;AAG1B,EAAA,IAAI,SAAA,EAAW;AACb,IAAA,OAAA,CAAQ,KAAK,gBAAgB,CAAA;AAAA,EAC/B;AAEA,EAAA,OAAO,QAAQ,MAAA,CAAO,OAAO,EAAE,IAAA,CAAK,GAAG,EAAE,IAAA,EAAK;AAChD","file":"index.cjs","sourcesContent":["export const spectreBaseStylesPath = \"@phcdevworks/spectre-ui/dist/base.css\";\nexport const spectreComponentsStylesPath = \"@phcdevworks/spectre-ui/dist/components.css\";\nexport const spectreUtilitiesStylesPath = \"@phcdevworks/spectre-ui/dist/utilities.css\";\n\nexport const spectreStyles = {\n base: spectreBaseStylesPath,\n components: spectreComponentsStylesPath,\n utilities: spectreUtilitiesStylesPath,\n};\n","import type { Config as TailwindConfig } from 'tailwindcss';\nimport type { SpectreTokens } from '../tokens';\n\nexport interface SpectreTailwindTheme {\n theme: TailwindConfig['theme'];\n}\n\nexport interface CreateSpectreTailwindThemeOptions {\n tokens: SpectreTokens;\n overrides?: Partial<SpectreTokens>;\n}\n\nexport function createSpectreTailwindTheme(\n options: CreateSpectreTailwindThemeOptions,\n): SpectreTailwindTheme {\n const { tokens, overrides } = options;\n\n // Shallow merge overrides into tokens\n const mergedTokens: SpectreTokens = {\n ...tokens,\n ...(overrides ?? {}),\n };\n\n const theme: TailwindConfig['theme'] = {\n // Safely map core token groups into Tailwind theme fields.\n // Use `as any` where necessary to avoid overfitting types right now.\n colors: (mergedTokens as any).colors ?? {},\n spacing: (mergedTokens as any).spacing ?? {},\n borderRadius: (mergedTokens as any).radii ?? {},\n boxShadow: (mergedTokens as any).shadows ?? {},\n fontFamily: (mergedTokens as any).typography?.families ?? {},\n };\n\n return { theme };\n}\n","import type { Config as TailwindConfig } from 'tailwindcss';\nimport { spectreTokens } from '../tokens';\nimport { createSpectreTailwindTheme } from './theme';\n\nconst { theme } = createSpectreTailwindTheme({\n tokens: spectreTokens,\n});\n\nexport const spectrePreset: TailwindConfig = {\n // Required for Tailwind's Config type with exactOptionalPropertyTypes\n content: [],\n theme: theme ?? {},\n plugins: [],\n};\n\nexport const spectreTailwindPreset: TailwindConfig = spectrePreset;\n","export type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'danger';\nexport type ButtonSize = 'sm' | 'md' | 'lg';\nexport type ButtonTone = 'default' | 'success' | 'warning' | 'danger';\n\nexport interface ButtonRecipeOptions {\n variant?: ButtonVariant;\n size?: ButtonSize;\n tone?: ButtonTone;\n fullWidth?: boolean;\n loading?: boolean;\n disabled?: boolean;\n iconOnly?: boolean;\n}\n\n/**\n * Generate Spectre button classes.\n *\n * Rules:\n * - Base: \"sp-btn\"\n * - Variant: \"sp-btn--primary\" / \"sp-btn--secondary\" / \"sp-btn--ghost\" / \"sp-btn--danger\"\n * - default variant is \"primary\"\n * - Size: \"sp-btn--sm\" / \"sp-btn--md\" / \"sp-btn--lg\"\n * - default size is \"md\"\n * - Tone: \"sp-btn--tone-success\" / \"sp-btn--tone-warning\" / \"sp-btn--tone-danger\"\n * - default tone is \"default\" (no tone class)\n * - fullWidth: add \"sp-btn--full\"\n * - loading: add \"sp-btn--loading\"\n * - disabled: add \"sp-btn--disabled\"\n * - iconOnly: add \"sp-btn--icon\"\n *\n * Must return a single space-joined, trimmed class string.\n */\nexport function getButtonClasses(opts: ButtonRecipeOptions = {}): string {\n const {\n variant = 'primary',\n size = 'md',\n tone = 'default',\n fullWidth = false,\n loading = false,\n disabled = false,\n iconOnly = false,\n } = opts;\n\n const classes: string[] = [];\n\n // Base\n classes.push('sp-btn');\n\n // Variant\n const variantMap: Record<ButtonVariant, string> = {\n primary: 'sp-btn--primary',\n secondary: 'sp-btn--secondary',\n ghost: 'sp-btn--ghost',\n danger: 'sp-btn--danger',\n };\n classes.push(variantMap[variant]);\n\n // Size\n const sizeMap: Record<ButtonSize, string> = {\n sm: 'sp-btn--sm',\n md: 'sp-btn--md',\n lg: 'sp-btn--lg',\n };\n classes.push(sizeMap[size]);\n\n // Tone (optional)\n if (tone !== 'default') {\n const toneMap: Record<Exclude<ButtonTone, 'default'>, string> = {\n success: 'sp-btn--tone-success',\n warning: 'sp-btn--tone-warning',\n danger: 'sp-btn--tone-danger',\n };\n classes.push(toneMap[tone as Exclude<ButtonTone, 'default'>]);\n }\n\n // Flags\n if (fullWidth) classes.push('sp-btn--full');\n if (loading) classes.push('sp-btn--loading');\n if (disabled) classes.push('sp-btn--disabled');\n if (iconOnly) classes.push('sp-btn--icon');\n\n // Final class string\n return classes.filter(Boolean).join(' ').trim();\n}\n","export type CardVariant = 'elevated' | 'outline' | 'ghost';\n\nexport interface CardRecipeOptions {\n variant?: CardVariant;\n interactive?: boolean; // hover/focus styles\n padded?: boolean; // apply default padding\n fullHeight?: boolean;\n}\n\n/**\n * Generate Spectre card classes.\n *\n * Rules:\n * - Base class: \"sp-card\"\n * - Variant (default: elevated):\n * - \"sp-card--elevated\"\n * - \"sp-card--outline\"\n * - \"sp-card--ghost\"\n * - interactive: add \"sp-card--interactive\"\n * - padded: add \"sp-card--padded\"\n * - fullHeight: add \"sp-card--full\"\n */\nexport function getCardClasses(opts: CardRecipeOptions = {}): string {\n const {\n variant = 'elevated',\n interactive = false,\n padded = false,\n fullHeight = false,\n } = opts;\n\n const classes: string[] = [];\n\n // Base\n classes.push('sp-card');\n\n // Variant\n const variantMap: Record<CardVariant, string> = {\n elevated: 'sp-card--elevated',\n outline: 'sp-card--outline',\n ghost: 'sp-card--ghost',\n };\n classes.push(variantMap[variant]);\n\n // Flags\n if (interactive) classes.push('sp-card--interactive');\n if (padded) classes.push('sp-card--padded');\n if (fullHeight) classes.push('sp-card--full');\n\n return classes.filter(Boolean).join(' ').trim();\n}\n","export type InputState = 'default' | 'error' | 'success';\nexport type InputSize = 'sm' | 'md' | 'lg';\n\nexport interface InputRecipeOptions {\n state?: InputState;\n size?: InputSize;\n fullWidth?: boolean;\n}\n\n/**\n * Generate Spectre input classes.\n *\n * Rules:\n * - Base class: \"sp-input\"\n * - State:\n * - \"default\" => no state modifier\n * - \"error\" => \"sp-input--error\"\n * - \"success\" => \"sp-input--success\"\n * - Size (default: md):\n * - \"sp-input--sm\"\n * - \"sp-input--md\"\n * - \"sp-input--lg\"\n * - fullWidth: add \"sp-input--full\"\n */\nexport function getInputClasses(opts: InputRecipeOptions = {}): string {\n const {\n state = 'default',\n size = 'md',\n fullWidth = false,\n } = opts;\n\n const classes: string[] = [];\n\n // Base\n classes.push('sp-input');\n\n // State\n if (state === 'error') {\n classes.push('sp-input--error');\n } else if (state === 'success') {\n classes.push('sp-input--success');\n }\n\n // Size\n const sizeMap: Record<InputSize, string> = {\n sm: 'sp-input--sm',\n md: 'sp-input--md',\n lg: 'sp-input--lg',\n };\n classes.push(sizeMap[size]);\n\n // Flags\n if (fullWidth) {\n classes.push('sp-input--full');\n }\n\n return classes.filter(Boolean).join(' ').trim();\n}\n"]}
1
+ {"version":3,"sources":["../src/css-constants.ts","../src/tailwind/theme.ts","../src/tailwind/preset.ts","../src/recipes/button.ts","../src/recipes/card.ts","../src/recipes/input.ts"],"names":["theme","spectreTokens"],"mappings":"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","file":"index.cjs","sourcesContent":["export const spectreBaseStylesPath = \"@phcdevworks/spectre-ui/dist/base.css\";\nexport const spectreComponentsStylesPath = \"@phcdevworks/spectre-ui/dist/components.css\";\nexport const spectreUtilitiesStylesPath = \"@phcdevworks/spectre-ui/dist/utilities.css\";\n\nexport const spectreStyles = {\n base: spectreBaseStylesPath,\n components: spectreComponentsStylesPath,\n utilities: spectreUtilitiesStylesPath,\n};\n","import type { Config as TailwindConfig } from \"tailwindcss\";\nimport type { SpectreTokens } from \"../tokens\";\n\nexport interface SpectreTailwindTheme {\n theme: TailwindConfig[\"theme\"];\n}\n\nexport interface CreateSpectreTailwindThemeOptions {\n tokens: SpectreTokens;\n overrides?: Partial<SpectreTokens>;\n}\n\nexport function createSpectreTailwindTheme(\n options: CreateSpectreTailwindThemeOptions,\n): SpectreTailwindTheme {\n const { tokens, overrides } = options;\n\n // Shallow merge overrides into tokens\n const mergedTokens: SpectreTokens = {\n ...(tokens as SpectreTokens),\n ...(overrides as Partial<SpectreTokens> | undefined),\n };\n\n // Minimal, semantic color mapping\n const colors: Record<string, unknown> = {\n page: mergedTokens.surface?.page,\n card: mergedTokens.surface?.card,\n input: mergedTokens.surface?.input,\n text: {\n page: mergedTokens.text?.onPage?.default,\n \"page-muted\": mergedTokens.text?.onPage?.muted,\n surface: mergedTokens.text?.onSurface?.default,\n \"surface-muted\": mergedTokens.text?.onSurface?.muted,\n },\n primary:\n (mergedTokens as any).buttons?.primary?.bg ??\n (mergedTokens as any).colors?.primary,\n };\n\n const spacing: Record<string, unknown> =\n (mergedTokens as any).spacing ?? {};\n\n const borderRadius: Record<string, unknown> =\n (mergedTokens as any).radii ?? {};\n\n const boxShadow: Record<string, unknown> =\n (mergedTokens as any).shadows ?? {};\n\n const fontFamily: Record<string, unknown> =\n (mergedTokens as any).typography?.families ?? {};\n\n const theme: TailwindConfig[\"theme\"] = {\n colors: colors as any,\n spacing: spacing as any,\n borderRadius: borderRadius as any,\n boxShadow: boxShadow as any,\n fontFamily: fontFamily as any,\n };\n\n return { theme };\n}\n","import type { Config as TailwindConfig } from \"tailwindcss\";\nimport { spectreTokens } from \"../tokens\";\nimport { createSpectreTailwindTheme } from \"./theme\";\n\nconst { theme } = createSpectreTailwindTheme({ tokens: spectreTokens });\n\nexport const spectrePreset: TailwindConfig = {\n content: [],\n theme: theme ?? {}, // ensure theme is never undefined\n};\n\nexport default spectrePreset;\n","export type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'danger';\nexport type ButtonSize = 'sm' | 'md' | 'lg';\nexport type ButtonTone = 'default' | 'success' | 'warning' | 'danger';\n\nexport interface ButtonRecipeOptions {\n variant?: ButtonVariant;\n size?: ButtonSize;\n tone?: ButtonTone;\n fullWidth?: boolean;\n loading?: boolean;\n disabled?: boolean;\n iconOnly?: boolean;\n}\n\n/**\n * Generate Spectre button classes.\n *\n * Rules:\n * - Base: \"sp-btn\"\n * - Variant: \"sp-btn--primary\" / \"sp-btn--secondary\" / \"sp-btn--ghost\" / \"sp-btn--danger\"\n * - default variant is \"primary\"\n * - Size: \"sp-btn--sm\" / \"sp-btn--md\" / \"sp-btn--lg\"\n * - default size is \"md\"\n * - Tone: \"sp-btn--tone-success\" / \"sp-btn--tone-warning\" / \"sp-btn--tone-danger\"\n * - default tone is \"default\" (no tone class)\n * - fullWidth: add \"sp-btn--full\"\n * - loading: add \"sp-btn--loading\"\n * - disabled: add \"sp-btn--disabled\"\n * - iconOnly: add \"sp-btn--icon\"\n *\n * Must return a single space-joined, trimmed class string.\n */\nexport function getButtonClasses(opts: ButtonRecipeOptions = {}): string {\n const {\n variant = 'primary',\n size = 'md',\n tone = 'default',\n fullWidth = false,\n loading = false,\n disabled = false,\n iconOnly = false,\n } = opts;\n\n const classes: string[] = [];\n\n // Base\n classes.push('sp-btn');\n\n // Variant\n const variantMap: Record<ButtonVariant, string> = {\n primary: 'sp-btn--primary',\n secondary: 'sp-btn--secondary',\n ghost: 'sp-btn--ghost',\n danger: 'sp-btn--danger',\n };\n classes.push(variantMap[variant]);\n\n // Size\n const sizeMap: Record<ButtonSize, string> = {\n sm: 'sp-btn--sm',\n md: 'sp-btn--md',\n lg: 'sp-btn--lg',\n };\n classes.push(sizeMap[size]);\n\n // Tone (optional)\n if (tone !== 'default') {\n const toneMap: Record<Exclude<ButtonTone, 'default'>, string> = {\n success: 'sp-btn--tone-success',\n warning: 'sp-btn--tone-warning',\n danger: 'sp-btn--tone-danger',\n };\n classes.push(toneMap[tone as Exclude<ButtonTone, 'default'>]);\n }\n\n // Flags\n if (fullWidth) classes.push('sp-btn--full');\n if (loading) classes.push('sp-btn--loading');\n if (disabled) classes.push('sp-btn--disabled');\n if (iconOnly) classes.push('sp-btn--icon');\n\n // Final class string\n return classes.filter(Boolean).join(' ').trim();\n}\n","export type CardVariant = 'elevated' | 'outline' | 'ghost';\n\nexport interface CardRecipeOptions {\n variant?: CardVariant;\n interactive?: boolean; // hover/focus styles\n padded?: boolean; // apply default padding\n fullHeight?: boolean;\n}\n\n/**\n * Generate Spectre card classes.\n *\n * Rules:\n * - Base class: \"sp-card\"\n * - Variant (default: elevated):\n * - \"sp-card--elevated\"\n * - \"sp-card--outline\"\n * - \"sp-card--ghost\"\n * - interactive: add \"sp-card--interactive\"\n * - padded: add \"sp-card--padded\"\n * - fullHeight: add \"sp-card--full\"\n */\nexport function getCardClasses(opts: CardRecipeOptions = {}): string {\n const {\n variant = 'elevated',\n interactive = false,\n padded = false,\n fullHeight = false,\n } = opts;\n\n const classes: string[] = [];\n\n // Base\n classes.push('sp-card');\n\n // Variant\n const variantMap: Record<CardVariant, string> = {\n elevated: 'sp-card--elevated',\n outline: 'sp-card--outline',\n ghost: 'sp-card--ghost',\n };\n classes.push(variantMap[variant]);\n\n // Flags\n if (interactive) classes.push('sp-card--interactive');\n if (padded) classes.push('sp-card--padded');\n if (fullHeight) classes.push('sp-card--full');\n\n return classes.filter(Boolean).join(' ').trim();\n}\n","export type InputState = 'default' | 'error' | 'success';\nexport type InputSize = 'sm' | 'md' | 'lg';\n\nexport interface InputRecipeOptions {\n state?: InputState;\n size?: InputSize;\n fullWidth?: boolean;\n}\n\n/**\n * Generate Spectre input classes.\n *\n * Rules:\n * - Base class: \"sp-input\"\n * - State:\n * - \"default\" => no state modifier\n * - \"error\" => \"sp-input--error\"\n * - \"success\" => \"sp-input--success\"\n * - Size (default: md):\n * - \"sp-input--sm\"\n * - \"sp-input--md\"\n * - \"sp-input--lg\"\n * - fullWidth: add \"sp-input--full\"\n */\nexport function getInputClasses(opts: InputRecipeOptions = {}): string {\n const {\n state = 'default',\n size = 'md',\n fullWidth = false,\n } = opts;\n\n const classes: string[] = [];\n\n // Base\n classes.push('sp-input');\n\n // State\n if (state === 'error') {\n classes.push('sp-input--error');\n } else if (state === 'success') {\n classes.push('sp-input--success');\n }\n\n // Size\n const sizeMap: Record<InputSize, string> = {\n sm: 'sp-input--sm',\n md: 'sp-input--md',\n lg: 'sp-input--lg',\n };\n classes.push(sizeMap[size]);\n\n // Flags\n if (fullWidth) {\n classes.push('sp-input--full');\n }\n\n return classes.filter(Boolean).join(' ').trim();\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -14,7 +14,7 @@ declare const spectreStyles: {
14
14
  declare const spectrePreset: Config;
15
15
 
16
16
  interface SpectreTailwindTheme {
17
- theme: Config['theme'];
17
+ theme: Config["theme"];
18
18
  }
19
19
  interface CreateSpectreTailwindThemeOptions {
20
20
  tokens: SpectreTokens;
package/dist/index.d.ts CHANGED
@@ -14,7 +14,7 @@ declare const spectreStyles: {
14
14
  declare const spectrePreset: Config;
15
15
 
16
16
  interface SpectreTailwindTheme {
17
- theme: Config['theme'];
17
+ theme: Config["theme"];
18
18
  }
19
19
  interface CreateSpectreTailwindThemeOptions {
20
20
  tokens: SpectreTokens;
package/dist/index.js CHANGED
@@ -16,29 +16,40 @@ function createSpectreTailwindTheme(options) {
16
16
  const { tokens, overrides } = options;
17
17
  const mergedTokens = {
18
18
  ...tokens,
19
- ...overrides ?? {}
19
+ ...overrides
20
20
  };
21
+ const colors = {
22
+ page: mergedTokens.surface?.page,
23
+ card: mergedTokens.surface?.card,
24
+ input: mergedTokens.surface?.input,
25
+ text: {
26
+ page: mergedTokens.text?.onPage?.default,
27
+ "page-muted": mergedTokens.text?.onPage?.muted,
28
+ surface: mergedTokens.text?.onSurface?.default,
29
+ "surface-muted": mergedTokens.text?.onSurface?.muted
30
+ },
31
+ primary: mergedTokens.buttons?.primary?.bg ?? mergedTokens.colors?.primary
32
+ };
33
+ const spacing = mergedTokens.spacing ?? {};
34
+ const borderRadius = mergedTokens.radii ?? {};
35
+ const boxShadow = mergedTokens.shadows ?? {};
36
+ const fontFamily = mergedTokens.typography?.families ?? {};
21
37
  const theme2 = {
22
- // Safely map core token groups into Tailwind theme fields.
23
- // Use `as any` where necessary to avoid overfitting types right now.
24
- colors: mergedTokens.colors ?? {},
25
- spacing: mergedTokens.spacing ?? {},
26
- borderRadius: mergedTokens.radii ?? {},
27
- boxShadow: mergedTokens.shadows ?? {},
28
- fontFamily: mergedTokens.typography?.families ?? {}
38
+ colors,
39
+ spacing,
40
+ borderRadius,
41
+ boxShadow,
42
+ fontFamily
29
43
  };
30
44
  return { theme: theme2 };
31
45
  }
32
46
 
33
47
  // src/tailwind/preset.ts
34
- var { theme } = createSpectreTailwindTheme({
35
- tokens: tokens
36
- });
48
+ var { theme } = createSpectreTailwindTheme({ tokens: tokens });
37
49
  var spectrePreset = {
38
- // Required for Tailwind's Config type with exactOptionalPropertyTypes
39
50
  content: [],
40
- theme: theme ?? {},
41
- plugins: []
51
+ theme: theme ?? {}
52
+ // ensure theme is never undefined
42
53
  };
43
54
 
44
55
  // src/recipes/button.ts
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/css-constants.ts","../src/tailwind/theme.ts","../src/tailwind/preset.ts","../src/recipes/button.ts","../src/recipes/card.ts","../src/recipes/input.ts"],"names":["theme","spectreTokens"],"mappings":"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","file":"index.js","sourcesContent":["export const spectreBaseStylesPath = \"@phcdevworks/spectre-ui/dist/base.css\";\nexport const spectreComponentsStylesPath = \"@phcdevworks/spectre-ui/dist/components.css\";\nexport const spectreUtilitiesStylesPath = \"@phcdevworks/spectre-ui/dist/utilities.css\";\n\nexport const spectreStyles = {\n base: spectreBaseStylesPath,\n components: spectreComponentsStylesPath,\n utilities: spectreUtilitiesStylesPath,\n};\n","import type { Config as TailwindConfig } from 'tailwindcss';\nimport type { SpectreTokens } from '../tokens';\n\nexport interface SpectreTailwindTheme {\n theme: TailwindConfig['theme'];\n}\n\nexport interface CreateSpectreTailwindThemeOptions {\n tokens: SpectreTokens;\n overrides?: Partial<SpectreTokens>;\n}\n\nexport function createSpectreTailwindTheme(\n options: CreateSpectreTailwindThemeOptions,\n): SpectreTailwindTheme {\n const { tokens, overrides } = options;\n\n // Shallow merge overrides into tokens\n const mergedTokens: SpectreTokens = {\n ...tokens,\n ...(overrides ?? {}),\n };\n\n const theme: TailwindConfig['theme'] = {\n // Safely map core token groups into Tailwind theme fields.\n // Use `as any` where necessary to avoid overfitting types right now.\n colors: (mergedTokens as any).colors ?? {},\n spacing: (mergedTokens as any).spacing ?? {},\n borderRadius: (mergedTokens as any).radii ?? {},\n boxShadow: (mergedTokens as any).shadows ?? {},\n fontFamily: (mergedTokens as any).typography?.families ?? {},\n };\n\n return { theme };\n}\n","import type { Config as TailwindConfig } from 'tailwindcss';\nimport { spectreTokens } from '../tokens';\nimport { createSpectreTailwindTheme } from './theme';\n\nconst { theme } = createSpectreTailwindTheme({\n tokens: spectreTokens,\n});\n\nexport const spectrePreset: TailwindConfig = {\n // Required for Tailwind's Config type with exactOptionalPropertyTypes\n content: [],\n theme: theme ?? {},\n plugins: [],\n};\n\nexport const spectreTailwindPreset: TailwindConfig = spectrePreset;\n","export type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'danger';\nexport type ButtonSize = 'sm' | 'md' | 'lg';\nexport type ButtonTone = 'default' | 'success' | 'warning' | 'danger';\n\nexport interface ButtonRecipeOptions {\n variant?: ButtonVariant;\n size?: ButtonSize;\n tone?: ButtonTone;\n fullWidth?: boolean;\n loading?: boolean;\n disabled?: boolean;\n iconOnly?: boolean;\n}\n\n/**\n * Generate Spectre button classes.\n *\n * Rules:\n * - Base: \"sp-btn\"\n * - Variant: \"sp-btn--primary\" / \"sp-btn--secondary\" / \"sp-btn--ghost\" / \"sp-btn--danger\"\n * - default variant is \"primary\"\n * - Size: \"sp-btn--sm\" / \"sp-btn--md\" / \"sp-btn--lg\"\n * - default size is \"md\"\n * - Tone: \"sp-btn--tone-success\" / \"sp-btn--tone-warning\" / \"sp-btn--tone-danger\"\n * - default tone is \"default\" (no tone class)\n * - fullWidth: add \"sp-btn--full\"\n * - loading: add \"sp-btn--loading\"\n * - disabled: add \"sp-btn--disabled\"\n * - iconOnly: add \"sp-btn--icon\"\n *\n * Must return a single space-joined, trimmed class string.\n */\nexport function getButtonClasses(opts: ButtonRecipeOptions = {}): string {\n const {\n variant = 'primary',\n size = 'md',\n tone = 'default',\n fullWidth = false,\n loading = false,\n disabled = false,\n iconOnly = false,\n } = opts;\n\n const classes: string[] = [];\n\n // Base\n classes.push('sp-btn');\n\n // Variant\n const variantMap: Record<ButtonVariant, string> = {\n primary: 'sp-btn--primary',\n secondary: 'sp-btn--secondary',\n ghost: 'sp-btn--ghost',\n danger: 'sp-btn--danger',\n };\n classes.push(variantMap[variant]);\n\n // Size\n const sizeMap: Record<ButtonSize, string> = {\n sm: 'sp-btn--sm',\n md: 'sp-btn--md',\n lg: 'sp-btn--lg',\n };\n classes.push(sizeMap[size]);\n\n // Tone (optional)\n if (tone !== 'default') {\n const toneMap: Record<Exclude<ButtonTone, 'default'>, string> = {\n success: 'sp-btn--tone-success',\n warning: 'sp-btn--tone-warning',\n danger: 'sp-btn--tone-danger',\n };\n classes.push(toneMap[tone as Exclude<ButtonTone, 'default'>]);\n }\n\n // Flags\n if (fullWidth) classes.push('sp-btn--full');\n if (loading) classes.push('sp-btn--loading');\n if (disabled) classes.push('sp-btn--disabled');\n if (iconOnly) classes.push('sp-btn--icon');\n\n // Final class string\n return classes.filter(Boolean).join(' ').trim();\n}\n","export type CardVariant = 'elevated' | 'outline' | 'ghost';\n\nexport interface CardRecipeOptions {\n variant?: CardVariant;\n interactive?: boolean; // hover/focus styles\n padded?: boolean; // apply default padding\n fullHeight?: boolean;\n}\n\n/**\n * Generate Spectre card classes.\n *\n * Rules:\n * - Base class: \"sp-card\"\n * - Variant (default: elevated):\n * - \"sp-card--elevated\"\n * - \"sp-card--outline\"\n * - \"sp-card--ghost\"\n * - interactive: add \"sp-card--interactive\"\n * - padded: add \"sp-card--padded\"\n * - fullHeight: add \"sp-card--full\"\n */\nexport function getCardClasses(opts: CardRecipeOptions = {}): string {\n const {\n variant = 'elevated',\n interactive = false,\n padded = false,\n fullHeight = false,\n } = opts;\n\n const classes: string[] = [];\n\n // Base\n classes.push('sp-card');\n\n // Variant\n const variantMap: Record<CardVariant, string> = {\n elevated: 'sp-card--elevated',\n outline: 'sp-card--outline',\n ghost: 'sp-card--ghost',\n };\n classes.push(variantMap[variant]);\n\n // Flags\n if (interactive) classes.push('sp-card--interactive');\n if (padded) classes.push('sp-card--padded');\n if (fullHeight) classes.push('sp-card--full');\n\n return classes.filter(Boolean).join(' ').trim();\n}\n","export type InputState = 'default' | 'error' | 'success';\nexport type InputSize = 'sm' | 'md' | 'lg';\n\nexport interface InputRecipeOptions {\n state?: InputState;\n size?: InputSize;\n fullWidth?: boolean;\n}\n\n/**\n * Generate Spectre input classes.\n *\n * Rules:\n * - Base class: \"sp-input\"\n * - State:\n * - \"default\" => no state modifier\n * - \"error\" => \"sp-input--error\"\n * - \"success\" => \"sp-input--success\"\n * - Size (default: md):\n * - \"sp-input--sm\"\n * - \"sp-input--md\"\n * - \"sp-input--lg\"\n * - fullWidth: add \"sp-input--full\"\n */\nexport function getInputClasses(opts: InputRecipeOptions = {}): string {\n const {\n state = 'default',\n size = 'md',\n fullWidth = false,\n } = opts;\n\n const classes: string[] = [];\n\n // Base\n classes.push('sp-input');\n\n // State\n if (state === 'error') {\n classes.push('sp-input--error');\n } else if (state === 'success') {\n classes.push('sp-input--success');\n }\n\n // Size\n const sizeMap: Record<InputSize, string> = {\n sm: 'sp-input--sm',\n md: 'sp-input--md',\n lg: 'sp-input--lg',\n };\n classes.push(sizeMap[size]);\n\n // Flags\n if (fullWidth) {\n classes.push('sp-input--full');\n }\n\n return classes.filter(Boolean).join(' ').trim();\n}\n"]}
1
+ {"version":3,"sources":["../src/css-constants.ts","../src/tailwind/theme.ts","../src/tailwind/preset.ts","../src/recipes/button.ts","../src/recipes/card.ts","../src/recipes/input.ts"],"names":["theme","spectreTokens"],"mappings":"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","file":"index.js","sourcesContent":["export const spectreBaseStylesPath = \"@phcdevworks/spectre-ui/dist/base.css\";\nexport const spectreComponentsStylesPath = \"@phcdevworks/spectre-ui/dist/components.css\";\nexport const spectreUtilitiesStylesPath = \"@phcdevworks/spectre-ui/dist/utilities.css\";\n\nexport const spectreStyles = {\n base: spectreBaseStylesPath,\n components: spectreComponentsStylesPath,\n utilities: spectreUtilitiesStylesPath,\n};\n","import type { Config as TailwindConfig } from \"tailwindcss\";\nimport type { SpectreTokens } from \"../tokens\";\n\nexport interface SpectreTailwindTheme {\n theme: TailwindConfig[\"theme\"];\n}\n\nexport interface CreateSpectreTailwindThemeOptions {\n tokens: SpectreTokens;\n overrides?: Partial<SpectreTokens>;\n}\n\nexport function createSpectreTailwindTheme(\n options: CreateSpectreTailwindThemeOptions,\n): SpectreTailwindTheme {\n const { tokens, overrides } = options;\n\n // Shallow merge overrides into tokens\n const mergedTokens: SpectreTokens = {\n ...(tokens as SpectreTokens),\n ...(overrides as Partial<SpectreTokens> | undefined),\n };\n\n // Minimal, semantic color mapping\n const colors: Record<string, unknown> = {\n page: mergedTokens.surface?.page,\n card: mergedTokens.surface?.card,\n input: mergedTokens.surface?.input,\n text: {\n page: mergedTokens.text?.onPage?.default,\n \"page-muted\": mergedTokens.text?.onPage?.muted,\n surface: mergedTokens.text?.onSurface?.default,\n \"surface-muted\": mergedTokens.text?.onSurface?.muted,\n },\n primary:\n (mergedTokens as any).buttons?.primary?.bg ??\n (mergedTokens as any).colors?.primary,\n };\n\n const spacing: Record<string, unknown> =\n (mergedTokens as any).spacing ?? {};\n\n const borderRadius: Record<string, unknown> =\n (mergedTokens as any).radii ?? {};\n\n const boxShadow: Record<string, unknown> =\n (mergedTokens as any).shadows ?? {};\n\n const fontFamily: Record<string, unknown> =\n (mergedTokens as any).typography?.families ?? {};\n\n const theme: TailwindConfig[\"theme\"] = {\n colors: colors as any,\n spacing: spacing as any,\n borderRadius: borderRadius as any,\n boxShadow: boxShadow as any,\n fontFamily: fontFamily as any,\n };\n\n return { theme };\n}\n","import type { Config as TailwindConfig } from \"tailwindcss\";\nimport { spectreTokens } from \"../tokens\";\nimport { createSpectreTailwindTheme } from \"./theme\";\n\nconst { theme } = createSpectreTailwindTheme({ tokens: spectreTokens });\n\nexport const spectrePreset: TailwindConfig = {\n content: [],\n theme: theme ?? {}, // ensure theme is never undefined\n};\n\nexport default spectrePreset;\n","export type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'danger';\nexport type ButtonSize = 'sm' | 'md' | 'lg';\nexport type ButtonTone = 'default' | 'success' | 'warning' | 'danger';\n\nexport interface ButtonRecipeOptions {\n variant?: ButtonVariant;\n size?: ButtonSize;\n tone?: ButtonTone;\n fullWidth?: boolean;\n loading?: boolean;\n disabled?: boolean;\n iconOnly?: boolean;\n}\n\n/**\n * Generate Spectre button classes.\n *\n * Rules:\n * - Base: \"sp-btn\"\n * - Variant: \"sp-btn--primary\" / \"sp-btn--secondary\" / \"sp-btn--ghost\" / \"sp-btn--danger\"\n * - default variant is \"primary\"\n * - Size: \"sp-btn--sm\" / \"sp-btn--md\" / \"sp-btn--lg\"\n * - default size is \"md\"\n * - Tone: \"sp-btn--tone-success\" / \"sp-btn--tone-warning\" / \"sp-btn--tone-danger\"\n * - default tone is \"default\" (no tone class)\n * - fullWidth: add \"sp-btn--full\"\n * - loading: add \"sp-btn--loading\"\n * - disabled: add \"sp-btn--disabled\"\n * - iconOnly: add \"sp-btn--icon\"\n *\n * Must return a single space-joined, trimmed class string.\n */\nexport function getButtonClasses(opts: ButtonRecipeOptions = {}): string {\n const {\n variant = 'primary',\n size = 'md',\n tone = 'default',\n fullWidth = false,\n loading = false,\n disabled = false,\n iconOnly = false,\n } = opts;\n\n const classes: string[] = [];\n\n // Base\n classes.push('sp-btn');\n\n // Variant\n const variantMap: Record<ButtonVariant, string> = {\n primary: 'sp-btn--primary',\n secondary: 'sp-btn--secondary',\n ghost: 'sp-btn--ghost',\n danger: 'sp-btn--danger',\n };\n classes.push(variantMap[variant]);\n\n // Size\n const sizeMap: Record<ButtonSize, string> = {\n sm: 'sp-btn--sm',\n md: 'sp-btn--md',\n lg: 'sp-btn--lg',\n };\n classes.push(sizeMap[size]);\n\n // Tone (optional)\n if (tone !== 'default') {\n const toneMap: Record<Exclude<ButtonTone, 'default'>, string> = {\n success: 'sp-btn--tone-success',\n warning: 'sp-btn--tone-warning',\n danger: 'sp-btn--tone-danger',\n };\n classes.push(toneMap[tone as Exclude<ButtonTone, 'default'>]);\n }\n\n // Flags\n if (fullWidth) classes.push('sp-btn--full');\n if (loading) classes.push('sp-btn--loading');\n if (disabled) classes.push('sp-btn--disabled');\n if (iconOnly) classes.push('sp-btn--icon');\n\n // Final class string\n return classes.filter(Boolean).join(' ').trim();\n}\n","export type CardVariant = 'elevated' | 'outline' | 'ghost';\n\nexport interface CardRecipeOptions {\n variant?: CardVariant;\n interactive?: boolean; // hover/focus styles\n padded?: boolean; // apply default padding\n fullHeight?: boolean;\n}\n\n/**\n * Generate Spectre card classes.\n *\n * Rules:\n * - Base class: \"sp-card\"\n * - Variant (default: elevated):\n * - \"sp-card--elevated\"\n * - \"sp-card--outline\"\n * - \"sp-card--ghost\"\n * - interactive: add \"sp-card--interactive\"\n * - padded: add \"sp-card--padded\"\n * - fullHeight: add \"sp-card--full\"\n */\nexport function getCardClasses(opts: CardRecipeOptions = {}): string {\n const {\n variant = 'elevated',\n interactive = false,\n padded = false,\n fullHeight = false,\n } = opts;\n\n const classes: string[] = [];\n\n // Base\n classes.push('sp-card');\n\n // Variant\n const variantMap: Record<CardVariant, string> = {\n elevated: 'sp-card--elevated',\n outline: 'sp-card--outline',\n ghost: 'sp-card--ghost',\n };\n classes.push(variantMap[variant]);\n\n // Flags\n if (interactive) classes.push('sp-card--interactive');\n if (padded) classes.push('sp-card--padded');\n if (fullHeight) classes.push('sp-card--full');\n\n return classes.filter(Boolean).join(' ').trim();\n}\n","export type InputState = 'default' | 'error' | 'success';\nexport type InputSize = 'sm' | 'md' | 'lg';\n\nexport interface InputRecipeOptions {\n state?: InputState;\n size?: InputSize;\n fullWidth?: boolean;\n}\n\n/**\n * Generate Spectre input classes.\n *\n * Rules:\n * - Base class: \"sp-input\"\n * - State:\n * - \"default\" => no state modifier\n * - \"error\" => \"sp-input--error\"\n * - \"success\" => \"sp-input--success\"\n * - Size (default: md):\n * - \"sp-input--sm\"\n * - \"sp-input--md\"\n * - \"sp-input--lg\"\n * - fullWidth: add \"sp-input--full\"\n */\nexport function getInputClasses(opts: InputRecipeOptions = {}): string {\n const {\n state = 'default',\n size = 'md',\n fullWidth = false,\n } = opts;\n\n const classes: string[] = [];\n\n // Base\n classes.push('sp-input');\n\n // State\n if (state === 'error') {\n classes.push('sp-input--error');\n } else if (state === 'success') {\n classes.push('sp-input--success');\n }\n\n // Size\n const sizeMap: Record<InputSize, string> = {\n sm: 'sp-input--sm',\n md: 'sp-input--md',\n lg: 'sp-input--lg',\n };\n classes.push(sizeMap[size]);\n\n // Flags\n if (fullWidth) {\n classes.push('sp-input--full');\n }\n\n return classes.filter(Boolean).join(' ').trim();\n}\n"]}
@@ -27,10 +27,10 @@
27
27
  }
28
28
 
29
29
  .sp-shadow-soft {
30
- box-shadow: var(--sp-shadow-md, 0 4px 10px rgba(15, 23, 42, 0.1));
30
+ box-shadow: var(--sp-shadow-md);
31
31
  }
32
32
 
33
33
  .sp-shadow-strong {
34
- box-shadow: var(--sp-shadow-lg, 0 10px 25px rgba(15, 23, 42, 0.18));
34
+ box-shadow: var(--sp-shadow-lg);
35
35
  }
36
36
  }
package/package.json CHANGED
@@ -1,20 +1,52 @@
1
1
  {
2
2
  "name": "@phcdevworks/spectre-ui",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "Framework-agnostic UI layer for the Spectre design system. Provides base CSS, component classes, utilities, and a Tailwind preset powered by @phcdevworks/spectre-tokens.",
5
+ "keywords": [
6
+ "phcdevworks",
7
+ "spectre",
8
+ "spectre-ui",
9
+ "design-system",
10
+ "design-tokens",
11
+ "component-library",
12
+ "ui-library",
13
+ "ui-components",
14
+ "tailwindcss",
15
+ "css-utilities",
16
+ "css-framework",
17
+ "tokens",
18
+ "utilities"
19
+ ],
20
+ "author": "PHCDevworks",
21
+ "license": "MIT",
22
+ "funding": [
23
+ {
24
+ "type": "github",
25
+ "url": "https://github.com/sponsors/phcdevworks"
26
+ },
27
+ {
28
+ "type": "buymeacoffee",
29
+ "url": "https://buymeacoffee.com/phcdevworks"
30
+ }
31
+ ],
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "https://github.com/phcdevworks/spectre-ui.git"
35
+ },
36
+ "bugs": {
37
+ "url": "https://github.com/phcdevworks/spectre-ui/issues"
38
+ },
39
+ "homepage": "https://github.com/phcdevworks/spectre-ui#readme",
40
+ "type": "module",
5
41
  "main": "./dist/index.cjs",
6
42
  "module": "./dist/index.js",
7
43
  "types": "./dist/index.d.ts",
8
- "type": "module",
9
44
  "exports": {
10
45
  ".": {
11
46
  "types": "./dist/index.d.ts",
12
47
  "import": "./dist/index.js",
13
48
  "require": "./dist/index.cjs"
14
49
  },
15
- "./dist/base.css": "./dist/base.css",
16
- "./dist/components.css": "./dist/components.css",
17
- "./dist/utilities.css": "./dist/utilities.css",
18
50
  "./base.css": "./dist/base.css",
19
51
  "./components.css": "./dist/components.css",
20
52
  "./utilities.css": "./dist/utilities.css"
@@ -28,28 +60,9 @@
28
60
  "scripts": {
29
61
  "build": "tsup --config tsup.config.ts",
30
62
  "dev": "tsup --config tsup.config.ts --watch",
31
- "clean": "rm -rf dist"
32
- },
33
- "repository": {
34
- "type": "git",
35
- "url": "git+https://github.com/phcdevworks/spectre-ui.git"
36
- },
37
- "keywords": [
38
- "spectre",
39
- "design-system",
40
- "tailwindcss",
41
- "components",
42
- "utilities",
43
- "tokens",
44
- "ui-library",
45
- "phcdevworks"
46
- ],
47
- "author": "PHCDevworks",
48
- "license": "MIT",
49
- "bugs": {
50
- "url": "https://github.com/phcdevworks/spectre-ui/issues"
63
+ "clean": "rm -rf dist",
64
+ "test": "vitest run"
51
65
  },
52
- "homepage": "https://github.com/phcdevworks/spectre-ui#readme",
53
66
  "publishConfig": {
54
67
  "access": "public"
55
68
  },
@@ -57,14 +70,14 @@
57
70
  "tailwindcss": "^3.4.0 || ^4.0.0"
58
71
  },
59
72
  "dependencies": {
60
- "@phcdevworks/spectre-tokens": "^0.0.2",
61
- "user": "^0.0.0"
73
+ "@phcdevworks/spectre-tokens": "^0.0.4"
62
74
  },
63
75
  "devDependencies": {
64
76
  "autoprefixer": "^10.4.20",
65
77
  "postcss": "^8.4.35",
66
78
  "tailwindcss": "^3.4.15",
67
79
  "tsup": "^8.5.1",
68
- "typescript": "^5.9.3"
80
+ "typescript": "^5.9.3",
81
+ "vitest": "^2.1.4"
69
82
  }
70
83
  }