@pure-ds/storybook 0.1.6 → 0.1.8

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.
@@ -1,8 +1,139 @@
1
1
  import { html } from 'lit';
2
2
 
3
+ const toRgb = (raw) => {
4
+ if (!raw) {
5
+ return null;
6
+ }
7
+
8
+ const value = raw.trim();
9
+
10
+ if (!value) {
11
+ return null;
12
+ }
13
+
14
+ const hexMatch = value.match(/^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i);
15
+ if (hexMatch) {
16
+ let hex = hexMatch[1];
17
+
18
+ if (hex.length === 3) {
19
+ hex = hex
20
+ .split('')
21
+ .map((ch) => ch + ch)
22
+ .join('');
23
+ }
24
+
25
+ if (hex.length === 8) {
26
+ hex = hex.slice(0, 6);
27
+ }
28
+
29
+ const intVal = parseInt(hex, 16);
30
+ return {
31
+ r: (intVal >> 16) & 255,
32
+ g: (intVal >> 8) & 255,
33
+ b: intVal & 255
34
+ };
35
+ }
36
+
37
+ const rgbMatch = value.match(/rgba?\(([^)]+)\)/i);
38
+ if (rgbMatch) {
39
+ const parts = rgbMatch[1]
40
+ .split(',')
41
+ .map((part) => part.trim())
42
+ .map((part) => (part.endsWith('%') ? (255 * parseFloat(part)) / 100 : parseFloat(part)));
43
+
44
+ if (parts.length < 3 || parts.some((part) => Number.isNaN(part))) {
45
+ return null;
46
+ }
47
+
48
+ return {
49
+ r: Math.max(0, Math.min(255, parts[0])),
50
+ g: Math.max(0, Math.min(255, parts[1])),
51
+ b: Math.max(0, Math.min(255, parts[2]))
52
+ };
53
+ }
54
+
55
+ return null;
56
+ };
57
+
58
+ const toRelativeLuminance = ({ r, g, b }) => {
59
+ const channel = (value) => {
60
+ const normalized = value / 255;
61
+ return normalized <= 0.04045
62
+ ? normalized / 12.92
63
+ : Math.pow((normalized + 0.055) / 1.055, 2.4);
64
+ };
65
+
66
+ const rLin = channel(r);
67
+ const gLin = channel(g);
68
+ const bLin = channel(b);
69
+
70
+ return 0.2126 * rLin + 0.7152 * gLin + 0.0722 * bLin;
71
+ };
72
+
73
+ const contrastRatio = (a, b) => {
74
+ const l1 = toRelativeLuminance(a);
75
+ const l2 = toRelativeLuminance(b);
76
+
77
+ const lighter = Math.max(l1, l2);
78
+ const darker = Math.min(l1, l2);
79
+
80
+ return (lighter + 0.05) / (darker + 0.05);
81
+ };
82
+
83
+ // Prefers the highest-contrast candidate for each swatch at runtime.
84
+ const chooseReadableTextColor = (colorName, shade, fallback) => {
85
+ if (typeof window === 'undefined' || !window.getComputedStyle) {
86
+ return fallback;
87
+ }
88
+
89
+ const style = window.getComputedStyle(document.documentElement);
90
+ const backgroundValue = style.getPropertyValue(`--color-${colorName}-${shade}`);
91
+ const background = toRgb(backgroundValue);
92
+
93
+ if (!background) {
94
+ return fallback;
95
+ }
96
+
97
+ const candidateSources = [
98
+ '--surface-inverse-text',
99
+ '--surface-text',
100
+ `--color-${colorName}-900`,
101
+ `--color-${colorName}-50`,
102
+ '#ffffff',
103
+ '#000000'
104
+ ];
105
+
106
+ let best = { value: fallback, ratio: 0 };
107
+
108
+ for (const source of candidateSources) {
109
+ let raw = source;
110
+
111
+ if (source.startsWith('--')) {
112
+ raw = style.getPropertyValue(source);
113
+ }
114
+
115
+ const rgb = toRgb(raw);
116
+
117
+ if (!rgb) {
118
+ continue;
119
+ }
120
+
121
+ const ratio = contrastRatio(background, rgb);
122
+
123
+ if (ratio > best.ratio) {
124
+ best = { value: `rgb(${Math.round(rgb.r)}, ${Math.round(rgb.g)}, ${Math.round(rgb.b)})`, ratio };
125
+ }
126
+ }
127
+
128
+ return best.value || fallback;
129
+ };
130
+
3
131
  export default {
4
132
  title: 'Foundations/Colors',
5
133
  parameters: {
134
+ pds: {
135
+ tags: ['colors']
136
+ },
6
137
  docs: {
7
138
  description: {
8
139
  component: 'Design tokens - colors, typography, spacing, icons'
@@ -26,89 +157,123 @@ export default {
26
157
  }
27
158
  };
28
159
 
29
- export const Default = {
30
- render: (args) => {
31
- // Apply preset if changed
32
- if (args.preset) {
33
- import('../../../src/js/pds.js').then(({ PDS }) => {
34
- PDS.applyDesign({ preset: args.preset });
35
- });
160
+ const colorNames = [
161
+ 'primary',
162
+ 'secondary',
163
+ 'accent',
164
+ 'success',
165
+ 'warning',
166
+ 'danger',
167
+ 'info'
168
+ ];
169
+
170
+ const colorShades = [50, 100, 200, 300, 400, 500, 600, 700, 800];
171
+
172
+ const getFallbackTextColor = (color, shade) =>
173
+ shade >= 400
174
+ ? 'var(--surface-inverse-text, #ffffff)'
175
+ : `var(--color-${color}-900)`;
176
+
177
+ const colorScaleStoryStyles = html`
178
+ <style>
179
+ .color-scale-story-container {
180
+ padding: var(--spacing-8);
36
181
  }
37
-
38
- // Apply color overrides
39
- if (args.primaryColor || args.secondaryColor) {
40
- import('../../../src/js/pds.js').then(({ PDS }) => {
41
- PDS.applyDesign({
42
- design: {
43
- colors: {
44
- primary: args.primaryColor,
45
- secondary: args.secondaryColor
46
- }
47
- }
48
- });
49
- });
182
+ .color-scale-container {
183
+ margin-bottom: var(--spacing-8);
50
184
  }
51
-
52
-
53
- const renderColorCard = (name, color) => html`
54
- <div class="color-card">
55
- <div class="color-swatch" style="background-color: var(--color-${color}-500);"></div>
56
- <div class="color-info">
57
- <strong>${name}</strong>
58
- <code>var(--color-${color}-500)</code>
59
- </div>
60
- <div class="color-scale" style="--scale-color: var(--color-${color}-500);">
61
- ${[50, 100, 200, 300, 400, 500, 600, 700, 800, 900].map(
62
- (shade) => html`<div style="--value: ${shade}"></div>`
63
- )}
64
- </div>
65
- </div>
66
- `;
67
-
68
- const renderColorScale = (color) => html`
69
- <div class="color-scale-row">
70
- ${[50, 100, 200, 300, 400, 500, 600, 700, 800, 900].map(shade => html`
71
- <div class="scale-item">
72
- <div class="swatch" style="background-color: var(--color-${color}-${shade})"></div>
73
- <span class="label">${shade}</span>
74
- </div>
75
- `)}
76
- </div>
77
- `;
78
-
185
+ .color-scale-row {
186
+ display: flex;
187
+ align-items: center;
188
+ gap: var(--spacing-4);
189
+ }
190
+ .color-scale-label {
191
+ width: 7.5rem;
192
+ font-weight: 600;
193
+ }
194
+ .color-scale-swatches {
195
+ display: flex;
196
+ flex: 1;
197
+ }
198
+ .color-scale-swatch {
199
+ flex: 1;
200
+ min-height: 3.75rem;
201
+ padding: var(--spacing-4);
202
+ text-align: center;
203
+ transition: transform 0.2s, box-shadow 0.2s;
204
+ cursor: pointer;
205
+ position: relative;
206
+ z-index: 1;
207
+ }
208
+ .color-scale-swatch--hover {
209
+ transform: translateY(-0.25rem);
210
+ z-index: 10;
211
+ box-shadow: var(--shadow-md);
212
+ }
213
+ ${colorNames
214
+ .map((color) =>
215
+ colorShades
216
+ .map((shade) => {
217
+ const textColor = getFallbackTextColor(color, shade);
218
+ return `
219
+ .color-scale-swatch[data-color="${color}"][data-shade="${shade}"] {
220
+ background: var(--color-${color}-${shade});
221
+ color: ${textColor};
222
+ }
223
+ `;
224
+ })
225
+ .join('\n')
226
+ )
227
+ .join('\n')}
228
+ </style>
229
+ `;
79
230
 
80
- return html`
81
- <div class="story-container" style="padding: 2rem;">
82
-
83
- <section class="showcase-section" data-section="color-system">
84
- <h2><pds-icon icon="palette" size="lg" class="icon-primary"></pds-icon> Color System</h2>
85
- <div class="color-grid">
86
- ${renderColorCard("Primary", "primary")}
87
- ${renderColorCard("Secondary", "secondary")}
88
- ${renderColorCard("Accent", "accent")}
89
- ${renderColorCard("Success", "success")}
90
- ${renderColorCard("Warning", "warning")}
91
- ${renderColorCard("Danger", "danger")}
92
- ${renderColorCard("Info", "info")}
93
- </div>
94
-
95
- <h3>Gray Scale (from Secondary)</h3>
96
- <div class="gray-scale-grid">
97
- ${[50, 100, 200, 300, 400, 500, 600, 700, 800].map(
231
+ const handleSwatchHoverEnter = (event) => {
232
+ event.currentTarget.classList.add('color-scale-swatch--hover');
233
+ };
234
+
235
+ const handleSwatchHoverLeave = (event) => {
236
+ event.currentTarget.classList.remove('color-scale-swatch--hover');
237
+ };
238
+
239
+ const renderColorScale = (colorName) => html`
240
+ <div class="color-scale-container">
241
+ <div class="color-scale-row">
242
+ <div class="color-scale-label">${colorName}</div>
243
+ <div class="color-scale-swatches">
244
+ ${colorShades.map(
98
245
  (shade) => html`
99
- <div class="gray-scale-item">
100
- <div
101
- class="gray-scale-swatch"
102
- style="background-color: var(--color-gray-${shade});"
103
- title="gray-${shade}"
104
- ></div>
105
- <div class="gray-scale-label">${shade}</div>
246
+ <div
247
+ class="color-scale-swatch"
248
+ data-color=${colorName}
249
+ data-shade=${shade}
250
+ style=${`color: ${chooseReadableTextColor(colorName, shade, getFallbackTextColor(colorName, shade))}`}
251
+ @mouseover=${handleSwatchHoverEnter}
252
+ @mouseout=${handleSwatchHoverLeave}
253
+ title="${colorName}-${shade}"
254
+ >
255
+ ${shade}
106
256
  </div>
107
257
  `
108
258
  )}
109
259
  </div>
110
- </section>
111
-
260
+ </div>
261
+ </div>
262
+ `;
263
+
264
+ export const Default = {
265
+ render: (args) => {
266
+ return html`
267
+ ${colorScaleStoryStyles}
268
+ <div class="story-container color-scale-story-container">
269
+ <h2>Color Scales</h2>
270
+ ${renderColorScale('primary')}
271
+ ${renderColorScale('secondary')}
272
+ ${renderColorScale('accent')}
273
+ ${renderColorScale('success')}
274
+ ${renderColorScale('warning')}
275
+ ${renderColorScale('danger')}
276
+ ${renderColorScale('info')}
112
277
  </div>
113
278
  `;
114
279
  },