@tenerife.music/ui 1.2.1 → 2.0.1

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.
@@ -0,0 +1,381 @@
1
+ import NextLink from 'next/link';
2
+ import * as React from 'react';
3
+ import { cva } from 'class-variance-authority';
4
+ import { jsxs, jsx } from 'react/jsx-runtime';
5
+
6
+ // src/EXTENSIONS/next/NextLinkAdapter.tsx
7
+ var FORBIDDEN_SPACING_PATTERNS = [
8
+ // Raw color utilities (bg-red-500, text-blue-600, etc.)
9
+ // These are always forbidden as they bypass the color token system
10
+ /\b(bg|text|border|ring|outline)-(red|blue|green|yellow|purple|pink|indigo|gray|slate|zinc|neutral|stone|orange|amber|emerald|teal|cyan|sky|violet|fuchsia|rose)-\d+/,
11
+ // Raw spacing utilities with arbitrary numbers (p-4, m-2, gap-3, etc.)
12
+ // Allow semantic spacing tokens (px-sm, py-md, etc.) which use token names
13
+ // Allow p-0, m-0, etc. as these are standard Tailwind classes for zero spacing
14
+ // Allow fractional values (0.5, 1.5, 2.5, 3.5) as these are standard Tailwind spacing classes used in tokens
15
+ // Note: Standard numeric values (p-4, m-2, etc.) are still flagged to encourage semantic tokens
16
+ /\b(p|m|px|py|pt|pb|pl|pr|mx|my|mt|mb|ml|mr|gap|space-[xy])-((?!0$|0\.5$|1\.5$|2\.5$|3\.5$)\d+(\.\d+)?|\[)/
17
+ ];
18
+ var ADVISORY_DIMENSION_PATTERNS = [
19
+ // Raw size utilities with arbitrary numbers (w-4, h-6, etc.)
20
+ // Allow semantic size tokens (h-8, w-9, etc.) which are standard design system values
21
+ // Only flag arbitrary values like w-[123px] or h-[calc(...)]
22
+ // Allow viewport-relative values (vh, vw, %) and relative units (rem, em) as these are legitimate design system values
23
+ /\b(w|h|min-w|min-h|max-w|max-h)-\[(?!\d+(vh|vw|%|rem|em)\])/
24
+ ];
25
+ function validateTokenUsage(classes, context) {
26
+ if (process.env.NODE_ENV === "production") {
27
+ return;
28
+ }
29
+ for (const pattern of FORBIDDEN_SPACING_PATTERNS) {
30
+ if (pattern.test(classes)) {
31
+ console.error(
32
+ `[tokenCVA] ERROR: Forbidden spacing utility detected in ${context}:
33
+ "${classes}"
34
+ Pattern: ${pattern}
35
+ Spacing utilities MUST use semanticSpacing tokens (e.g., from component tokens).`
36
+ );
37
+ }
38
+ }
39
+ for (const pattern of ADVISORY_DIMENSION_PATTERNS) {
40
+ if (pattern.test(classes)) {
41
+ console.warn(
42
+ `[tokenCVA] WARN: Dimension utility detected in ${context}:
43
+ "${classes}"
44
+ Pattern: ${pattern}
45
+ Dimension utilities are allowed until a dimension token system is introduced.`
46
+ );
47
+ }
48
+ }
49
+ }
50
+ function validateVariantConfig(value, path, visited = /* @__PURE__ */ new Set()) {
51
+ if (value === void 0 || value === null) {
52
+ return;
53
+ }
54
+ const key = `${path}-${String(value)}`;
55
+ if (visited.has(key)) {
56
+ return;
57
+ }
58
+ visited.add(key);
59
+ if (typeof value === "string") {
60
+ validateTokenUsage(value, path);
61
+ } else if (Array.isArray(value)) {
62
+ value.forEach((item, index) => {
63
+ validateVariantConfig(item, `${path}[${index}]`, visited);
64
+ });
65
+ } else if (typeof value === "object") {
66
+ Object.entries(value).forEach(([key2, val]) => {
67
+ validateVariantConfig(val, `${path}.${key2}`, visited);
68
+ });
69
+ }
70
+ }
71
+ function tokenCVA(config) {
72
+ if (process.env.NODE_ENV !== "production") {
73
+ if (config.base) {
74
+ validateVariantConfig(config.base, "base");
75
+ }
76
+ if (config.variants) {
77
+ Object.entries(config.variants).forEach(([variantKey, variantValues]) => {
78
+ Object.entries(variantValues).forEach(([valueKey, value]) => {
79
+ validateVariantConfig(value, `variants.${variantKey}.${valueKey}`);
80
+ });
81
+ });
82
+ }
83
+ if (config.compoundVariants) {
84
+ config.compoundVariants.forEach((compound, index) => {
85
+ if (compound.class) {
86
+ validateVariantConfig(compound.class, `compoundVariants[${index}].class`);
87
+ }
88
+ if (compound.className) {
89
+ validateVariantConfig(compound.className, `compoundVariants[${index}].className`);
90
+ }
91
+ });
92
+ }
93
+ }
94
+ return cva(config.base, {
95
+ variants: config.variants,
96
+ compoundVariants: config.compoundVariants,
97
+ defaultVariants: config.defaultVariants
98
+ });
99
+ }
100
+
101
+ // src/FOUNDATION/tokens/components/motion.ts
102
+ var MOTION_TOKENS = {
103
+ /**
104
+ * Pre-configured transition tokens
105
+ * Combines duration and easing for common use cases
106
+ */
107
+ transitionPreset: {
108
+ // Slow transition
109
+ colors: "transition-colors duration-normal ease-out"}};
110
+
111
+ // src/FOUNDATION/tokens/components/link.ts
112
+ var LINK_TOKENS = {
113
+ /**
114
+ * Link heights by size
115
+ * Maps to Tailwind height utilities
116
+ */
117
+ height: {
118
+ // 28px (1.75rem)
119
+ sm: "h-8",
120
+ // 32px (2rem)
121
+ md: "h-9",
122
+ // 36px (2.25rem)
123
+ lg: "h-10"},
124
+ /**
125
+ * Link padding by size
126
+ * Horizontal and vertical padding values
127
+ */
128
+ padding: {
129
+ horizontal: {
130
+ // 4px (0.25rem) - maps to semanticSpacing.xs
131
+ sm: "px-sm",
132
+ // 8px (0.5rem) - maps to semanticSpacing.sm
133
+ md: "px-md",
134
+ // 16px (1rem) - maps to semanticSpacing.md
135
+ lg: "px-lg"},
136
+ vertical: {
137
+ xs: "py-xs",
138
+ // 4px (0.25rem) - maps to semanticSpacing.xs
139
+ sm: "py-sm"}
140
+ },
141
+ /**
142
+ * Layout tokens
143
+ * Base layout utilities for link component
144
+ */
145
+ layout: "inline-flex items-center justify-center whitespace-nowrap",
146
+ // Base layout for link container
147
+ /**
148
+ * Font weight token
149
+ * References foundation typography fontWeight tokens from Typography Authority
150
+ *
151
+ * @rule References fontWeight.medium (500) from Typography Authority
152
+ * @see docs/architecture/TYPOGRAPHY_AUTHORITY_CONTRACT.md
153
+ */
154
+ fontWeight: "font-medium",
155
+ // References fontWeight.medium (500) - Typography Authority compliant
156
+ /**
157
+ * Icon wrapper layout
158
+ * Layout utilities for icon containers
159
+ */
160
+ iconWrapper: "inline-flex items-center",
161
+ // Layout for left/right icon wrappers
162
+ /**
163
+ * Font sizes by link size
164
+ * References foundation typography fontSize tokens from Typography Authority
165
+ *
166
+ * @rule All fontSize values reference Typography Authority tokens
167
+ * @see docs/architecture/TYPOGRAPHY_AUTHORITY_CONTRACT.md
168
+ */
169
+ fontSize: {
170
+ // References fontSize.xs[0] from Typography Authority (~12px)
171
+ sm: "text-xs",
172
+ // References fontSize.xs[0] from Typography Authority (~12px)
173
+ md: "text-sm",
174
+ // References fontSize.sm[0] from Typography Authority (~14px)
175
+ lg: "text-sm"},
176
+ /**
177
+ * Border radius for outline and ghost variants
178
+ * References componentRadius from Radius Authority
179
+ *
180
+ * @rule References borderRadius.md (6px / 0.375rem) from Radius Authority
181
+ * @see docs/architecture/RADIUS_AUTHORITY_CONTRACT.md
182
+ */
183
+ radius: "rounded-md",
184
+ // References borderRadius.md (6px / 0.375rem) - Radius Authority compliant
185
+ /**
186
+ * Underline offset for text decoration
187
+ * Uses spacing token (xs = 4px) which matches underline-offset-4
188
+ */
189
+ underlineOffset: "underline-offset-4",
190
+ // 4px (0.25rem) - matches semanticSpacing.xs
191
+ /**
192
+ * Transition tokens
193
+ * References Motion Authority tokens for consistent motion behavior
194
+ *
195
+ * @rule Uses MOTION_TOKENS.transitionPreset.colors from Motion Authority
196
+ * @rule Motion transitions MUST use canonical motion tokens only
197
+ * @see docs/architecture/MOTION_AUTHORITY_CONTRACT.md
198
+ */
199
+ transition: {
200
+ colors: MOTION_TOKENS.transitionPreset.colors
201
+ // "transition-colors duration-normal ease-out" - Motion Authority compliant
202
+ },
203
+ /**
204
+ * Focus state tokens
205
+ * Focus ring for keyboard navigation
206
+ *
207
+ * @rule Focus MUST use focus-visible: prefix (keyboard navigation only)
208
+ * @rule Focus MUST be blocked when disabled={true}
209
+ */
210
+ focus: {
211
+ ring: "focus-visible:ring-2 focus-visible:ring-ring",
212
+ // Focus ring using semantic ring color
213
+ outline: "focus-visible:outline-none",
214
+ // Remove default outline (replaced by ring)
215
+ offset: "focus-visible:ring-offset-2"
216
+ // Ring offset
217
+ },
218
+ /**
219
+ * Disabled state tokens
220
+ */
221
+ state: {
222
+ disabled: {
223
+ pointerEvents: "disabled:pointer-events-none",
224
+ // Disable pointer events
225
+ opacity: "disabled:opacity-50"
226
+ // Disabled opacity
227
+ }
228
+ },
229
+ /**
230
+ * Color tokens for link variants
231
+ * Uses semantic color tokens that map to CSS variables
232
+ */
233
+ variant: {
234
+ primary: {
235
+ text: "text-primary",
236
+ // Primary text using CSS var
237
+ hover: "hover:text-primary/80",
238
+ // Primary hover text
239
+ underline: "hover:underline"
240
+ // Underline on hover
241
+ },
242
+ secondary: {
243
+ text: "text-secondary",
244
+ // Secondary text using CSS var
245
+ hover: "hover:underline"
246
+ // Underline on hover
247
+ },
248
+ accent: {
249
+ text: "text-accent",
250
+ // Accent text using CSS var (accent color, not accent-foreground)
251
+ hover: "hover:text-accent/80",
252
+ // Accent hover text
253
+ underline: "hover:underline"
254
+ // Underline on hover
255
+ },
256
+ outline: {
257
+ border: "border border-input",
258
+ // Input border using CSS var
259
+ background: "bg-background",
260
+ // Background using CSS var
261
+ text: "text-foreground",
262
+ // Foreground text using CSS var
263
+ hover: {
264
+ background: "hover:bg-accent",
265
+ // Hover background
266
+ text: "hover:text-accent-foreground"
267
+ // Hover text
268
+ }
269
+ },
270
+ ghost: {
271
+ text: "text-foreground",
272
+ // Foreground text using CSS var
273
+ hover: {
274
+ background: "hover:bg-accent",
275
+ // Hover background
276
+ text: "hover:text-accent-foreground"
277
+ // Hover text
278
+ }
279
+ },
280
+ link: {
281
+ text: "text-primary",
282
+ // Primary text using CSS var
283
+ hover: "hover:underline"
284
+ // Underline on hover
285
+ },
286
+ destructive: {
287
+ text: "text-destructive",
288
+ // Destructive text using CSS var
289
+ hover: "hover:text-destructive/80",
290
+ // Destructive hover text
291
+ underline: "hover:underline"
292
+ // Underline on hover
293
+ }
294
+ }
295
+ };
296
+ var ICON_WRAPPER_CLASS = LINK_TOKENS.iconWrapper;
297
+ function renderIcon(icon) {
298
+ if (!icon) return null;
299
+ return /* @__PURE__ */ jsx("span", { className: ICON_WRAPPER_CLASS, children: icon });
300
+ }
301
+ var linkVariants = tokenCVA({
302
+ base: `${LINK_TOKENS.layout} ${LINK_TOKENS.fontWeight} ${LINK_TOKENS.transition.colors} ${LINK_TOKENS.focus.outline} ${LINK_TOKENS.focus.ring} ${LINK_TOKENS.focus.offset} ${LINK_TOKENS.state.disabled.pointerEvents} ${LINK_TOKENS.state.disabled.opacity}`,
303
+ variants: {
304
+ variant: {
305
+ primary: `${LINK_TOKENS.variant.primary.text} ${LINK_TOKENS.variant.primary.hover} ${LINK_TOKENS.underlineOffset} ${LINK_TOKENS.variant.primary.underline}`,
306
+ secondary: `${LINK_TOKENS.variant.secondary.text} ${LINK_TOKENS.underlineOffset} ${LINK_TOKENS.variant.secondary.hover}`,
307
+ accent: `${LINK_TOKENS.variant.accent.text} ${LINK_TOKENS.variant.accent.hover} ${LINK_TOKENS.underlineOffset} ${LINK_TOKENS.variant.accent.underline}`,
308
+ outline: `${LINK_TOKENS.variant.outline.border} ${LINK_TOKENS.variant.outline.background} ${LINK_TOKENS.variant.outline.text} ${LINK_TOKENS.radius} ${LINK_TOKENS.variant.outline.hover.background} ${LINK_TOKENS.variant.outline.hover.text}`,
309
+ ghost: `${LINK_TOKENS.variant.ghost.text} ${LINK_TOKENS.variant.ghost.hover.background} ${LINK_TOKENS.variant.ghost.hover.text} ${LINK_TOKENS.radius}`,
310
+ link: `${LINK_TOKENS.variant.link.text} ${LINK_TOKENS.underlineOffset} ${LINK_TOKENS.variant.link.hover}`,
311
+ destructive: `${LINK_TOKENS.variant.destructive.text} ${LINK_TOKENS.variant.destructive.hover} ${LINK_TOKENS.underlineOffset} ${LINK_TOKENS.variant.destructive.underline}`
312
+ },
313
+ size: {
314
+ sm: `${LINK_TOKENS.height.sm} ${LINK_TOKENS.fontSize.sm} ${LINK_TOKENS.padding.horizontal.sm} ${LINK_TOKENS.padding.vertical.xs}`,
315
+ md: `${LINK_TOKENS.height.md} ${LINK_TOKENS.fontSize.md} ${LINK_TOKENS.padding.horizontal.md} ${LINK_TOKENS.padding.vertical.sm}`,
316
+ lg: `${LINK_TOKENS.height.lg} ${LINK_TOKENS.fontSize.lg} ${LINK_TOKENS.padding.horizontal.lg} ${LINK_TOKENS.padding.vertical.sm}`
317
+ }
318
+ },
319
+ defaultVariants: {
320
+ variant: "link",
321
+ size: "md"
322
+ }
323
+ });
324
+ var Link = React.forwardRef(
325
+ ({ variant, size, leftIcon, rightIcon, children, disabled, onClick, href, tabIndex, ...props }, ref) => {
326
+ const handleClick = React.useCallback(
327
+ (e) => {
328
+ if (disabled) {
329
+ e.preventDefault();
330
+ e.stopPropagation();
331
+ return;
332
+ }
333
+ onClick?.(e);
334
+ },
335
+ [disabled, onClick]
336
+ );
337
+ const finalClassName = linkVariants({ variant, size });
338
+ const finalTabIndex = disabled ? tabIndex ?? -1 : tabIndex;
339
+ const finalAriaDisabled = disabled ? true : void 0;
340
+ return /* @__PURE__ */ jsxs(
341
+ "a",
342
+ {
343
+ className: finalClassName,
344
+ ref,
345
+ href,
346
+ tabIndex: finalTabIndex,
347
+ "aria-disabled": finalAriaDisabled,
348
+ onClick: handleClick,
349
+ ...props,
350
+ children: [
351
+ renderIcon(leftIcon),
352
+ children,
353
+ renderIcon(rightIcon)
354
+ ]
355
+ }
356
+ );
357
+ }
358
+ );
359
+ Link.displayName = "Link";
360
+ var NextLinkAdapter = React.forwardRef(
361
+ ({ href, prefetch, replace, scroll, shallow, locale, ...props }, ref) => {
362
+ const hrefString = typeof href === "string" ? href : href.pathname || String(href);
363
+ return /* @__PURE__ */ jsx(
364
+ NextLink,
365
+ {
366
+ href,
367
+ prefetch,
368
+ replace,
369
+ scroll,
370
+ shallow,
371
+ locale,
372
+ passHref: true,
373
+ legacyBehavior: true,
374
+ children: /* @__PURE__ */ jsx(Link, { ref, href: hrefString, ...props })
375
+ }
376
+ );
377
+ }
378
+ );
379
+ NextLinkAdapter.displayName = "NextLinkAdapter";
380
+
381
+ export { NextLinkAdapter };