@tenerife.music/ui 1.2.0 → 2.0.0
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 +39 -2
- package/dist/Link-ZWr5iFB0.d.cts +60 -0
- package/dist/Link-ZWr5iFB0.d.ts +60 -0
- package/dist/extensions/next/index.cjs +406 -0
- package/dist/extensions/next/index.d.cts +37 -0
- package/dist/extensions/next/index.d.ts +37 -0
- package/dist/extensions/next/index.mjs +381 -0
- package/dist/{index-DXmHg8ji.d.cts → index-DGtRM9Db.d.cts} +193 -901
- package/dist/{index-DXmHg8ji.d.ts → index-DGtRM9Db.d.ts} +193 -901
- package/dist/index.cjs +3376 -2793
- package/dist/index.d.cts +1670 -722
- package/dist/index.d.ts +1670 -722
- package/dist/index.mjs +3336 -2765
- package/dist/preset.cjs +129 -321
- package/dist/preset.mjs +129 -321
- package/dist/styles.css +417 -101
- package/dist/theme/index.cjs +20 -230
- package/dist/theme/index.mjs +20 -230
- package/dist/tokens/index.cjs +502 -593
- package/dist/tokens/index.d.cts +165 -2
- package/dist/tokens/index.d.ts +165 -2
- package/dist/tokens/index.mjs +490 -577
- package/package.json +24 -8
package/README.md
CHANGED
|
@@ -123,6 +123,19 @@ export default function App() {
|
|
|
123
123
|
| **Final Foundation Lock** | Authoritative source of truth |
|
|
124
124
|
| Storybook | Component examples and contracts |
|
|
125
125
|
|
|
126
|
+
### Development Resources
|
|
127
|
+
|
|
128
|
+
- **Component Creation**: [Extension Component Creation Checklist](docs/workflows/tasks/COMPONENT_CREATION_CHECKLIST.md)
|
|
129
|
+
- **CLI Generator**: Use `pnpm run component:generate -- <ComponentName> [--category <category>]` to generate component scaffold
|
|
130
|
+
- See checklist for complete process and requirements
|
|
131
|
+
- **Component Refactoring**: [Component Review & Improvement Pipeline (18A)](docs/workflows/foundation/FOUNDATION_STEP_PIPELINE.md)
|
|
132
|
+
- **Canonical process** for reviewing, improving, and validating existing components
|
|
133
|
+
- Mandatory 12-step pipeline (STEP 0-11) for Foundation and Extension components
|
|
134
|
+
- See pipeline for complete refactoring process and requirements
|
|
135
|
+
- **Component Examples**: [Extension Component Examples](docs/reference/EXTENSION_COMPONENT_EXAMPLES.md)
|
|
136
|
+
- **Component Needs**: [Component Needs Inventory](docs/workflows/tasks/COMPONENT_NEEDS_INVENTORY.md)
|
|
137
|
+
- **Feedback Process**: [Usage Feedback Process](docs/workflows/tasks/FEEDBACK_COLLECTION_PROCESS.md)
|
|
138
|
+
|
|
126
139
|
---
|
|
127
140
|
|
|
128
141
|
## 🏗 Architecture Overview
|
|
@@ -137,10 +150,11 @@ There is exactly **one Foundation component per category**.
|
|
|
137
150
|
- Select
|
|
138
151
|
- ContextMenu
|
|
139
152
|
- Toast
|
|
153
|
+
- Button (**FINAL LOCK**)
|
|
140
154
|
|
|
141
155
|
All Foundation components:
|
|
142
156
|
|
|
143
|
-
- delegate behavior to Radix UI
|
|
157
|
+
- delegate behavior to Radix UI (or native HTML elements)
|
|
144
158
|
- expose token-driven visual APIs
|
|
145
159
|
- are backward-compatible and locked
|
|
146
160
|
|
|
@@ -151,7 +165,6 @@ that rely strictly on tokens and shared semantics.
|
|
|
151
165
|
|
|
152
166
|
Examples:
|
|
153
167
|
|
|
154
|
-
- Button
|
|
155
168
|
- Input / Textarea
|
|
156
169
|
- Card / Badge
|
|
157
170
|
- Layout primitives (Stack, Grid, Container)
|
|
@@ -199,6 +212,30 @@ Contributions are welcome **within the boundaries of the system**.
|
|
|
199
212
|
|
|
200
213
|
Architectural discussions take precedence over visual changes.
|
|
201
214
|
|
|
215
|
+
### Creating New Components
|
|
216
|
+
|
|
217
|
+
To create a new Extension component:
|
|
218
|
+
|
|
219
|
+
1. **Check Component Needs**: Review [Component Needs Inventory](docs/tasks/COMPONENT_NEEDS_INVENTORY.md) to ensure the component is needed
|
|
220
|
+
2. **Use Template Generator**: Run `tsx scripts/generate-extension-component.ts <ComponentName> --category <category>`
|
|
221
|
+
3. **Follow Checklist**: Complete all items in [Extension Component Creation Checklist](docs/tasks/EXTENSION_COMPONENT_CREATION_CHECKLIST.md)
|
|
222
|
+
4. **Reference Examples**: Use [Extension Component Examples](docs/reference/EXTENSION_COMPONENT_EXAMPLES.md) as patterns
|
|
223
|
+
|
|
224
|
+
### Requesting Components
|
|
225
|
+
|
|
226
|
+
To request a new component:
|
|
227
|
+
|
|
228
|
+
1. **Create GitHub Issue**: Use the [Component Request template](.github/ISSUE_TEMPLATE/component-request.md)
|
|
229
|
+
2. **Provide Use Case**: Describe the specific use case and frequency of need
|
|
230
|
+
3. **Document Workaround**: Explain current solution and pain points
|
|
231
|
+
4. **Wait for Review**: Requests are reviewed monthly (see [Feedback Review Process](docs/tasks/FEEDBACK_REVIEW_PROCESS.md))
|
|
232
|
+
|
|
233
|
+
### Development Tools
|
|
234
|
+
|
|
235
|
+
- **Component Analysis**: `tsx scripts/analyze-component-needs.ts` - Analyzes codebase for component patterns
|
|
236
|
+
- **Feedback Collection**: `tsx scripts/collect-usage-feedback.ts` - Collects and analyzes usage feedback
|
|
237
|
+
- **Component Generator**: `tsx scripts/generate-extension-component.ts <Name> --category <category>` - Generates component scaffold
|
|
238
|
+
|
|
202
239
|
---
|
|
203
240
|
|
|
204
241
|
## 📜 License
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import * as class_variance_authority_types from 'class-variance-authority/types';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Link variant values (internal - used for type derivation only)
|
|
6
|
+
*
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
declare const _LINK_VARIANTS: readonly ["primary", "secondary", "accent", "outline", "ghost", "link", "destructive"];
|
|
10
|
+
/**
|
|
11
|
+
* Link variant type
|
|
12
|
+
*
|
|
13
|
+
* @public
|
|
14
|
+
*/
|
|
15
|
+
type LinkVariant = (typeof _LINK_VARIANTS)[number];
|
|
16
|
+
/**
|
|
17
|
+
* Link size values (internal - used for type derivation only)
|
|
18
|
+
*
|
|
19
|
+
* @internal
|
|
20
|
+
*/
|
|
21
|
+
declare const _LINK_SIZES: readonly ["sm", "md", "lg"];
|
|
22
|
+
/**
|
|
23
|
+
* Link size type
|
|
24
|
+
*
|
|
25
|
+
* @public
|
|
26
|
+
*/
|
|
27
|
+
type LinkSize = (typeof _LINK_SIZES)[number];
|
|
28
|
+
declare const linkVariants: (props?: ({
|
|
29
|
+
variant?: "primary" | "secondary" | "accent" | "destructive" | "outline" | "link" | "ghost" | null | undefined;
|
|
30
|
+
size?: "sm" | "md" | "lg" | null | undefined;
|
|
31
|
+
} & class_variance_authority_types.ClassProp) | undefined) => string;
|
|
32
|
+
interface LinkProps extends Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, "className" | "style"> {
|
|
33
|
+
/**
|
|
34
|
+
* Link variant style
|
|
35
|
+
* @default "link"
|
|
36
|
+
*/
|
|
37
|
+
variant?: LinkVariant;
|
|
38
|
+
/**
|
|
39
|
+
* Link size
|
|
40
|
+
* @default "md"
|
|
41
|
+
*/
|
|
42
|
+
size?: LinkSize;
|
|
43
|
+
/**
|
|
44
|
+
* Icon to display on the left side
|
|
45
|
+
*/
|
|
46
|
+
leftIcon?: React.ReactNode;
|
|
47
|
+
/**
|
|
48
|
+
* Icon to display on the right side
|
|
49
|
+
*/
|
|
50
|
+
rightIcon?: React.ReactNode;
|
|
51
|
+
/**
|
|
52
|
+
* Whether the link is disabled
|
|
53
|
+
* When disabled, the link will not be navigable and will be removed from the tab order
|
|
54
|
+
* @default false
|
|
55
|
+
*/
|
|
56
|
+
disabled?: boolean;
|
|
57
|
+
}
|
|
58
|
+
declare const Link: React.ForwardRefExoticComponent<LinkProps & React.RefAttributes<HTMLAnchorElement>>;
|
|
59
|
+
|
|
60
|
+
export { type LinkProps as L, Link as a, type LinkSize as b, type LinkVariant as c, linkVariants as l };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import * as class_variance_authority_types from 'class-variance-authority/types';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Link variant values (internal - used for type derivation only)
|
|
6
|
+
*
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
declare const _LINK_VARIANTS: readonly ["primary", "secondary", "accent", "outline", "ghost", "link", "destructive"];
|
|
10
|
+
/**
|
|
11
|
+
* Link variant type
|
|
12
|
+
*
|
|
13
|
+
* @public
|
|
14
|
+
*/
|
|
15
|
+
type LinkVariant = (typeof _LINK_VARIANTS)[number];
|
|
16
|
+
/**
|
|
17
|
+
* Link size values (internal - used for type derivation only)
|
|
18
|
+
*
|
|
19
|
+
* @internal
|
|
20
|
+
*/
|
|
21
|
+
declare const _LINK_SIZES: readonly ["sm", "md", "lg"];
|
|
22
|
+
/**
|
|
23
|
+
* Link size type
|
|
24
|
+
*
|
|
25
|
+
* @public
|
|
26
|
+
*/
|
|
27
|
+
type LinkSize = (typeof _LINK_SIZES)[number];
|
|
28
|
+
declare const linkVariants: (props?: ({
|
|
29
|
+
variant?: "primary" | "secondary" | "accent" | "destructive" | "outline" | "link" | "ghost" | null | undefined;
|
|
30
|
+
size?: "sm" | "md" | "lg" | null | undefined;
|
|
31
|
+
} & class_variance_authority_types.ClassProp) | undefined) => string;
|
|
32
|
+
interface LinkProps extends Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, "className" | "style"> {
|
|
33
|
+
/**
|
|
34
|
+
* Link variant style
|
|
35
|
+
* @default "link"
|
|
36
|
+
*/
|
|
37
|
+
variant?: LinkVariant;
|
|
38
|
+
/**
|
|
39
|
+
* Link size
|
|
40
|
+
* @default "md"
|
|
41
|
+
*/
|
|
42
|
+
size?: LinkSize;
|
|
43
|
+
/**
|
|
44
|
+
* Icon to display on the left side
|
|
45
|
+
*/
|
|
46
|
+
leftIcon?: React.ReactNode;
|
|
47
|
+
/**
|
|
48
|
+
* Icon to display on the right side
|
|
49
|
+
*/
|
|
50
|
+
rightIcon?: React.ReactNode;
|
|
51
|
+
/**
|
|
52
|
+
* Whether the link is disabled
|
|
53
|
+
* When disabled, the link will not be navigable and will be removed from the tab order
|
|
54
|
+
* @default false
|
|
55
|
+
*/
|
|
56
|
+
disabled?: boolean;
|
|
57
|
+
}
|
|
58
|
+
declare const Link: React.ForwardRefExoticComponent<LinkProps & React.RefAttributes<HTMLAnchorElement>>;
|
|
59
|
+
|
|
60
|
+
export { type LinkProps as L, Link as a, type LinkSize as b, type LinkVariant as c, linkVariants as l };
|
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var NextLink = require('next/link');
|
|
4
|
+
var React = require('react');
|
|
5
|
+
var classVarianceAuthority = require('class-variance-authority');
|
|
6
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
7
|
+
|
|
8
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
9
|
+
|
|
10
|
+
function _interopNamespace(e) {
|
|
11
|
+
if (e && e.__esModule) return e;
|
|
12
|
+
var n = Object.create(null);
|
|
13
|
+
if (e) {
|
|
14
|
+
Object.keys(e).forEach(function (k) {
|
|
15
|
+
if (k !== 'default') {
|
|
16
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
17
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
18
|
+
enumerable: true,
|
|
19
|
+
get: function () { return e[k]; }
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
n.default = e;
|
|
25
|
+
return Object.freeze(n);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
var NextLink__default = /*#__PURE__*/_interopDefault(NextLink);
|
|
29
|
+
var React__namespace = /*#__PURE__*/_interopNamespace(React);
|
|
30
|
+
|
|
31
|
+
// src/EXTENSIONS/next/NextLinkAdapter.tsx
|
|
32
|
+
var FORBIDDEN_SPACING_PATTERNS = [
|
|
33
|
+
// Raw color utilities (bg-red-500, text-blue-600, etc.)
|
|
34
|
+
// These are always forbidden as they bypass the color token system
|
|
35
|
+
/\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+/,
|
|
36
|
+
// Raw spacing utilities with arbitrary numbers (p-4, m-2, gap-3, etc.)
|
|
37
|
+
// Allow semantic spacing tokens (px-sm, py-md, etc.) which use token names
|
|
38
|
+
// Allow p-0, m-0, etc. as these are standard Tailwind classes for zero spacing
|
|
39
|
+
// Allow fractional values (0.5, 1.5, 2.5, 3.5) as these are standard Tailwind spacing classes used in tokens
|
|
40
|
+
// Note: Standard numeric values (p-4, m-2, etc.) are still flagged to encourage semantic tokens
|
|
41
|
+
/\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+)?|\[)/
|
|
42
|
+
];
|
|
43
|
+
var ADVISORY_DIMENSION_PATTERNS = [
|
|
44
|
+
// Raw size utilities with arbitrary numbers (w-4, h-6, etc.)
|
|
45
|
+
// Allow semantic size tokens (h-8, w-9, etc.) which are standard design system values
|
|
46
|
+
// Only flag arbitrary values like w-[123px] or h-[calc(...)]
|
|
47
|
+
// Allow viewport-relative values (vh, vw, %) and relative units (rem, em) as these are legitimate design system values
|
|
48
|
+
/\b(w|h|min-w|min-h|max-w|max-h)-\[(?!\d+(vh|vw|%|rem|em)\])/
|
|
49
|
+
];
|
|
50
|
+
function validateTokenUsage(classes, context) {
|
|
51
|
+
if (process.env.NODE_ENV === "production") {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
for (const pattern of FORBIDDEN_SPACING_PATTERNS) {
|
|
55
|
+
if (pattern.test(classes)) {
|
|
56
|
+
console.error(
|
|
57
|
+
`[tokenCVA] ERROR: Forbidden spacing utility detected in ${context}:
|
|
58
|
+
"${classes}"
|
|
59
|
+
Pattern: ${pattern}
|
|
60
|
+
Spacing utilities MUST use semanticSpacing tokens (e.g., from component tokens).`
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
for (const pattern of ADVISORY_DIMENSION_PATTERNS) {
|
|
65
|
+
if (pattern.test(classes)) {
|
|
66
|
+
console.warn(
|
|
67
|
+
`[tokenCVA] WARN: Dimension utility detected in ${context}:
|
|
68
|
+
"${classes}"
|
|
69
|
+
Pattern: ${pattern}
|
|
70
|
+
Dimension utilities are allowed until a dimension token system is introduced.`
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function validateVariantConfig(value, path, visited = /* @__PURE__ */ new Set()) {
|
|
76
|
+
if (value === void 0 || value === null) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const key = `${path}-${String(value)}`;
|
|
80
|
+
if (visited.has(key)) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
visited.add(key);
|
|
84
|
+
if (typeof value === "string") {
|
|
85
|
+
validateTokenUsage(value, path);
|
|
86
|
+
} else if (Array.isArray(value)) {
|
|
87
|
+
value.forEach((item, index) => {
|
|
88
|
+
validateVariantConfig(item, `${path}[${index}]`, visited);
|
|
89
|
+
});
|
|
90
|
+
} else if (typeof value === "object") {
|
|
91
|
+
Object.entries(value).forEach(([key2, val]) => {
|
|
92
|
+
validateVariantConfig(val, `${path}.${key2}`, visited);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function tokenCVA(config) {
|
|
97
|
+
if (process.env.NODE_ENV !== "production") {
|
|
98
|
+
if (config.base) {
|
|
99
|
+
validateVariantConfig(config.base, "base");
|
|
100
|
+
}
|
|
101
|
+
if (config.variants) {
|
|
102
|
+
Object.entries(config.variants).forEach(([variantKey, variantValues]) => {
|
|
103
|
+
Object.entries(variantValues).forEach(([valueKey, value]) => {
|
|
104
|
+
validateVariantConfig(value, `variants.${variantKey}.${valueKey}`);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
if (config.compoundVariants) {
|
|
109
|
+
config.compoundVariants.forEach((compound, index) => {
|
|
110
|
+
if (compound.class) {
|
|
111
|
+
validateVariantConfig(compound.class, `compoundVariants[${index}].class`);
|
|
112
|
+
}
|
|
113
|
+
if (compound.className) {
|
|
114
|
+
validateVariantConfig(compound.className, `compoundVariants[${index}].className`);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return classVarianceAuthority.cva(config.base, {
|
|
120
|
+
variants: config.variants,
|
|
121
|
+
compoundVariants: config.compoundVariants,
|
|
122
|
+
defaultVariants: config.defaultVariants
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// src/FOUNDATION/tokens/components/motion.ts
|
|
127
|
+
var MOTION_TOKENS = {
|
|
128
|
+
/**
|
|
129
|
+
* Pre-configured transition tokens
|
|
130
|
+
* Combines duration and easing for common use cases
|
|
131
|
+
*/
|
|
132
|
+
transitionPreset: {
|
|
133
|
+
// Slow transition
|
|
134
|
+
colors: "transition-colors duration-normal ease-out"}};
|
|
135
|
+
|
|
136
|
+
// src/FOUNDATION/tokens/components/link.ts
|
|
137
|
+
var LINK_TOKENS = {
|
|
138
|
+
/**
|
|
139
|
+
* Link heights by size
|
|
140
|
+
* Maps to Tailwind height utilities
|
|
141
|
+
*/
|
|
142
|
+
height: {
|
|
143
|
+
// 28px (1.75rem)
|
|
144
|
+
sm: "h-8",
|
|
145
|
+
// 32px (2rem)
|
|
146
|
+
md: "h-9",
|
|
147
|
+
// 36px (2.25rem)
|
|
148
|
+
lg: "h-10"},
|
|
149
|
+
/**
|
|
150
|
+
* Link padding by size
|
|
151
|
+
* Horizontal and vertical padding values
|
|
152
|
+
*/
|
|
153
|
+
padding: {
|
|
154
|
+
horizontal: {
|
|
155
|
+
// 4px (0.25rem) - maps to semanticSpacing.xs
|
|
156
|
+
sm: "px-sm",
|
|
157
|
+
// 8px (0.5rem) - maps to semanticSpacing.sm
|
|
158
|
+
md: "px-md",
|
|
159
|
+
// 16px (1rem) - maps to semanticSpacing.md
|
|
160
|
+
lg: "px-lg"},
|
|
161
|
+
vertical: {
|
|
162
|
+
xs: "py-xs",
|
|
163
|
+
// 4px (0.25rem) - maps to semanticSpacing.xs
|
|
164
|
+
sm: "py-sm"}
|
|
165
|
+
},
|
|
166
|
+
/**
|
|
167
|
+
* Layout tokens
|
|
168
|
+
* Base layout utilities for link component
|
|
169
|
+
*/
|
|
170
|
+
layout: "inline-flex items-center justify-center whitespace-nowrap",
|
|
171
|
+
// Base layout for link container
|
|
172
|
+
/**
|
|
173
|
+
* Font weight token
|
|
174
|
+
* References foundation typography fontWeight tokens from Typography Authority
|
|
175
|
+
*
|
|
176
|
+
* @rule References fontWeight.medium (500) from Typography Authority
|
|
177
|
+
* @see docs/architecture/TYPOGRAPHY_AUTHORITY_CONTRACT.md
|
|
178
|
+
*/
|
|
179
|
+
fontWeight: "font-medium",
|
|
180
|
+
// References fontWeight.medium (500) - Typography Authority compliant
|
|
181
|
+
/**
|
|
182
|
+
* Icon wrapper layout
|
|
183
|
+
* Layout utilities for icon containers
|
|
184
|
+
*/
|
|
185
|
+
iconWrapper: "inline-flex items-center",
|
|
186
|
+
// Layout for left/right icon wrappers
|
|
187
|
+
/**
|
|
188
|
+
* Font sizes by link size
|
|
189
|
+
* References foundation typography fontSize tokens from Typography Authority
|
|
190
|
+
*
|
|
191
|
+
* @rule All fontSize values reference Typography Authority tokens
|
|
192
|
+
* @see docs/architecture/TYPOGRAPHY_AUTHORITY_CONTRACT.md
|
|
193
|
+
*/
|
|
194
|
+
fontSize: {
|
|
195
|
+
// References fontSize.xs[0] from Typography Authority (~12px)
|
|
196
|
+
sm: "text-xs",
|
|
197
|
+
// References fontSize.xs[0] from Typography Authority (~12px)
|
|
198
|
+
md: "text-sm",
|
|
199
|
+
// References fontSize.sm[0] from Typography Authority (~14px)
|
|
200
|
+
lg: "text-sm"},
|
|
201
|
+
/**
|
|
202
|
+
* Border radius for outline and ghost variants
|
|
203
|
+
* References componentRadius from Radius Authority
|
|
204
|
+
*
|
|
205
|
+
* @rule References borderRadius.md (6px / 0.375rem) from Radius Authority
|
|
206
|
+
* @see docs/architecture/RADIUS_AUTHORITY_CONTRACT.md
|
|
207
|
+
*/
|
|
208
|
+
radius: "rounded-md",
|
|
209
|
+
// References borderRadius.md (6px / 0.375rem) - Radius Authority compliant
|
|
210
|
+
/**
|
|
211
|
+
* Underline offset for text decoration
|
|
212
|
+
* Uses spacing token (xs = 4px) which matches underline-offset-4
|
|
213
|
+
*/
|
|
214
|
+
underlineOffset: "underline-offset-4",
|
|
215
|
+
// 4px (0.25rem) - matches semanticSpacing.xs
|
|
216
|
+
/**
|
|
217
|
+
* Transition tokens
|
|
218
|
+
* References Motion Authority tokens for consistent motion behavior
|
|
219
|
+
*
|
|
220
|
+
* @rule Uses MOTION_TOKENS.transitionPreset.colors from Motion Authority
|
|
221
|
+
* @rule Motion transitions MUST use canonical motion tokens only
|
|
222
|
+
* @see docs/architecture/MOTION_AUTHORITY_CONTRACT.md
|
|
223
|
+
*/
|
|
224
|
+
transition: {
|
|
225
|
+
colors: MOTION_TOKENS.transitionPreset.colors
|
|
226
|
+
// "transition-colors duration-normal ease-out" - Motion Authority compliant
|
|
227
|
+
},
|
|
228
|
+
/**
|
|
229
|
+
* Focus state tokens
|
|
230
|
+
* Focus ring for keyboard navigation
|
|
231
|
+
*
|
|
232
|
+
* @rule Focus MUST use focus-visible: prefix (keyboard navigation only)
|
|
233
|
+
* @rule Focus MUST be blocked when disabled={true}
|
|
234
|
+
*/
|
|
235
|
+
focus: {
|
|
236
|
+
ring: "focus-visible:ring-2 focus-visible:ring-ring",
|
|
237
|
+
// Focus ring using semantic ring color
|
|
238
|
+
outline: "focus-visible:outline-none",
|
|
239
|
+
// Remove default outline (replaced by ring)
|
|
240
|
+
offset: "focus-visible:ring-offset-2"
|
|
241
|
+
// Ring offset
|
|
242
|
+
},
|
|
243
|
+
/**
|
|
244
|
+
* Disabled state tokens
|
|
245
|
+
*/
|
|
246
|
+
state: {
|
|
247
|
+
disabled: {
|
|
248
|
+
pointerEvents: "disabled:pointer-events-none",
|
|
249
|
+
// Disable pointer events
|
|
250
|
+
opacity: "disabled:opacity-50"
|
|
251
|
+
// Disabled opacity
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
/**
|
|
255
|
+
* Color tokens for link variants
|
|
256
|
+
* Uses semantic color tokens that map to CSS variables
|
|
257
|
+
*/
|
|
258
|
+
variant: {
|
|
259
|
+
primary: {
|
|
260
|
+
text: "text-primary",
|
|
261
|
+
// Primary text using CSS var
|
|
262
|
+
hover: "hover:text-primary/80",
|
|
263
|
+
// Primary hover text
|
|
264
|
+
underline: "hover:underline"
|
|
265
|
+
// Underline on hover
|
|
266
|
+
},
|
|
267
|
+
secondary: {
|
|
268
|
+
text: "text-secondary",
|
|
269
|
+
// Secondary text using CSS var
|
|
270
|
+
hover: "hover:underline"
|
|
271
|
+
// Underline on hover
|
|
272
|
+
},
|
|
273
|
+
accent: {
|
|
274
|
+
text: "text-accent",
|
|
275
|
+
// Accent text using CSS var (accent color, not accent-foreground)
|
|
276
|
+
hover: "hover:text-accent/80",
|
|
277
|
+
// Accent hover text
|
|
278
|
+
underline: "hover:underline"
|
|
279
|
+
// Underline on hover
|
|
280
|
+
},
|
|
281
|
+
outline: {
|
|
282
|
+
border: "border border-input",
|
|
283
|
+
// Input border using CSS var
|
|
284
|
+
background: "bg-background",
|
|
285
|
+
// Background using CSS var
|
|
286
|
+
text: "text-foreground",
|
|
287
|
+
// Foreground text using CSS var
|
|
288
|
+
hover: {
|
|
289
|
+
background: "hover:bg-accent",
|
|
290
|
+
// Hover background
|
|
291
|
+
text: "hover:text-accent-foreground"
|
|
292
|
+
// Hover text
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
ghost: {
|
|
296
|
+
text: "text-foreground",
|
|
297
|
+
// Foreground text using CSS var
|
|
298
|
+
hover: {
|
|
299
|
+
background: "hover:bg-accent",
|
|
300
|
+
// Hover background
|
|
301
|
+
text: "hover:text-accent-foreground"
|
|
302
|
+
// Hover text
|
|
303
|
+
}
|
|
304
|
+
},
|
|
305
|
+
link: {
|
|
306
|
+
text: "text-primary",
|
|
307
|
+
// Primary text using CSS var
|
|
308
|
+
hover: "hover:underline"
|
|
309
|
+
// Underline on hover
|
|
310
|
+
},
|
|
311
|
+
destructive: {
|
|
312
|
+
text: "text-destructive",
|
|
313
|
+
// Destructive text using CSS var
|
|
314
|
+
hover: "hover:text-destructive/80",
|
|
315
|
+
// Destructive hover text
|
|
316
|
+
underline: "hover:underline"
|
|
317
|
+
// Underline on hover
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
var ICON_WRAPPER_CLASS = LINK_TOKENS.iconWrapper;
|
|
322
|
+
function renderIcon(icon) {
|
|
323
|
+
if (!icon) return null;
|
|
324
|
+
return /* @__PURE__ */ jsxRuntime.jsx("span", { className: ICON_WRAPPER_CLASS, children: icon });
|
|
325
|
+
}
|
|
326
|
+
var linkVariants = tokenCVA({
|
|
327
|
+
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}`,
|
|
328
|
+
variants: {
|
|
329
|
+
variant: {
|
|
330
|
+
primary: `${LINK_TOKENS.variant.primary.text} ${LINK_TOKENS.variant.primary.hover} ${LINK_TOKENS.underlineOffset} ${LINK_TOKENS.variant.primary.underline}`,
|
|
331
|
+
secondary: `${LINK_TOKENS.variant.secondary.text} ${LINK_TOKENS.underlineOffset} ${LINK_TOKENS.variant.secondary.hover}`,
|
|
332
|
+
accent: `${LINK_TOKENS.variant.accent.text} ${LINK_TOKENS.variant.accent.hover} ${LINK_TOKENS.underlineOffset} ${LINK_TOKENS.variant.accent.underline}`,
|
|
333
|
+
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}`,
|
|
334
|
+
ghost: `${LINK_TOKENS.variant.ghost.text} ${LINK_TOKENS.variant.ghost.hover.background} ${LINK_TOKENS.variant.ghost.hover.text} ${LINK_TOKENS.radius}`,
|
|
335
|
+
link: `${LINK_TOKENS.variant.link.text} ${LINK_TOKENS.underlineOffset} ${LINK_TOKENS.variant.link.hover}`,
|
|
336
|
+
destructive: `${LINK_TOKENS.variant.destructive.text} ${LINK_TOKENS.variant.destructive.hover} ${LINK_TOKENS.underlineOffset} ${LINK_TOKENS.variant.destructive.underline}`
|
|
337
|
+
},
|
|
338
|
+
size: {
|
|
339
|
+
sm: `${LINK_TOKENS.height.sm} ${LINK_TOKENS.fontSize.sm} ${LINK_TOKENS.padding.horizontal.sm} ${LINK_TOKENS.padding.vertical.xs}`,
|
|
340
|
+
md: `${LINK_TOKENS.height.md} ${LINK_TOKENS.fontSize.md} ${LINK_TOKENS.padding.horizontal.md} ${LINK_TOKENS.padding.vertical.sm}`,
|
|
341
|
+
lg: `${LINK_TOKENS.height.lg} ${LINK_TOKENS.fontSize.lg} ${LINK_TOKENS.padding.horizontal.lg} ${LINK_TOKENS.padding.vertical.sm}`
|
|
342
|
+
}
|
|
343
|
+
},
|
|
344
|
+
defaultVariants: {
|
|
345
|
+
variant: "link",
|
|
346
|
+
size: "md"
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
var Link = React__namespace.forwardRef(
|
|
350
|
+
({ variant, size, leftIcon, rightIcon, children, disabled, onClick, href, tabIndex, ...props }, ref) => {
|
|
351
|
+
const handleClick = React__namespace.useCallback(
|
|
352
|
+
(e) => {
|
|
353
|
+
if (disabled) {
|
|
354
|
+
e.preventDefault();
|
|
355
|
+
e.stopPropagation();
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
onClick?.(e);
|
|
359
|
+
},
|
|
360
|
+
[disabled, onClick]
|
|
361
|
+
);
|
|
362
|
+
const finalClassName = linkVariants({ variant, size });
|
|
363
|
+
const finalTabIndex = disabled ? tabIndex ?? -1 : tabIndex;
|
|
364
|
+
const finalAriaDisabled = disabled ? true : void 0;
|
|
365
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
366
|
+
"a",
|
|
367
|
+
{
|
|
368
|
+
className: finalClassName,
|
|
369
|
+
ref,
|
|
370
|
+
href,
|
|
371
|
+
tabIndex: finalTabIndex,
|
|
372
|
+
"aria-disabled": finalAriaDisabled,
|
|
373
|
+
onClick: handleClick,
|
|
374
|
+
...props,
|
|
375
|
+
children: [
|
|
376
|
+
renderIcon(leftIcon),
|
|
377
|
+
children,
|
|
378
|
+
renderIcon(rightIcon)
|
|
379
|
+
]
|
|
380
|
+
}
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
);
|
|
384
|
+
Link.displayName = "Link";
|
|
385
|
+
var NextLinkAdapter = React__namespace.forwardRef(
|
|
386
|
+
({ href, prefetch, replace, scroll, shallow, locale, ...props }, ref) => {
|
|
387
|
+
const hrefString = typeof href === "string" ? href : href.pathname || String(href);
|
|
388
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
389
|
+
NextLink__default.default,
|
|
390
|
+
{
|
|
391
|
+
href,
|
|
392
|
+
prefetch,
|
|
393
|
+
replace,
|
|
394
|
+
scroll,
|
|
395
|
+
shallow,
|
|
396
|
+
locale,
|
|
397
|
+
passHref: true,
|
|
398
|
+
legacyBehavior: true,
|
|
399
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(Link, { ref, href: hrefString, ...props })
|
|
400
|
+
}
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
);
|
|
404
|
+
NextLinkAdapter.displayName = "NextLinkAdapter";
|
|
405
|
+
|
|
406
|
+
exports.NextLinkAdapter = NextLinkAdapter;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { LinkProps as LinkProps$1 } from 'next/link';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { L as LinkProps } from '../../Link-ZWr5iFB0.cjs';
|
|
4
|
+
import 'class-variance-authority/types';
|
|
5
|
+
|
|
6
|
+
interface NextLinkAdapterProps extends Omit<LinkProps, "href"> {
|
|
7
|
+
/**
|
|
8
|
+
* Next.js Link props
|
|
9
|
+
*/
|
|
10
|
+
href: LinkProps$1["href"];
|
|
11
|
+
/**
|
|
12
|
+
* Next.js specific props
|
|
13
|
+
*/
|
|
14
|
+
prefetch?: LinkProps$1["prefetch"];
|
|
15
|
+
replace?: LinkProps$1["replace"];
|
|
16
|
+
scroll?: LinkProps$1["scroll"];
|
|
17
|
+
shallow?: LinkProps$1["shallow"];
|
|
18
|
+
locale?: LinkProps$1["locale"];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* NextLinkAdapter
|
|
22
|
+
*
|
|
23
|
+
* A compatibility adapter that bridges Next.js `next/link` with TenerifeUI `Link`.
|
|
24
|
+
* This adapter resolves the "nested <a> tag" hydration error common in Next.js 13+
|
|
25
|
+
* by utilizing the `legacyBehavior` pattern, allowing Foundation Link (which is an <a>)
|
|
26
|
+
* to function as the child of NextLink.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```tsx
|
|
30
|
+
* <NextLinkAdapter href="/dashboard" variant="primary">
|
|
31
|
+
* Dashboard
|
|
32
|
+
* </NextLinkAdapter>
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
declare const NextLinkAdapter: React.ForwardRefExoticComponent<NextLinkAdapterProps & React.RefAttributes<HTMLAnchorElement>>;
|
|
36
|
+
|
|
37
|
+
export { NextLinkAdapter, type NextLinkAdapterProps };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { LinkProps as LinkProps$1 } from 'next/link';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { L as LinkProps } from '../../Link-ZWr5iFB0.js';
|
|
4
|
+
import 'class-variance-authority/types';
|
|
5
|
+
|
|
6
|
+
interface NextLinkAdapterProps extends Omit<LinkProps, "href"> {
|
|
7
|
+
/**
|
|
8
|
+
* Next.js Link props
|
|
9
|
+
*/
|
|
10
|
+
href: LinkProps$1["href"];
|
|
11
|
+
/**
|
|
12
|
+
* Next.js specific props
|
|
13
|
+
*/
|
|
14
|
+
prefetch?: LinkProps$1["prefetch"];
|
|
15
|
+
replace?: LinkProps$1["replace"];
|
|
16
|
+
scroll?: LinkProps$1["scroll"];
|
|
17
|
+
shallow?: LinkProps$1["shallow"];
|
|
18
|
+
locale?: LinkProps$1["locale"];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* NextLinkAdapter
|
|
22
|
+
*
|
|
23
|
+
* A compatibility adapter that bridges Next.js `next/link` with TenerifeUI `Link`.
|
|
24
|
+
* This adapter resolves the "nested <a> tag" hydration error common in Next.js 13+
|
|
25
|
+
* by utilizing the `legacyBehavior` pattern, allowing Foundation Link (which is an <a>)
|
|
26
|
+
* to function as the child of NextLink.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```tsx
|
|
30
|
+
* <NextLinkAdapter href="/dashboard" variant="primary">
|
|
31
|
+
* Dashboard
|
|
32
|
+
* </NextLinkAdapter>
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
declare const NextLinkAdapter: React.ForwardRefExoticComponent<NextLinkAdapterProps & React.RefAttributes<HTMLAnchorElement>>;
|
|
36
|
+
|
|
37
|
+
export { NextLinkAdapter, type NextLinkAdapterProps };
|