@phcdevworks/spectre-ui 0.0.2 → 0.0.3

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, var(--sp-color-neutral-900, #0f172a));
28
+ background-color: var(--sp-surface-page, var(--sp-color-neutral-50, #f8fafc));
22
29
  }
23
30
 
24
31
  img,
@@ -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,7 +79,10 @@
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;
@@ -48,116 +122,119 @@
48
122
 
49
123
  /* primary */
50
124
  .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));
125
+ background-color: var(--sp-component-button-primary-bg);
126
+ color: var(--sp-component-button-primary-text);
127
+ box-shadow: var(
128
+ --sp-component-button-shadow,
129
+ var(--sp-shadow-sm)
130
+ );
54
131
  }
55
132
 
56
133
  .sp-btn--primary.sp-btn--hover,
57
134
  .sp-btn--primary:hover {
58
- background-color: var(--sp-button-primary-bghover, #4338ca);
135
+ background-color: var(--sp-component-button-primary-bg-hover);
59
136
  }
60
137
 
61
138
  .sp-btn--primary.sp-btn--active,
62
139
  .sp-btn--primary:active {
63
- background-color: var(--sp-button-primary-bgactive, #3730a3);
140
+ background-color: var(--sp-component-button-primary-bg-active);
64
141
  }
65
142
 
66
143
  .sp-btn--primary.sp-btn--disabled,
67
144
  .sp-btn--primary:disabled {
68
- background-color: var(--sp-button-primary-bgdisabled, #e0e7ff);
69
- color: var(--sp-button-primary-textdisabled, #9ca3af);
145
+ background-color: var(--sp-component-button-primary-bg-disabled);
146
+ color: var(--sp-component-button-primary-text-disabled);
70
147
  box-shadow: none;
71
148
  }
72
149
 
73
150
  /* secondary */
74
151
  .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);
152
+ background-color: var(--sp-component-button-secondary-bg);
153
+ color: var(--sp-component-button-secondary-text);
154
+ border-color: var(--sp-component-button-secondary-border);
78
155
  }
79
156
 
80
157
  .sp-btn--secondary.sp-btn--hover,
81
158
  .sp-btn--secondary:hover {
82
- background-color: var(--sp-button-secondary-bghover, #eef2ff);
159
+ background-color: var(--sp-component-button-secondary-bg-hover);
83
160
  }
84
161
 
85
162
  .sp-btn--secondary.sp-btn--active,
86
163
  .sp-btn--secondary:active {
87
- background-color: var(--sp-button-secondary-bgactive, #e0e7ff);
164
+ background-color: var(--sp-component-button-secondary-bg-active);
88
165
  }
89
166
 
90
167
  .sp-btn--secondary.sp-btn--disabled,
91
168
  .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);
169
+ background-color: var(--sp-component-button-secondary-bg-disabled);
170
+ color: var(--sp-component-button-secondary-text-disabled);
171
+ border-color: var(--sp-component-button-secondary-border-disabled);
95
172
  }
96
173
 
97
174
  /* ghost */
98
175
  .sp-btn--ghost {
99
- background-color: transparent;
100
- color: var(--sp-button-ghost-text, #4f46e5);
176
+ background-color: var(--sp-component-button-ghost-bg);
177
+ color: var(--sp-component-button-ghost-text);
101
178
  }
102
179
 
103
180
  .sp-btn--ghost.sp-btn--hover,
104
181
  .sp-btn--ghost:hover {
105
- background-color: var(--sp-button-ghost-bghover, rgba(79, 70, 229, 0.06));
182
+ background-color: var(--sp-component-button-ghost-bg-hover);
106
183
  }
107
184
 
108
185
  .sp-btn--ghost.sp-btn--active,
109
186
  .sp-btn--ghost:active {
110
- background-color: var(--sp-button-ghost-bgactive, rgba(79, 70, 229, 0.12));
187
+ background-color: var(--sp-component-button-ghost-bg-active);
111
188
  }
112
189
 
113
190
  .sp-btn--ghost.sp-btn--disabled,
114
191
  .sp-btn--ghost:disabled {
115
- color: var(--sp-button-ghost-textdisabled, #9ca3af);
116
- background-color: transparent;
192
+ color: var(--sp-component-button-ghost-text-disabled);
193
+ background-color: var(--sp-component-button-ghost-bg-disabled);
117
194
  }
118
195
 
119
196
  /* danger */
120
197
  .sp-btn--danger {
121
- background-color: var(--sp-button-danger-bg, #dc2626);
122
- color: var(--sp-button-danger-text, #ffffff);
198
+ background-color: var(--sp-component-button-danger-bg);
199
+ color: var(--sp-component-button-danger-text);
123
200
  }
124
201
 
125
202
  .sp-btn--danger.sp-btn--hover,
126
203
  .sp-btn--danger:hover {
127
- background-color: var(--sp-button-danger-bghover, #b91c1c);
204
+ background-color: var(--sp-component-button-danger-bg-hover);
128
205
  }
129
206
 
130
207
  .sp-btn--danger.sp-btn--active,
131
208
  .sp-btn--danger:active {
132
- background-color: var(--sp-button-danger-bgactive, #991b1b);
209
+ background-color: var(--sp-component-button-danger-bg-active);
133
210
  }
134
211
 
135
212
  .sp-btn--danger.sp-btn--disabled,
136
213
  .sp-btn--danger:disabled {
137
- background-color: var(--sp-button-danger-bgdisabled, #fee2e2);
138
- color: var(--sp-button-danger-textdisabled, #9ca3af);
214
+ background-color: var(--sp-component-button-danger-bg-disabled);
215
+ color: var(--sp-component-button-danger-text-disabled);
139
216
  }
140
217
 
141
218
  /* success */
142
219
  .sp-btn--success {
143
- background-color: var(--sp-button-success-bg, #16a34a);
144
- color: var(--sp-button-success-text, #ffffff);
220
+ background-color: var(--sp-component-button-success-bg);
221
+ color: var(--sp-component-button-success-text);
145
222
  }
146
223
 
147
224
  .sp-btn--success.sp-btn--hover,
148
225
  .sp-btn--success:hover {
149
- background-color: var(--sp-button-success-bghover, #15803d);
226
+ background-color: var(--sp-component-button-success-bg-hover);
150
227
  }
151
228
 
152
229
  .sp-btn--success.sp-btn--active,
153
230
  .sp-btn--success:active {
154
- background-color: var(--sp-button-success-bgactive, #166534);
231
+ background-color: var(--sp-component-button-success-bg-active);
155
232
  }
156
233
 
157
234
  .sp-btn--success.sp-btn--disabled,
158
235
  .sp-btn--success:disabled {
159
- background-color: var(--sp-button-success-bgdisabled, #bbf7d0);
160
- color: var(--sp-button-success-textdisabled, #065f46);
236
+ background-color: var(--sp-component-button-success-bg-disabled);
237
+ color: var(--sp-component-button-success-text-disabled);
161
238
  }
162
239
 
163
240
  /* INPUTS --------------------------------------------------------------- */
@@ -167,9 +244,9 @@
167
244
  display: block;
168
245
  padding: var(--sp-space-2xs, 0.25rem) var(--sp-space-md, 1rem);
169
246
  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);
247
+ border: 1px solid var(--sp-component-input-border, var(--sp-form-default-border));
248
+ background: var(--sp-component-input-bg, var(--sp-surface-input, var(--sp-form-default-bg)));
249
+ color: var(--sp-component-input-text, var(--sp-text-on-surface-default, var(--sp-form-default-text)));
173
250
  font-family: var(--sp-font-family-sans, system-ui);
174
251
  font-size: var(--sp-font-md-size, 1rem);
175
252
  line-height: var(--sp-font-md-line-height, 1.5rem);
@@ -180,32 +257,38 @@
180
257
  }
181
258
 
182
259
  .sp-input::placeholder {
183
- color: var(--sp-form-default-placeholder, #9ca3af);
260
+ color: var(--sp-component-input-placeholder, var(--sp-text-on-surface-muted, var(--sp-form-default-placeholder)));
184
261
  }
185
262
 
186
263
  .sp-input:focus,
187
264
  .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));
265
+ border-color: var(
266
+ --sp-component-input-border-focus,
267
+ var(--sp-component-input-border, var(--sp-form-focus-border))
268
+ );
269
+ box-shadow: 0 0 0 var(--sp-focus-ring-width, 2px) var(
270
+ --sp-component-input-ring,
271
+ var(--sp-form-focus-ring, var(--sp-color-focus-primary))
272
+ );
190
273
  outline: none;
191
274
  }
192
275
 
193
276
  .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);
277
+ border-color: var(--sp-component-input-border-invalid, var(--sp-form-invalid-border));
278
+ background-color: var(--sp-component-input-bg-invalid, var(--sp-form-invalid-bg));
279
+ color: var(--sp-component-input-text-invalid, var(--sp-form-invalid-text));
197
280
  }
198
281
 
199
282
  .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);
283
+ border-color: var(--sp-component-input-border-valid, var(--sp-form-valid-border));
284
+ background-color: var(--sp-component-input-bg-valid, var(--sp-form-valid-bg));
285
+ color: var(--sp-component-input-text-valid, var(--sp-form-valid-text));
203
286
  }
204
287
 
205
288
  .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);
289
+ background-color: var(--sp-component-input-bg-disabled, var(--sp-form-disabled-bg));
290
+ color: var(--sp-component-input-text-disabled, var(--sp-form-disabled-text));
291
+ border-color: var(--sp-component-input-border-disabled, var(--sp-form-disabled-border));
209
292
  }
210
293
 
211
294
  .sp-input--disabled,
@@ -216,32 +299,40 @@
216
299
  /* CARDS ---------------------------------------------------------------- */
217
300
 
218
301
  .sp-card {
219
- background-color: var(--sp-card-bg, #ffffff);
220
- color: var(--sp-card-text, #0f172a);
302
+ background: var(--sp-component-card-bg);
303
+ color: var(--sp-component-card-text);
221
304
  border-radius: var(--sp-radius-lg, 8px);
222
305
  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;
306
+ box-shadow: var(--sp-component-card-shadow, var(--sp-shadow-sm));
307
+ border: 1px solid var(
308
+ --sp-component-card-border-base,
309
+ var(--sp-component-card-ghost-border, var(--sp-component-card-bg))
310
+ );
311
+ }
312
+
313
+ .sp-card p,
314
+ .sp-card span {
315
+ color: var(--sp-component-card-text-muted);
225
316
  }
226
317
 
227
318
  .sp-card--elevated {
228
- box-shadow: var(--sp-shadow-lg, 0 8px 20px rgba(15, 23, 42, 0.18));
319
+ box-shadow: var(--sp-component-card-shadow-elevated, var(--sp-shadow-lg));
229
320
  }
230
321
 
231
322
  .sp-card--flat {
232
- box-shadow: var(--sp-shadow-none, none);
233
- border-color: var(--sp-color-neutral-200, #e5e7eb);
323
+ box-shadow: var(--sp-component-card-shadow-flat, var(--sp-shadow-none));
324
+ border-color: var(--sp-component-card-border);
234
325
  }
235
326
 
236
327
  .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);
328
+ background-color: var(--sp-component-card-outline-bg, var(--sp-component-card-bg));
329
+ border-color: var(--sp-component-card-outline-border, var(--sp-component-card-border));
330
+ box-shadow: var(--sp-component-card-shadow-outline, var(--sp-shadow-none));
240
331
  }
241
332
 
242
333
  .sp-card--ghost {
243
- background-color: transparent;
244
- border-color: transparent;
245
- box-shadow: none;
334
+ background-color: var(--sp-component-card-ghost-bg);
335
+ border-color: var(--sp-component-card-ghost-border);
336
+ box-shadow: var(--sp-component-card-shadow-ghost, var(--sp-shadow-none));
246
337
  }
247
338
  }
package/dist/index.cjs CHANGED
@@ -1,7 +1,12 @@
1
1
  'use strict';
2
2
 
3
+ var plugin = require('tailwindcss/plugin');
3
4
  var spectreTokens = require('@phcdevworks/spectre-tokens');
4
5
 
6
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
7
+
8
+ var plugin__default = /*#__PURE__*/_interopDefault(plugin);
9
+
5
10
  // src/css-constants.ts
6
11
  var spectreBaseStylesPath = "@phcdevworks/spectre-ui/dist/base.css";
7
12
  var spectreComponentsStylesPath = "@phcdevworks/spectre-ui/dist/components.css";
@@ -19,10 +24,44 @@ function createSpectreTailwindTheme(options) {
19
24
  ...tokens,
20
25
  ...overrides ?? {}
21
26
  };
27
+ const mergedColors = mergedTokens.colors ?? {};
28
+ const attachSemanticColors = (existing, semantic) => {
29
+ if (!semantic || Object.keys(semantic).length === 0) {
30
+ return Object.keys(existing).length > 0 ? existing : void 0;
31
+ }
32
+ return {
33
+ ...existing,
34
+ ...semantic
35
+ };
36
+ };
37
+ const themeColors = {
38
+ ...mergedColors
39
+ };
40
+ const surfaceColors = attachSemanticColors(
41
+ mergedColors.surface ?? {},
42
+ mergedTokens.surface
43
+ );
44
+ if (surfaceColors) {
45
+ themeColors.surface = surfaceColors;
46
+ }
47
+ const textColors = attachSemanticColors(
48
+ mergedColors.text ?? {},
49
+ mergedTokens.text
50
+ );
51
+ if (textColors) {
52
+ themeColors.text = textColors;
53
+ }
54
+ const componentColors = attachSemanticColors(
55
+ mergedColors.component ?? {},
56
+ mergedTokens.component
57
+ );
58
+ if (componentColors) {
59
+ themeColors.component = componentColors;
60
+ }
22
61
  const theme2 = {
23
62
  // Safely map core token groups into Tailwind theme fields.
24
63
  // Use `as any` where necessary to avoid overfitting types right now.
25
- colors: mergedTokens.colors ?? {},
64
+ colors: themeColors,
26
65
  spacing: mergedTokens.spacing ?? {},
27
66
  borderRadius: mergedTokens.radii ?? {},
28
67
  boxShadow: mergedTokens.shadows ?? {},
@@ -35,11 +74,73 @@ function createSpectreTailwindTheme(options) {
35
74
  var { theme } = createSpectreTailwindTheme({
36
75
  tokens: spectreTokens.tokens
37
76
  });
77
+ var resolveTokenValue = (value, fallback) => {
78
+ if (typeof value === "string") {
79
+ return value;
80
+ }
81
+ if (value && typeof value === "object") {
82
+ const maybeDefault = value.default;
83
+ if (typeof maybeDefault === "string") {
84
+ return maybeDefault;
85
+ }
86
+ const firstEntry = Object.values(value).find(
87
+ (entry) => typeof entry === "string"
88
+ );
89
+ if (typeof firstEntry === "string") {
90
+ return firstEntry;
91
+ }
92
+ }
93
+ return fallback;
94
+ };
95
+ var semanticUtilities = plugin__default.default(({ addUtilities }) => {
96
+ const tokens = spectreTokens.tokens;
97
+ const neutralScale = tokens?.colors?.neutral ?? {};
98
+ const formDefault = tokens?.forms?.default ?? {};
99
+ const surfaceTokens = tokens?.surface ?? {};
100
+ const textTokens = tokens?.text ?? {};
101
+ const surfacePage = resolveTokenValue(
102
+ surfaceTokens.page,
103
+ neutralScale["50"]
104
+ );
105
+ const surfaceCard = resolveTokenValue(
106
+ surfaceTokens.card,
107
+ formDefault.bg ?? surfacePage ?? neutralScale["50"]
108
+ );
109
+ const surfaceInput = resolveTokenValue(
110
+ surfaceTokens.input,
111
+ formDefault.bg ?? surfaceCard ?? surfacePage
112
+ );
113
+ const textOnPage = resolveTokenValue(
114
+ textTokens?.on?.page ?? textTokens?.onPage,
115
+ neutralScale["900"] ?? formDefault.text
116
+ );
117
+ const textOnSurface = resolveTokenValue(
118
+ textTokens?.on?.surface ?? textTokens?.onSurface,
119
+ formDefault.text ?? textOnPage ?? neutralScale["900"]
120
+ );
121
+ const utilities = {};
122
+ if (surfacePage) {
123
+ utilities[".bg-surface-page"] = { backgroundColor: surfacePage };
124
+ }
125
+ if (surfaceCard) {
126
+ utilities[".bg-surface-card"] = { backgroundColor: surfaceCard };
127
+ }
128
+ if (surfaceInput) {
129
+ utilities[".bg-surface-input"] = { backgroundColor: surfaceInput };
130
+ }
131
+ if (textOnPage) {
132
+ utilities[".text-on-page"] = { color: textOnPage };
133
+ }
134
+ if (textOnSurface) {
135
+ utilities[".text-on-surface"] = { color: textOnSurface };
136
+ }
137
+ addUtilities(utilities);
138
+ });
38
139
  var spectrePreset = {
39
140
  // Required for Tailwind's Config type with exactOptionalPropertyTypes
40
141
  content: [],
41
142
  theme: theme ?? {},
42
- plugins: []
143
+ plugins: [semanticUtilities]
43
144
  };
44
145
 
45
146
  // 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":"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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,\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","plugin"],"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,\n ...(overrides ?? {}),\n };\n\n const mergedColors = (mergedTokens as any).colors ?? {};\n\n const attachSemanticColors = (\n existing: Record<string, any>,\n semantic: Record<string, any> | undefined,\n ) => {\n if (!semantic || Object.keys(semantic).length === 0) {\n return Object.keys(existing).length > 0 ? existing : undefined;\n }\n\n return {\n ...existing,\n ...semantic,\n };\n };\n\n const themeColors: Record<string, any> = {\n ...mergedColors,\n };\n\n const surfaceColors = attachSemanticColors(\n mergedColors.surface ?? {},\n (mergedTokens as any).surface,\n );\n if (surfaceColors) {\n themeColors.surface = surfaceColors;\n }\n\n const textColors = attachSemanticColors(\n mergedColors.text ?? {},\n (mergedTokens as any).text,\n );\n if (textColors) {\n themeColors.text = textColors;\n }\n\n const componentColors = attachSemanticColors(\n mergedColors.component ?? {},\n (mergedTokens as any).component,\n );\n if (componentColors) {\n themeColors.component = componentColors;\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: themeColors,\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 plugin from 'tailwindcss/plugin';\nimport { spectreTokens } from '../tokens';\nimport { createSpectreTailwindTheme } from './theme';\n\nconst { theme } = createSpectreTailwindTheme({\n tokens: spectreTokens,\n});\n\nconst resolveTokenValue = (value: unknown, fallback?: string): string | undefined => {\n if (typeof value === 'string') {\n return value;\n }\n\n if (value && typeof value === 'object') {\n const maybeDefault = (value as Record<string, unknown>).default;\n if (typeof maybeDefault === 'string') {\n return maybeDefault;\n }\n\n const firstEntry = Object.values(value as Record<string, unknown>).find(\n (entry) => typeof entry === 'string',\n );\n if (typeof firstEntry === 'string') {\n return firstEntry;\n }\n }\n\n return fallback;\n};\n\nconst semanticUtilities = plugin(({ addUtilities }) => {\n const tokens = spectreTokens as any;\n const neutralScale = tokens?.colors?.neutral ?? {};\n const formDefault = tokens?.forms?.default ?? {};\n\n const surfaceTokens = tokens?.surface ?? {};\n const textTokens = tokens?.text ?? {};\n\n const surfacePage = resolveTokenValue(\n surfaceTokens.page,\n neutralScale['50'],\n );\n const surfaceCard = resolveTokenValue(\n surfaceTokens.card,\n formDefault.bg ?? surfacePage ?? neutralScale['50'],\n );\n const surfaceInput = resolveTokenValue(\n surfaceTokens.input,\n formDefault.bg ?? surfaceCard ?? surfacePage,\n );\n\n const textOnPage = resolveTokenValue(\n textTokens?.on?.page ?? textTokens?.onPage,\n neutralScale['900'] ?? formDefault.text,\n );\n const textOnSurface = resolveTokenValue(\n textTokens?.on?.surface ?? textTokens?.onSurface,\n formDefault.text ?? textOnPage ?? neutralScale['900'],\n );\n\n const utilities: Record<string, Record<string, string>> = {};\n\n if (surfacePage) {\n utilities['.bg-surface-page'] = { backgroundColor: surfacePage };\n }\n\n if (surfaceCard) {\n utilities['.bg-surface-card'] = { backgroundColor: surfaceCard };\n }\n\n if (surfaceInput) {\n utilities['.bg-surface-input'] = { backgroundColor: surfaceInput };\n }\n\n if (textOnPage) {\n utilities['.text-on-page'] = { color: textOnPage };\n }\n\n if (textOnSurface) {\n utilities['.text-on-surface'] = { color: textOnSurface };\n }\n\n addUtilities(utilities);\n});\n\nexport const spectrePreset: TailwindConfig = {\n // Required for Tailwind's Config type with exactOptionalPropertyTypes\n content: [],\n theme: theme ?? {},\n plugins: [semanticUtilities],\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"]}
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ import plugin from 'tailwindcss/plugin';
1
2
  import { tokens } from '@phcdevworks/spectre-tokens';
2
3
  export { tokens as spectreTokens } from '@phcdevworks/spectre-tokens';
3
4
 
@@ -18,10 +19,44 @@ function createSpectreTailwindTheme(options) {
18
19
  ...tokens,
19
20
  ...overrides ?? {}
20
21
  };
22
+ const mergedColors = mergedTokens.colors ?? {};
23
+ const attachSemanticColors = (existing, semantic) => {
24
+ if (!semantic || Object.keys(semantic).length === 0) {
25
+ return Object.keys(existing).length > 0 ? existing : void 0;
26
+ }
27
+ return {
28
+ ...existing,
29
+ ...semantic
30
+ };
31
+ };
32
+ const themeColors = {
33
+ ...mergedColors
34
+ };
35
+ const surfaceColors = attachSemanticColors(
36
+ mergedColors.surface ?? {},
37
+ mergedTokens.surface
38
+ );
39
+ if (surfaceColors) {
40
+ themeColors.surface = surfaceColors;
41
+ }
42
+ const textColors = attachSemanticColors(
43
+ mergedColors.text ?? {},
44
+ mergedTokens.text
45
+ );
46
+ if (textColors) {
47
+ themeColors.text = textColors;
48
+ }
49
+ const componentColors = attachSemanticColors(
50
+ mergedColors.component ?? {},
51
+ mergedTokens.component
52
+ );
53
+ if (componentColors) {
54
+ themeColors.component = componentColors;
55
+ }
21
56
  const theme2 = {
22
57
  // Safely map core token groups into Tailwind theme fields.
23
58
  // Use `as any` where necessary to avoid overfitting types right now.
24
- colors: mergedTokens.colors ?? {},
59
+ colors: themeColors,
25
60
  spacing: mergedTokens.spacing ?? {},
26
61
  borderRadius: mergedTokens.radii ?? {},
27
62
  boxShadow: mergedTokens.shadows ?? {},
@@ -34,11 +69,73 @@ function createSpectreTailwindTheme(options) {
34
69
  var { theme } = createSpectreTailwindTheme({
35
70
  tokens: tokens
36
71
  });
72
+ var resolveTokenValue = (value, fallback) => {
73
+ if (typeof value === "string") {
74
+ return value;
75
+ }
76
+ if (value && typeof value === "object") {
77
+ const maybeDefault = value.default;
78
+ if (typeof maybeDefault === "string") {
79
+ return maybeDefault;
80
+ }
81
+ const firstEntry = Object.values(value).find(
82
+ (entry) => typeof entry === "string"
83
+ );
84
+ if (typeof firstEntry === "string") {
85
+ return firstEntry;
86
+ }
87
+ }
88
+ return fallback;
89
+ };
90
+ var semanticUtilities = plugin(({ addUtilities }) => {
91
+ const tokens$1 = tokens;
92
+ const neutralScale = tokens$1?.colors?.neutral ?? {};
93
+ const formDefault = tokens$1?.forms?.default ?? {};
94
+ const surfaceTokens = tokens$1?.surface ?? {};
95
+ const textTokens = tokens$1?.text ?? {};
96
+ const surfacePage = resolveTokenValue(
97
+ surfaceTokens.page,
98
+ neutralScale["50"]
99
+ );
100
+ const surfaceCard = resolveTokenValue(
101
+ surfaceTokens.card,
102
+ formDefault.bg ?? surfacePage ?? neutralScale["50"]
103
+ );
104
+ const surfaceInput = resolveTokenValue(
105
+ surfaceTokens.input,
106
+ formDefault.bg ?? surfaceCard ?? surfacePage
107
+ );
108
+ const textOnPage = resolveTokenValue(
109
+ textTokens?.on?.page ?? textTokens?.onPage,
110
+ neutralScale["900"] ?? formDefault.text
111
+ );
112
+ const textOnSurface = resolveTokenValue(
113
+ textTokens?.on?.surface ?? textTokens?.onSurface,
114
+ formDefault.text ?? textOnPage ?? neutralScale["900"]
115
+ );
116
+ const utilities = {};
117
+ if (surfacePage) {
118
+ utilities[".bg-surface-page"] = { backgroundColor: surfacePage };
119
+ }
120
+ if (surfaceCard) {
121
+ utilities[".bg-surface-card"] = { backgroundColor: surfaceCard };
122
+ }
123
+ if (surfaceInput) {
124
+ utilities[".bg-surface-input"] = { backgroundColor: surfaceInput };
125
+ }
126
+ if (textOnPage) {
127
+ utilities[".text-on-page"] = { color: textOnPage };
128
+ }
129
+ if (textOnSurface) {
130
+ utilities[".text-on-surface"] = { color: textOnSurface };
131
+ }
132
+ addUtilities(utilities);
133
+ });
37
134
  var spectrePreset = {
38
135
  // Required for Tailwind's Config type with exactOptionalPropertyTypes
39
136
  content: [],
40
137
  theme: theme ?? {},
41
- plugins: []
138
+ plugins: [semanticUtilities]
42
139
  };
43
140
 
44
141
  // 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","tokens"],"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 mergedColors = (mergedTokens as any).colors ?? {};\n\n const attachSemanticColors = (\n existing: Record<string, any>,\n semantic: Record<string, any> | undefined,\n ) => {\n if (!semantic || Object.keys(semantic).length === 0) {\n return Object.keys(existing).length > 0 ? existing : undefined;\n }\n\n return {\n ...existing,\n ...semantic,\n };\n };\n\n const themeColors: Record<string, any> = {\n ...mergedColors,\n };\n\n const surfaceColors = attachSemanticColors(\n mergedColors.surface ?? {},\n (mergedTokens as any).surface,\n );\n if (surfaceColors) {\n themeColors.surface = surfaceColors;\n }\n\n const textColors = attachSemanticColors(\n mergedColors.text ?? {},\n (mergedTokens as any).text,\n );\n if (textColors) {\n themeColors.text = textColors;\n }\n\n const componentColors = attachSemanticColors(\n mergedColors.component ?? {},\n (mergedTokens as any).component,\n );\n if (componentColors) {\n themeColors.component = componentColors;\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: themeColors,\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 plugin from 'tailwindcss/plugin';\nimport { spectreTokens } from '../tokens';\nimport { createSpectreTailwindTheme } from './theme';\n\nconst { theme } = createSpectreTailwindTheme({\n tokens: spectreTokens,\n});\n\nconst resolveTokenValue = (value: unknown, fallback?: string): string | undefined => {\n if (typeof value === 'string') {\n return value;\n }\n\n if (value && typeof value === 'object') {\n const maybeDefault = (value as Record<string, unknown>).default;\n if (typeof maybeDefault === 'string') {\n return maybeDefault;\n }\n\n const firstEntry = Object.values(value as Record<string, unknown>).find(\n (entry) => typeof entry === 'string',\n );\n if (typeof firstEntry === 'string') {\n return firstEntry;\n }\n }\n\n return fallback;\n};\n\nconst semanticUtilities = plugin(({ addUtilities }) => {\n const tokens = spectreTokens as any;\n const neutralScale = tokens?.colors?.neutral ?? {};\n const formDefault = tokens?.forms?.default ?? {};\n\n const surfaceTokens = tokens?.surface ?? {};\n const textTokens = tokens?.text ?? {};\n\n const surfacePage = resolveTokenValue(\n surfaceTokens.page,\n neutralScale['50'],\n );\n const surfaceCard = resolveTokenValue(\n surfaceTokens.card,\n formDefault.bg ?? surfacePage ?? neutralScale['50'],\n );\n const surfaceInput = resolveTokenValue(\n surfaceTokens.input,\n formDefault.bg ?? surfaceCard ?? surfacePage,\n );\n\n const textOnPage = resolveTokenValue(\n textTokens?.on?.page ?? textTokens?.onPage,\n neutralScale['900'] ?? formDefault.text,\n );\n const textOnSurface = resolveTokenValue(\n textTokens?.on?.surface ?? textTokens?.onSurface,\n formDefault.text ?? textOnPage ?? neutralScale['900'],\n );\n\n const utilities: Record<string, Record<string, string>> = {};\n\n if (surfacePage) {\n utilities['.bg-surface-page'] = { backgroundColor: surfacePage };\n }\n\n if (surfaceCard) {\n utilities['.bg-surface-card'] = { backgroundColor: surfaceCard };\n }\n\n if (surfaceInput) {\n utilities['.bg-surface-input'] = { backgroundColor: surfaceInput };\n }\n\n if (textOnPage) {\n utilities['.text-on-page'] = { color: textOnPage };\n }\n\n if (textOnSurface) {\n utilities['.text-on-surface'] = { color: textOnSurface };\n }\n\n addUtilities(utilities);\n});\n\nexport const spectrePreset: TailwindConfig = {\n // Required for Tailwind's Config type with exactOptionalPropertyTypes\n content: [],\n theme: theme ?? {},\n plugins: [semanticUtilities],\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"]}
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.3",
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"
@@ -30,26 +62,6 @@
30
62
  "dev": "tsup --config tsup.config.ts --watch",
31
63
  "clean": "rm -rf dist"
32
64
  },
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"
51
- },
52
- "homepage": "https://github.com/phcdevworks/spectre-ui#readme",
53
65
  "publishConfig": {
54
66
  "access": "public"
55
67
  },
@@ -57,8 +69,7 @@
57
69
  "tailwindcss": "^3.4.0 || ^4.0.0"
58
70
  },
59
71
  "dependencies": {
60
- "@phcdevworks/spectre-tokens": "^0.0.2",
61
- "user": "^0.0.0"
72
+ "@phcdevworks/spectre-tokens": "^0.0.3"
62
73
  },
63
74
  "devDependencies": {
64
75
  "autoprefixer": "^10.4.20",