@phcdevworks/spectre-ui 0.0.3 → 0.0.5

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
@@ -24,7 +24,16 @@ npm install @phcdevworks/spectre-ui
24
24
 
25
25
  ### 1. Import Spectre CSS
26
26
 
27
- Import the CSS bundles anywhere in your app, layout, or build pipeline.
27
+ Import the canonical bundle anywhere in your app, layout, or build pipeline.
28
+
29
+ ```ts
30
+ // Recommended: one-line Spectre UI bundle (tokens + base + components + utilities)
31
+ import "@phcdevworks/spectre-ui/index.css";
32
+ ```
33
+
34
+ `index.css` automatically loads the Spectre Tokens CSS, so you don't need to import `@phcdevworks/spectre-tokens` separately in most apps.
35
+
36
+ **Advanced layer control:** If you prefer to manage the layers individually, you can still import each file directly.
28
37
 
29
38
  ```css
30
39
  @import "@phcdevworks/spectre-ui/base.css";
package/dist/base.css CHANGED
@@ -24,8 +24,8 @@
24
24
  font-family: var(--sp-font-family-sans, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif);
25
25
  font-size: var(--sp-font-md-size, 1rem);
26
26
  line-height: var(--sp-font-md-line-height, 1.5);
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));
27
+ color: var(--sp-text-on-page-default);
28
+ background-color: var(--sp-surface-page);
29
29
  }
30
30
 
31
31
  img,
@@ -52,7 +52,7 @@
52
52
  }
53
53
 
54
54
  a {
55
- color: var(--sp-link-color, var(--sp-color-primary-600, #4f46e5));
55
+ color: var(--sp-color-primary, inherit);
56
56
  text-decoration: none;
57
57
  }
58
58
 
@@ -61,12 +61,12 @@
61
61
  }
62
62
 
63
63
  :focus-visible {
64
- outline: 2px solid var(--sp-focus-ring-color, #4f46e5);
64
+ outline: 2px solid var(--sp-color-primary, currentColor);
65
65
  outline-offset: 2px;
66
66
  }
67
67
 
68
68
  ::selection {
69
- 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));
70
70
  color: inherit;
71
71
  }
72
72
  }
@@ -87,6 +87,8 @@
87
87
  font-size: var(--sp-font-md-size, 1rem);
88
88
  line-height: 1;
89
89
  font-weight: var(--sp-font-md-weight, 500);
90
+ text-decoration: none;
91
+ appearance: none;
90
92
  transition:
91
93
  background-color var(--sp-duration-fast, 150ms) var(--sp-easing-out, ease),
92
94
  color var(--sp-duration-fast, 150ms) var(--sp-easing-out, ease),
@@ -97,8 +99,25 @@
97
99
  }
98
100
 
99
101
  .sp-btn:disabled,
100
- .sp-btn[aria-disabled="true"] {
102
+ .sp-btn[aria-disabled="true"],
103
+ .sp-btn.sp-btn--disabled {
101
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);
102
121
  }
103
122
 
104
123
  /* sizes */
@@ -239,13 +258,39 @@
239
258
 
240
259
  /* INPUTS --------------------------------------------------------------- */
241
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
+
242
286
  .sp-input {
243
287
  width: 100%;
244
288
  display: block;
289
+ appearance: none;
245
290
  padding: var(--sp-space-2xs, 0.25rem) var(--sp-space-md, 1rem);
246
291
  border-radius: var(--sp-radius-md, 4px);
247
292
  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)));
293
+ background-color: var(--sp-component-input-bg, var(--sp-surface-input, var(--sp-form-default-bg)));
249
294
  color: var(--sp-component-input-text, var(--sp-text-on-surface-default, var(--sp-form-default-text)));
250
295
  font-family: var(--sp-font-family-sans, system-ui);
251
296
  font-size: var(--sp-font-md-size, 1rem);
@@ -260,6 +305,28 @@
260
305
  color: var(--sp-component-input-placeholder, var(--sp-text-on-surface-muted, var(--sp-form-default-placeholder)));
261
306
  }
262
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%;
328
+ }
329
+
263
330
  .sp-input:focus,
264
331
  .sp-input--focus {
265
332
  border-color: var(
@@ -289,6 +356,7 @@
289
356
  background-color: var(--sp-component-input-bg-disabled, var(--sp-form-disabled-bg));
290
357
  color: var(--sp-component-input-text-disabled, var(--sp-form-disabled-text));
291
358
  border-color: var(--sp-component-input-border-disabled, var(--sp-form-disabled-border));
359
+ cursor: not-allowed;
292
360
  }
293
361
 
294
362
  .sp-input--disabled,
@@ -299,7 +367,7 @@
299
367
  /* CARDS ---------------------------------------------------------------- */
300
368
 
301
369
  .sp-card {
302
- background: var(--sp-component-card-bg);
370
+ background-color: var(--sp-component-card-bg);
303
371
  color: var(--sp-component-card-text);
304
372
  border-radius: var(--sp-radius-lg, 8px);
305
373
  padding: var(--sp-space-lg, 1.5rem);
@@ -315,6 +383,11 @@
315
383
  color: var(--sp-component-card-text-muted);
316
384
  }
317
385
 
386
+ .sp-card h3,
387
+ .sp-card h4 {
388
+ color: var(--sp-component-card-text);
389
+ }
390
+
318
391
  .sp-card--elevated {
319
392
  box-shadow: var(--sp-component-card-shadow-elevated, var(--sp-shadow-lg));
320
393
  }
@@ -335,4 +408,26 @@
335
408
  border-color: var(--sp-component-card-ghost-border);
336
409
  box-shadow: var(--sp-component-card-shadow-ghost, var(--sp-shadow-none));
337
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));
432
+ }
338
433
  }
package/dist/index.cjs CHANGED
@@ -1,12 +1,7 @@
1
1
  'use strict';
2
2
 
3
- var plugin = require('tailwindcss/plugin');
4
3
  var spectreTokens = require('@phcdevworks/spectre-tokens');
5
4
 
6
- function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
7
-
8
- var plugin__default = /*#__PURE__*/_interopDefault(plugin);
9
-
10
5
  // src/css-constants.ts
11
6
  var spectreBaseStylesPath = "@phcdevworks/spectre-ui/dist/base.css";
12
7
  var spectreComponentsStylesPath = "@phcdevworks/spectre-ui/dist/components.css";
@@ -22,125 +17,40 @@ function createSpectreTailwindTheme(options) {
22
17
  const { tokens, overrides } = options;
23
18
  const mergedTokens = {
24
19
  ...tokens,
25
- ...overrides ?? {}
20
+ ...overrides
26
21
  };
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
- };
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
36
33
  };
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
- }
34
+ const spacing = mergedTokens.spacing ?? {};
35
+ const borderRadius = mergedTokens.radii ?? {};
36
+ const boxShadow = mergedTokens.shadows ?? {};
37
+ const fontFamily = mergedTokens.typography?.families ?? {};
61
38
  const theme2 = {
62
- // Safely map core token groups into Tailwind theme fields.
63
- // Use `as any` where necessary to avoid overfitting types right now.
64
- colors: themeColors,
65
- spacing: mergedTokens.spacing ?? {},
66
- borderRadius: mergedTokens.radii ?? {},
67
- boxShadow: mergedTokens.shadows ?? {},
68
- fontFamily: mergedTokens.typography?.families ?? {}
39
+ colors,
40
+ spacing,
41
+ borderRadius,
42
+ boxShadow,
43
+ fontFamily
69
44
  };
70
45
  return { theme: theme2 };
71
46
  }
72
47
 
73
48
  // src/tailwind/preset.ts
74
- var { theme } = createSpectreTailwindTheme({
75
- tokens: spectreTokens.tokens
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
- });
49
+ var { theme } = createSpectreTailwindTheme({ tokens: spectreTokens.tokens });
139
50
  var spectrePreset = {
140
- // Required for Tailwind's Config type with exactOptionalPropertyTypes
141
51
  content: [],
142
- theme: theme ?? {},
143
- plugins: [semanticUtilities]
52
+ theme: theme ?? {}
53
+ // ensure theme is never undefined
144
54
  };
145
55
 
146
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","plugin"],"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,MAAM,YAAA,GAAgB,YAAA,CAAqB,MAAA,IAAU,EAAC;AAEtD,EAAA,MAAM,oBAAA,GAAuB,CAC3B,QAAA,EACA,QAAA,KACG;AACH,IAAA,IAAI,CAAC,QAAA,IAAY,MAAA,CAAO,KAAK,QAAQ,CAAA,CAAE,WAAW,CAAA,EAAG;AACnD,MAAA,OAAO,OAAO,IAAA,CAAK,QAAQ,CAAA,CAAE,MAAA,GAAS,IAAI,QAAA,GAAW,MAAA;AAAA,IACvD;AAEA,IAAA,OAAO;AAAA,MACL,GAAG,QAAA;AAAA,MACH,GAAG;AAAA,KACL;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,WAAA,GAAmC;AAAA,IACvC,GAAG;AAAA,GACL;AAEA,EAAA,MAAM,aAAA,GAAgB,oBAAA;AAAA,IACpB,YAAA,CAAa,WAAW,EAAC;AAAA,IACxB,YAAA,CAAqB;AAAA,GACxB;AACA,EAAA,IAAI,aAAA,EAAe;AACjB,IAAA,WAAA,CAAY,OAAA,GAAU,aAAA;AAAA,EACxB;AAEA,EAAA,MAAM,UAAA,GAAa,oBAAA;AAAA,IACjB,YAAA,CAAa,QAAQ,EAAC;AAAA,IACrB,YAAA,CAAqB;AAAA,GACxB;AACA,EAAA,IAAI,UAAA,EAAY;AACd,IAAA,WAAA,CAAY,IAAA,GAAO,UAAA;AAAA,EACrB;AAEA,EAAA,MAAM,eAAA,GAAkB,oBAAA;AAAA,IACtB,YAAA,CAAa,aAAa,EAAC;AAAA,IAC1B,YAAA,CAAqB;AAAA,GACxB;AACA,EAAA,IAAI,eAAA,EAAiB;AACnB,IAAA,WAAA,CAAY,SAAA,GAAY,eAAA;AAAA,EAC1B;AAEA,EAAA,MAAMA,MAAAA,GAAiC;AAAA;AAAA;AAAA,IAGrC,MAAA,EAAQ,WAAA;AAAA,IACR,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;;;ACzEA,IAAM,EAAE,KAAA,EAAM,GAAI,0BAAA,CAA2B;AAAA,EAC3C,MAAA,EAAQC;AACV,CAAC,CAAA;AAED,IAAM,iBAAA,GAAoB,CAAC,KAAA,EAAgB,QAAA,KAA0C;AACnF,EAAA,IAAI,OAAO,UAAU,QAAA,EAAU;AAC7B,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,IAAI,KAAA,IAAS,OAAO,KAAA,KAAU,QAAA,EAAU;AACtC,IAAA,MAAM,eAAgB,KAAA,CAAkC,OAAA;AACxD,IAAA,IAAI,OAAO,iBAAiB,QAAA,EAAU;AACpC,MAAA,OAAO,YAAA;AAAA,IACT;AAEA,IAAA,MAAM,UAAA,GAAa,MAAA,CAAO,MAAA,CAAO,KAAgC,CAAA,CAAE,IAAA;AAAA,MACjE,CAAC,KAAA,KAAU,OAAO,KAAA,KAAU;AAAA,KAC9B;AACA,IAAA,IAAI,OAAO,eAAe,QAAA,EAAU;AAClC,MAAA,OAAO,UAAA;AAAA,IACT;AAAA,EACF;AAEA,EAAA,OAAO,QAAA;AACT,CAAA;AAEA,IAAM,iBAAA,GAAoBC,uBAAA,CAAO,CAAC,EAAE,cAAa,KAAM;AACrD,EAAA,MAAM,MAAA,GAASD,oBAAA;AACf,EAAA,MAAM,YAAA,GAAe,MAAA,EAAQ,MAAA,EAAQ,OAAA,IAAW,EAAC;AACjD,EAAA,MAAM,WAAA,GAAc,MAAA,EAAQ,KAAA,EAAO,OAAA,IAAW,EAAC;AAE/C,EAAA,MAAM,aAAA,GAAgB,MAAA,EAAQ,OAAA,IAAW,EAAC;AAC1C,EAAA,MAAM,UAAA,GAAa,MAAA,EAAQ,IAAA,IAAQ,EAAC;AAEpC,EAAA,MAAM,WAAA,GAAc,iBAAA;AAAA,IAClB,aAAA,CAAc,IAAA;AAAA,IACd,aAAa,IAAI;AAAA,GACnB;AACA,EAAA,MAAM,WAAA,GAAc,iBAAA;AAAA,IAClB,aAAA,CAAc,IAAA;AAAA,IACd,WAAA,CAAY,EAAA,IAAM,WAAA,IAAe,YAAA,CAAa,IAAI;AAAA,GACpD;AACA,EAAA,MAAM,YAAA,GAAe,iBAAA;AAAA,IACnB,aAAA,CAAc,KAAA;AAAA,IACd,WAAA,CAAY,MAAM,WAAA,IAAe;AAAA,GACnC;AAEA,EAAA,MAAM,UAAA,GAAa,iBAAA;AAAA,IACjB,UAAA,EAAY,EAAA,EAAI,IAAA,IAAQ,UAAA,EAAY,MAAA;AAAA,IACpC,YAAA,CAAa,KAAK,CAAA,IAAK,WAAA,CAAY;AAAA,GACrC;AACA,EAAA,MAAM,aAAA,GAAgB,iBAAA;AAAA,IACpB,UAAA,EAAY,EAAA,EAAI,OAAA,IAAW,UAAA,EAAY,SAAA;AAAA,IACvC,WAAA,CAAY,IAAA,IAAQ,UAAA,IAAc,YAAA,CAAa,KAAK;AAAA,GACtD;AAEA,EAAA,MAAM,YAAoD,EAAC;AAE3D,EAAA,IAAI,WAAA,EAAa;AACf,IAAA,SAAA,CAAU,kBAAkB,CAAA,GAAI,EAAE,eAAA,EAAiB,WAAA,EAAY;AAAA,EACjE;AAEA,EAAA,IAAI,WAAA,EAAa;AACf,IAAA,SAAA,CAAU,kBAAkB,CAAA,GAAI,EAAE,eAAA,EAAiB,WAAA,EAAY;AAAA,EACjE;AAEA,EAAA,IAAI,YAAA,EAAc;AAChB,IAAA,SAAA,CAAU,mBAAmB,CAAA,GAAI,EAAE,eAAA,EAAiB,YAAA,EAAa;AAAA,EACnE;AAEA,EAAA,IAAI,UAAA,EAAY;AACd,IAAA,SAAA,CAAU,eAAe,CAAA,GAAI,EAAE,KAAA,EAAO,UAAA,EAAW;AAAA,EACnD;AAEA,EAAA,IAAI,aAAA,EAAe;AACjB,IAAA,SAAA,CAAU,kBAAkB,CAAA,GAAI,EAAE,KAAA,EAAO,aAAA,EAAc;AAAA,EACzD;AAEA,EAAA,YAAA,CAAa,SAAS,CAAA;AACxB,CAAC,CAAA;AAEM,IAAM,aAAA,GAAgC;AAAA;AAAA,EAE3C,SAAS,EAAC;AAAA,EACV,KAAA,EAAO,SAAS,EAAC;AAAA,EACjB,OAAA,EAAS,CAAC,iBAAiB;AAC7B;;;AC3DO,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 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"]}
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.css ADDED
@@ -0,0 +1,8 @@
1
+ /* Spectre UI canonical bundle:
2
+ - Tokens first (defines --sp-* vars)
3
+ - Then UI base, components, utilities
4
+ */
5
+ @import "@phcdevworks/spectre-tokens/index.css";
6
+ @import "./base.css";
7
+ @import "./components.css";
8
+ @import "./utilities.css";
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
@@ -1,4 +1,3 @@
1
- import plugin from 'tailwindcss/plugin';
2
1
  import { tokens } from '@phcdevworks/spectre-tokens';
3
2
  export { tokens as spectreTokens } from '@phcdevworks/spectre-tokens';
4
3
 
@@ -17,125 +16,40 @@ function createSpectreTailwindTheme(options) {
17
16
  const { tokens, overrides } = options;
18
17
  const mergedTokens = {
19
18
  ...tokens,
20
- ...overrides ?? {}
19
+ ...overrides
21
20
  };
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
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
34
32
  };
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
- }
33
+ const spacing = mergedTokens.spacing ?? {};
34
+ const borderRadius = mergedTokens.radii ?? {};
35
+ const boxShadow = mergedTokens.shadows ?? {};
36
+ const fontFamily = mergedTokens.typography?.families ?? {};
56
37
  const theme2 = {
57
- // Safely map core token groups into Tailwind theme fields.
58
- // Use `as any` where necessary to avoid overfitting types right now.
59
- colors: themeColors,
60
- spacing: mergedTokens.spacing ?? {},
61
- borderRadius: mergedTokens.radii ?? {},
62
- boxShadow: mergedTokens.shadows ?? {},
63
- fontFamily: mergedTokens.typography?.families ?? {}
38
+ colors,
39
+ spacing,
40
+ borderRadius,
41
+ boxShadow,
42
+ fontFamily
64
43
  };
65
44
  return { theme: theme2 };
66
45
  }
67
46
 
68
47
  // src/tailwind/preset.ts
69
- var { theme } = createSpectreTailwindTheme({
70
- tokens: tokens
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
- });
48
+ var { theme } = createSpectreTailwindTheme({ tokens: tokens });
134
49
  var spectrePreset = {
135
- // Required for Tailwind's Config type with exactOptionalPropertyTypes
136
50
  content: [],
137
- theme: theme ?? {},
138
- plugins: [semanticUtilities]
51
+ theme: theme ?? {}
52
+ // ensure theme is never undefined
139
53
  };
140
54
 
141
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","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"]}
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@phcdevworks/spectre-ui",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
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
5
  "keywords": [
6
6
  "phcdevworks",
@@ -47,6 +47,7 @@
47
47
  "import": "./dist/index.js",
48
48
  "require": "./dist/index.cjs"
49
49
  },
50
+ "./index.css": "./dist/index.css",
50
51
  "./base.css": "./dist/base.css",
51
52
  "./components.css": "./dist/components.css",
52
53
  "./utilities.css": "./dist/utilities.css"
@@ -60,7 +61,8 @@
60
61
  "scripts": {
61
62
  "build": "tsup --config tsup.config.ts",
62
63
  "dev": "tsup --config tsup.config.ts --watch",
63
- "clean": "rm -rf dist"
64
+ "clean": "rm -rf dist",
65
+ "test": "vitest run"
64
66
  },
65
67
  "publishConfig": {
66
68
  "access": "public"
@@ -69,13 +71,14 @@
69
71
  "tailwindcss": "^3.4.0 || ^4.0.0"
70
72
  },
71
73
  "dependencies": {
72
- "@phcdevworks/spectre-tokens": "^0.0.3"
74
+ "@phcdevworks/spectre-tokens": "^0.0.4"
73
75
  },
74
76
  "devDependencies": {
75
77
  "autoprefixer": "^10.4.20",
76
78
  "postcss": "^8.4.35",
77
79
  "tailwindcss": "^3.4.15",
78
80
  "tsup": "^8.5.1",
79
- "typescript": "^5.9.3"
81
+ "typescript": "^5.9.3",
82
+ "vitest": "^2.1.4"
80
83
  }
81
84
  }