diff --git a/docs/storybook/package.json b/docs/storybook/package.json index 4d1d50e7..40357be9 100644 --- a/docs/storybook/package.json +++ b/docs/storybook/package.json @@ -19,12 +19,12 @@ "react-dom": "catalog:core" }, "devDependencies": { - "@storybook/react-vite": "^10.1.4", + "@storybook/react-vite": "^10.2.8", "@types/react": "catalog:types", "@types/react-dom": "catalog:types", "@vitejs/plugin-react": "^5.1.2", "globals": "^16.5.0", - "storybook": "^10.1.4", + "storybook": "^10.2.8", "typescript": "catalog:core", "vite": "^7.2.7" } diff --git a/docs/website/src/components/example/button-disabled.tsx b/docs/website/src/components/example/button-disabled.tsx index 7367e498..bc27ad30 100644 --- a/docs/website/src/components/example/button-disabled.tsx +++ b/docs/website/src/components/example/button-disabled.tsx @@ -4,7 +4,9 @@ export default function ButtonDisabled() { return (
- +
); } diff --git a/docs/website/src/components/example/button-loading.tsx b/docs/website/src/components/example/button-loading.tsx index 9390e81a..635c5553 100644 --- a/docs/website/src/components/example/button-loading.tsx +++ b/docs/website/src/components/example/button-loading.tsx @@ -4,7 +4,9 @@ export default function ButtonLoading() { return (
- +
); } diff --git a/docs/website/src/components/example/button-prefix-suffix.tsx b/docs/website/src/components/example/button-prefix-suffix.tsx index 996c5bd2..d1408752 100644 --- a/docs/website/src/components/example/button-prefix-suffix.tsx +++ b/docs/website/src/components/example/button-prefix-suffix.tsx @@ -1,12 +1,14 @@ import { Button } from '@cocso-ui/react'; -import { PlusIcon, ArrowIOSForwardIcon } from '@cocso-ui/react-icons'; +import { ArrowIOSForwardIcon, PlusIcon } from '@cocso-ui/react-icons'; export default function ButtonPrefixSuffix() { return (
- +
); } diff --git a/docs/website/src/components/example/button-shape.tsx b/docs/website/src/components/example/button-shape.tsx index b9d9ee65..d8f6e31f 100644 --- a/docs/website/src/components/example/button-shape.tsx +++ b/docs/website/src/components/example/button-shape.tsx @@ -6,7 +6,9 @@ export default function ButtonShape() {
- +
); } diff --git a/docs/website/src/components/example/button-size.tsx b/docs/website/src/components/example/button-size.tsx index 73bb9e90..aebc383e 100644 --- a/docs/website/src/components/example/button-size.tsx +++ b/docs/website/src/components/example/button-size.tsx @@ -7,7 +7,6 @@ export default function ButtonSize() { - ); } diff --git a/docs/website/src/components/example/button-variant.tsx b/docs/website/src/components/example/button-variant.tsx index a5a0a29f..5e2d3d5c 100644 --- a/docs/website/src/components/example/button-variant.tsx +++ b/docs/website/src/components/example/button-variant.tsx @@ -7,7 +7,7 @@ export default function ButtonVariant() { - + diff --git a/docs/website/src/components/example/checkbox-default.tsx b/docs/website/src/components/example/checkbox-default.tsx index f15ab077..7f2003ae 100644 --- a/docs/website/src/components/example/checkbox-default.tsx +++ b/docs/website/src/components/example/checkbox-default.tsx @@ -6,10 +6,5 @@ import { useState } from 'react'; export default function CheckboxDefault() { const [status, setStatus] = useState<'on' | 'off' | 'intermediate'>('off'); - return ( - setStatus(newStatus)} - /> - ); + return setStatus(newStatus)} />; } diff --git a/docs/website/src/components/example/day-picker-default.tsx b/docs/website/src/components/example/day-picker-default.tsx index 5aaeb70b..fc340bb4 100644 --- a/docs/website/src/components/example/day-picker-default.tsx +++ b/docs/website/src/components/example/day-picker-default.tsx @@ -6,10 +6,5 @@ import { useState } from 'react'; export default function DayPickerDefault() { const [value, setValue] = useState(new Date()); - return ( - setValue(date ?? undefined)} - /> - ); + return setValue(date ?? undefined)} />; } diff --git a/docs/website/src/components/example/day-picker-range.tsx b/docs/website/src/components/example/day-picker-range.tsx index 14aa2aec..2bddbc04 100644 --- a/docs/website/src/components/example/day-picker-range.tsx +++ b/docs/website/src/components/example/day-picker-range.tsx @@ -11,7 +11,7 @@ export default function DayPickerRange() { return ( setValue(date ?? undefined)} + onValueChange={date => setValue(date ?? undefined)} minDate={today} maxDate={maxDate} /> diff --git a/docs/website/src/components/example/link-indicator.tsx b/docs/website/src/components/example/link-indicator.tsx index 6d34b05c..7788c155 100644 --- a/docs/website/src/components/example/link-indicator.tsx +++ b/docs/website/src/components/example/link-indicator.tsx @@ -4,7 +4,9 @@ export default function LinkIndicator() { return (
밑줄 있음 - 밑줄 없음 + + 밑줄 없음 +
); } diff --git a/docs/website/src/components/example/link-size.tsx b/docs/website/src/components/example/link-size.tsx index b4f00acb..16b0dc95 100644 --- a/docs/website/src/components/example/link-size.tsx +++ b/docs/website/src/components/example/link-size.tsx @@ -3,10 +3,18 @@ import { Link } from '@cocso-ui/react'; export default function LinkSize() { return (
- XSmall - Small - Medium - Large + + XSmall + + + Small + + + Medium + + + Large +
); } diff --git a/docs/website/src/components/example/month-picker-default.tsx b/docs/website/src/components/example/month-picker-default.tsx index 2fd86602..5a24a155 100644 --- a/docs/website/src/components/example/month-picker-default.tsx +++ b/docs/website/src/components/example/month-picker-default.tsx @@ -6,10 +6,5 @@ import { useState } from 'react'; export default function MonthPickerDefault() { const [value, setValue] = useState(new Date()); - return ( - setValue(date ?? undefined)} - /> - ); + return setValue(date ?? undefined)} />; } diff --git a/docs/website/src/components/example/month-picker-range.tsx b/docs/website/src/components/example/month-picker-range.tsx index 74585e63..c92832df 100644 --- a/docs/website/src/components/example/month-picker-range.tsx +++ b/docs/website/src/components/example/month-picker-range.tsx @@ -11,7 +11,7 @@ export default function MonthPickerRange() { return ( setValue(date ?? undefined)} + onValueChange={date => setValue(date ?? undefined)} minDate={minDate} maxDate={maxDate} /> diff --git a/docs/website/src/components/example/otp-default.tsx b/docs/website/src/components/example/otp-default.tsx index 6789179d..54d16166 100644 --- a/docs/website/src/components/example/otp-default.tsx +++ b/docs/website/src/components/example/otp-default.tsx @@ -6,6 +6,7 @@ export default function OtpDefault() { return ( {Array.from({ length: 6 }, (_, index) => ( + // biome-ignore lint/suspicious/noArrayIndexKey: fixed-length OTP field ))} diff --git a/docs/website/src/components/example/pagination-default.tsx b/docs/website/src/components/example/pagination-default.tsx index 072583e4..ba323450 100644 --- a/docs/website/src/components/example/pagination-default.tsx +++ b/docs/website/src/components/example/pagination-default.tsx @@ -6,11 +6,5 @@ import { useState } from 'react'; export default function PaginationDefault() { const [page, setPage] = useState(1); - return ( - - ); + return ; } diff --git a/docs/website/src/components/example/pagination-max-visible.tsx b/docs/website/src/components/example/pagination-max-visible.tsx index ea67720d..22f8fba5 100644 --- a/docs/website/src/components/example/pagination-max-visible.tsx +++ b/docs/website/src/components/example/pagination-max-visible.tsx @@ -6,12 +6,5 @@ import { useState } from 'react'; export default function PaginationMaxVisible() { const [page, setPage] = useState(1); - return ( - - ); + return ; } diff --git a/docs/website/src/components/example/select-size.tsx b/docs/website/src/components/example/select-size.tsx index 1471a537..eca913d4 100644 --- a/docs/website/src/components/example/select-size.tsx +++ b/docs/website/src/components/example/select-size.tsx @@ -3,12 +3,18 @@ import { Select } from '@cocso-ui/react'; export default function SelectSize() { return (
- - - - - - + + + +
); } diff --git a/docs/website/src/components/example/toast-variant.tsx b/docs/website/src/components/example/toast-variant.tsx index 3645dcd2..306e9057 100644 --- a/docs/website/src/components/example/toast-variant.tsx +++ b/docs/website/src/components/example/toast-variant.tsx @@ -6,11 +6,21 @@ export default function ToastVariant() { return ( <>
- - - - - + + + + +
diff --git a/docs/website/src/components/example/typography-type.tsx b/docs/website/src/components/example/typography-type.tsx index d17c12f0..82eef9f7 100644 --- a/docs/website/src/components/example/typography-type.tsx +++ b/docs/website/src/components/example/typography-type.tsx @@ -3,8 +3,12 @@ import { Typography } from '@cocso-ui/react'; export default function TypographyType() { return (
- Heading 텍스트 - Body 텍스트입니다. 일반적인 본문 내용에 사용합니다. + + Heading 텍스트 + + + Body 텍스트입니다. 일반적인 본문 내용에 사용합니다. +
); } diff --git a/docs/website/src/components/example/typography-weight.tsx b/docs/website/src/components/example/typography-weight.tsx index 631ae735..90495aaf 100644 --- a/docs/website/src/components/example/typography-weight.tsx +++ b/docs/website/src/components/example/typography-weight.tsx @@ -3,11 +3,21 @@ import { Typography } from '@cocso-ui/react'; export default function TypographyWeight() { return (
- Light (300) - Normal (400) - Medium (500) - Semibold (600) - Bold (700) + + Light (300) + + + Normal (400) + + + Medium (500) + + + Semibold (600) + + + Bold (700) +
); } diff --git a/ecosystem/baseframe/bin/index.js b/ecosystem/baseframe/bin/index.js index 7e4cbd1d..22b2c840 100755 --- a/ecosystem/baseframe/bin/index.js +++ b/ecosystem/baseframe/bin/index.js @@ -1,14 +1,21 @@ #!/usr/bin/env node -import h from"fs-extra";import{createRequire as oe}from"module";import w from"path";import ie from"yaml";import se from"yargs";function y(e,t){return{tokens:G(e),collections:_(t)}}function _(e){return e.data.map(t=>({name:t.name,modes:t.modes}))}function Y(e){let{collection:t,tokens:r}=e.data;return Object.entries(r).map(([n,o])=>{let i=Object.entries(o.values).map(([a,s])=>({mode:a,value:s}));return{token:{name:n,collection:t},values:i}})}function G(e){return e.flatMap(Y)}function k(e){let t=String(e).trim();if(typeof e=="number")return{isValid:!0,value:{kind:"NumberValue",value:e}};let r=D(t);if(r.isValid)return r;let n=N(t);if(n.isValid)return n;let o=E(t);if(o.isValid)return o;let i=T(t);if(i.isValid)return i;let a=W(t);if(a.isValid)return a;let s=z(t);if(s.isValid)return s;let l=X(t);return l.isValid?l:{isValid:!0,value:{kind:"StringValue",value:t}}}function f(e){switch(e.kind){case"HexColor":return e.value;case"RgbColor":return`rgb(${e.r}, ${e.g}, ${e.b})`;case"RgbaColor":return`rgba(${e.r}, ${e.g}, ${e.b}, ${e.a})`;case"TokenRef":return`$${e.collection}.${e.token}`;case"SizeValue":return`${e.value}${e.unit}`;case"DurationValue":return`${e.value}${e.unit}`;case"NumberValue":return e.value.toString();case"StringValue":return e.value;case"ShadowLayer":{let t=f(e.offsetX),r=f(e.offsetY),n=f(e.blur),o=f(e.spread),i=f(e.color);return`${t} ${r} ${n} ${o} ${i}`}case"Shadow":return e.layers.map(t=>f(t)).join(", ");default:return String(e)}}function D(e){return/^#([A-Fa-f0-9]{3}|[A-Fa-f0-9]{4}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$/.test(e)?{isValid:!0,value:{kind:"HexColor",value:e}}:{isValid:!1,error:`Invalid hex color: ${e}`}}function N(e){let t=/^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/,r=e.match(t);if(!r)return{isValid:!1,error:`Invalid rgb color: ${e}`};let[,n,o,i]=r,a=Number.parseInt(n,10),s=Number.parseInt(o,10),l=Number.parseInt(i,10);return a<0||a>255||s<0||s>255||l<0||l>255?{isValid:!1,error:`Invalid RGB values: ${e}`}:{isValid:!0,value:{kind:"RgbColor",r:a,g:s,b:l}}}function E(e){let t=/^rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*([01]?\.?\d*)\s*\)$/,r=e.match(t);if(!r)return{isValid:!1,error:`Invalid rgba color: ${e}`};let[,n,o,i,a]=r,s=Number.parseInt(n,10),l=Number.parseInt(o,10),c=Number.parseInt(i,10),u=Number.parseFloat(a);return s<0||s>255||l<0||l>255||c<0||c>255?{isValid:!1,error:`Invalid RGB values: ${e}`}:u<0||u>1?{isValid:!1,error:`Invalid alpha value: ${e}`}:{isValid:!0,value:{kind:"RgbaColor",r:s,g:l,b:c,a:u}}}function T(e){if(!/^\$[a-zA-Z0-9][a-zA-Z0-9_-]*(?:\.[a-zA-Z0-9][a-zA-Z0-9_-]*)+$/.test(e))return{isValid:!1,error:`Invalid token reference: ${e}`};let n=e.substring(1).split(".");if(n.length<2)return{isValid:!1,error:`Invalid token format: ${e}`};let o=n[n.length-1];return{isValid:!0,value:{kind:"TokenRef",collection:n.slice(0,-1).join("."),token:o}}}function z(e){let t=/^([+-]?\d*\.?\d+)(px|rem|em|vw|vh|%)$/,r=e.match(t);if(!r)return{isValid:!1,error:`Invalid size: ${e}`};let[,n,o]=r,i=Number.parseFloat(n);return Number.isNaN(i)?{isValid:!1,error:`Invalid size value: ${e}`}:{isValid:!0,value:{kind:"SizeValue",value:i,unit:o}}}function X(e){let t=/^([+-]?\d*\.?\d+)(ms|s)$/,r=e.match(t);if(!r)return{isValid:!1,error:`Invalid duration: ${e}`};let[,n,o]=r,i=Number.parseFloat(n);return Number.isNaN(i)?{isValid:!1,error:`Invalid duration value: ${e}`}:{isValid:!0,value:{kind:"DurationValue",value:i,unit:o}}}function H(e){if(!e)return{isValid:!1,error:"Missing color value"};if(e.startsWith("$"))return T(e);if(e.startsWith("var("))return{isValid:!0,value:{kind:"StringValue",value:e}};let t=D(e);if(t.isValid)return t;let r=N(e);if(r.isValid)return r;let n=E(e);return n.isValid?n:{isValid:!1,error:`Invalid color: ${e}`}}function W(e){if(!e.includes("px")&&!e.includes("$"))return{isValid:!1,error:`Invalid shadow format: ${e}`};try{let t=e.split(",").map(n=>n.trim()),r=[];for(let n of t){let o=Z(n);if(!o.isValid||!o.value)return{isValid:!1,error:`Invalid shadow layer: ${n}`};r.push(o.value)}return{isValid:!0,value:{kind:"Shadow",layers:r}}}catch{return{isValid:!1,error:`Invalid shadow format: ${e}`}}}function Z(e){let t=e.trim().split(/\s+/);if(t.length<4)return{isValid:!1,error:`Invalid shadow layer format: ${e}`};try{let r=v(t[0]),n=v(t[1]),o=v(t[2]),i=v(t[3]);if(!r.isValid||!n.isValid||!o.isValid||!i.isValid)return{isValid:!1,error:`Invalid shadow dimensions: ${e}`};let a=t.slice(4).join(" ").trim(),s=H(a);return s.isValid?{isValid:!0,value:{kind:"ShadowLayer",color:s.value,offsetX:r.value,offsetY:n.value,blur:o.value,spread:i.value}}:{isValid:!1,error:`Invalid shadow color: ${a}`}}catch{return{isValid:!1,error:`Invalid shadow layer: ${e}`}}}function v(e){return e.startsWith("$")?T(e):z(e)}function B(e){return k(e)}function U(e){let t=[];return e.forEach(r=>{r.values.forEach(n=>{let o=B(n.value);t.push({tokenName:r.token.name,collection:r.token.collection,mode:n.mode,value:n.value,result:o})})}),t}function g(e){let t=[];return e.kind==="TokenRef"?t.push({collection:e.collection,token:e.token}):e.kind==="ShadowLayer"?(t.push(...g(e.color)),t.push(...g(e.offsetX)),t.push(...g(e.offsetY)),t.push(...g(e.blur)),t.push(...g(e.spread))):e.kind==="Shadow"&&e.layers.forEach(r=>{t.push(...g(r))}),t}function q(e,t){let r=[],n=[],o=e.data.collection,i=t.get(o);return i?(Object.entries(e.data.tokens).forEach(([a,s])=>{let l=Object.keys(s.values),c=i.modes.filter(u=>!l.includes(u));c.length>0&&r.push({type:"MISSING_MODE",message:`Token '${a}' is missing modes: ${c.join(", ")}`,tokenName:a,collection:o}),Object.entries(s.values).forEach(([u,d])=>{let p=k(d);p.isValid||r.push({type:"INVALID_VALUE_FORMAT",message:p.error||`Invalid value format for token '${a}' in mode '${u}'`,tokenName:a,collection:o,mode:u,value:d})})}),{isValid:r.length===0,errors:r,warnings:n}):(r.push({type:"INVALID_COLLECTION",message:`Collection '${o}' is not defined`,collection:o}),{isValid:!1,errors:r,warnings:n})}function K(e,t){let r=[],n=[];return U(e).forEach(({tokenName:i,collection:a,mode:s,value:l,result:c})=>{if(!c.isValid){r.push({type:"INVALID_VALUE_FORMAT",message:c.error||"Invalid value format",tokenName:i,collection:a,mode:s,value:l});return}c.value&&g(c.value).forEach(({collection:d,token:p})=>{let A=`$${d}.${p}`;t.find(M=>M.token.name===A)||r.push({type:"INVALID_PRIMITIVE_TOKEN",message:`Referenced token '${A}' not found`,tokenName:i,collection:a,mode:s,value:l})})}),{isValid:r.length===0,errors:r,warnings:n}}function P(e,t){let r=[],n=[];e.forEach(s=>{let l=q(s,t);r.push(...l.errors),n.push(...l.warnings)});let o={kind:"TokenCollections",metadata:{id:"temp",name:"temp"},data:Array.from(t.values())},i=y(e,o),a=K(i.tokens,i.tokens);return r.push(...a.errors),n.push(...a.warnings),{isValid:r.length===0,errors:r,warnings:n}}function b(e,t){let r=new Map(t.data.map(o=>[o.name,o])),n=P(e,r);if(!n.isValid)throw console.error("Token validation failed:"),n.errors.forEach(o=>{console.error(` ${o.message}`)}),new Error("Token validation failed. Please fix the errors above.");return n.warnings.length>0&&(console.warn("Token validation warnings:"),n.warnings.forEach(o=>{console.warn(` ${o}`)})),y(e,t)}function x(e,t,r){return{resolve(n){return m(n,e,t,r)},resolveTokenRef(n){let o=`$${n.collection}.${n.token}`;if(!e.find(s=>s.token.name===o))throw new Error(`Token not found: ${o}`);return`var(${r(o)})`}}}function m(e,t,r,n){switch(e.kind){case"TokenRef":{let o=`$${e.collection}.${e.token}`;if(!t.find(s=>s.token.name===o))throw new Error(`Token not found: ${o}`);return{kind:"StringValue",value:`var(${n(o)})`}}case"ShadowLayer":return{kind:"ShadowLayer",color:m(e.color,t,r,n),offsetX:m(e.offsetX,t,r,n),offsetY:m(e.offsetY,t,r,n),blur:m(e.blur,t,r,n),spread:m(e.spread,t,r,n)};case"Shadow":return{kind:"Shadow",layers:e.layers.map(o=>m(o,t,r,n))};default:return e}}function V(e){return typeof e=="string"?e:typeof e=="number"?e.toString():f(e)}function $(e,t,r,n){let o=String(e);if(!o.startsWith("$")){let s=k(o);if(s.isValid&&s.value){let c=x(t,"default",u=>r(u,n)).resolve(s.value);return V(c)}return e}let i=k(o);if(!i.isValid||!i.value)throw new Error(`Invalid token reference: ${o}`);return x(t,"default",s=>r(s,n)).resolveTokenRef(i.value)}function R(e,t){let r=e.replace(/^\$/,"").replace(/\./g,"-");return t?`--${t}-${r}`:`--${r}`}function J(e,t,r,n){let o=e.values.find(s=>s.mode===t);if(!o)throw new Error(`No value found for token '${e.token.name}' in mode '${t}'`);let i=$(o.value,r,R,n);return`${R(e.token.name,n)}: ${V(i)};`}function Q(e,t,r,n,o){let i=t.map(a=>` ${J(a,r,n,o)}`).join(` +import k from"fs-extra";import{createRequire as oe}from"module";import I from"path";import ie from"yaml";import se from"yargs";function b(e,t){return{tokens:X(e),collections:G(t)}}function G(e){return e.data.map(t=>({name:t.name,modes:t.modes}))}function H(e){let{collection:t,tokens:r}=e.data;return Object.entries(r).map(([n,o])=>{let i=Object.entries(o.values).map(([s,a])=>({mode:s,value:a}));return{token:{name:n,collection:t},values:i}})}function X(e){return e.flatMap(H)}function $(e){return/^#([A-Fa-f0-9]{3}|[A-Fa-f0-9]{4}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$/.test(e)?{isValid:!0,value:{kind:"HexColor",value:e}}:{isValid:!1,error:`Invalid hex color: ${e}`}}function x(e){let t=/^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/,r=e.match(t);if(!r)return{isValid:!1,error:`Invalid rgb color: ${e}`};let[,n,o,i]=r,s=Number.parseInt(n,10),a=Number.parseInt(o,10),l=Number.parseInt(i,10);return s<0||s>255||a<0||a>255||l<0||l>255?{isValid:!1,error:`Invalid RGB values: ${e}`}:{isValid:!0,value:{kind:"RgbColor",r:s,g:a,b:l}}}function T(e){let t=/^rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*([01]?\.?\d*)\s*\)$/,r=e.match(t);if(!r)return{isValid:!1,error:`Invalid rgba color: ${e}`};let[,n,o,i,s]=r,a=Number.parseInt(n,10),l=Number.parseInt(o,10),c=Number.parseInt(i,10),u=Number.parseFloat(s);return a<0||a>255||l<0||l>255||c<0||c>255?{isValid:!1,error:`Invalid RGB values: ${e}`}:u<0||u>1?{isValid:!1,error:`Invalid alpha value: ${e}`}:{isValid:!0,value:{kind:"RgbaColor",r:a,g:l,b:c,a:u}}}function y(e){if(!/^\$[a-zA-Z0-9][a-zA-Z0-9_-]*(?:\.[a-zA-Z0-9][a-zA-Z0-9_-]*)+$/.test(e))return{isValid:!1,error:`Invalid token reference: ${e}`};let n=e.substring(1).split(".");if(n.length<2)return{isValid:!1,error:`Invalid token format: ${e}`};let o=n[n.length-1];return{isValid:!0,value:{kind:"TokenRef",collection:n.slice(0,-1).join("."),token:o}}}function w(e){let t=/^([+-]?\d*\.?\d+)(px|rem|em|vw|vh|%)$/,r=e.match(t);if(!r)return{isValid:!1,error:`Invalid size: ${e}`};let[,n,o]=r,i=Number.parseFloat(n);return Number.isNaN(i)?{isValid:!1,error:`Invalid size value: ${e}`}:{isValid:!0,value:{kind:"SizeValue",value:i,unit:o}}}function O(e){let t=/^([+-]?\d*\.?\d+)(ms|s)$/,r=e.match(t);if(!r)return{isValid:!1,error:`Invalid duration: ${e}`};let[,n,o]=r,i=Number.parseFloat(n);return Number.isNaN(i)?{isValid:!1,error:`Invalid duration value: ${e}`}:{isValid:!0,value:{kind:"DurationValue",value:i,unit:o}}}function W(e){if(!e)return{isValid:!1,error:"Missing color value"};if(e.startsWith("$"))return y(e);if(e.startsWith("var("))return{isValid:!0,value:{kind:"StringValue",value:e}};let t=$(e);if(t.isValid)return t;let r=x(e);if(r.isValid)return r;let n=T(e);return n.isValid?n:{isValid:!1,error:`Invalid color: ${e}`}}function R(e){return e.startsWith("$")?y(e):w(e)}function Z(e){let t=e.trim().split(/\s+/);if(t.length<4)return{isValid:!1,error:`Invalid shadow layer format: ${e}`};try{let r=R(t[0]),n=R(t[1]),o=R(t[2]),i=R(t[3]);if(!r.isValid||!n.isValid||!o.isValid||!i.isValid)return{isValid:!1,error:`Invalid shadow dimensions: ${e}`};let s=t.slice(4).join(" ").trim(),a=W(s);return a.isValid?{isValid:!0,value:{kind:"ShadowLayer",color:a.value,offsetX:r.value,offsetY:n.value,blur:o.value,spread:i.value}}:{isValid:!1,error:`Invalid shadow color: ${s}`}}catch{return{isValid:!1,error:`Invalid shadow layer: ${e}`}}}function L(e){if(!e.includes("px")&&!e.includes("$"))return{isValid:!1,error:`Invalid shadow format: ${e}`};try{let t=e.split(",").map(n=>n.trim()),r=[];for(let n of t){let o=Z(n);if(!o.isValid||!o.value)return{isValid:!1,error:`Invalid shadow layer: ${n}`};r.push(o.value)}return{isValid:!0,value:{kind:"Shadow",layers:r}}}catch{return{isValid:!1,error:`Invalid shadow format: ${e}`}}}function V(e){let t=String(e).trim();if(typeof e=="number")return{isValid:!0,value:{kind:"NumberValue",value:e}};let r=$(t);if(r.isValid)return r;let n=x(t);if(n.isValid)return n;let o=T(t);if(o.isValid)return o;let i=y(t);if(i.isValid)return i;let s=L(t);if(s.isValid)return s;let a=w(t);if(a.isValid)return a;let l=O(t);return l.isValid?l:{isValid:!0,value:{kind:"StringValue",value:t}}}function p(e){switch(e.kind){case"HexColor":return e.value;case"RgbColor":return`rgb(${e.r}, ${e.g}, ${e.b})`;case"RgbaColor":return`rgba(${e.r}, ${e.g}, ${e.b}, ${e.a})`;case"TokenRef":return`$${e.collection}.${e.token}`;case"SizeValue":return`${e.value}${e.unit}`;case"DurationValue":return`${e.value}${e.unit}`;case"NumberValue":return e.value.toString();case"StringValue":return e.value;case"ShadowLayer":{let t=p(e.offsetX),r=p(e.offsetY),n=p(e.blur),o=p(e.spread),i=p(e.color);return`${t} ${r} ${n} ${o} ${i}`}case"Shadow":return e.layers.map(t=>p(t)).join(", ");default:return String(e)}}function B(e){return V(e)}function U(e){let t=[];return e.forEach(r=>{r.values.forEach(n=>{let o=B(n.value);t.push({tokenName:r.token.name,collection:r.token.collection,mode:n.mode,value:n.value,result:o})})}),t}function m(e){let t=[];return e.kind==="TokenRef"?t.push({collection:e.collection,token:e.token}):e.kind==="ShadowLayer"?(t.push(...m(e.color)),t.push(...m(e.offsetX)),t.push(...m(e.offsetY)),t.push(...m(e.blur)),t.push(...m(e.spread))):e.kind==="Shadow"&&e.layers.forEach(r=>{t.push(...m(r))}),t}function q(e,t){let r=[],n=[],o=e.data.collection,i=t.get(o);return i?(Object.entries(e.data.tokens).forEach(([s,a])=>{let l=Object.keys(a.values),c=i.modes.filter(u=>!l.includes(u));c.length>0&&r.push({type:"MISSING_MODE",message:`Token '${s}' is missing modes: ${c.join(", ")}`,tokenName:s,collection:o}),Object.entries(a.values).forEach(([u,d])=>{let f=V(d);f.isValid||r.push({type:"INVALID_VALUE_FORMAT",message:f.error||`Invalid value format for token '${s}' in mode '${u}'`,tokenName:s,collection:o,mode:u,value:d})})}),{isValid:r.length===0,errors:r,warnings:n}):(r.push({type:"INVALID_COLLECTION",message:`Collection '${o}' is not defined`,collection:o}),{isValid:!1,errors:r,warnings:n})}function K(e,t){let r=[],n=[];return U(e).forEach(({tokenName:i,collection:s,mode:a,value:l,result:c})=>{if(!c.isValid){r.push({type:"INVALID_VALUE_FORMAT",message:c.error||"Invalid value format",tokenName:i,collection:s,mode:a,value:l});return}c.value&&m(c.value).forEach(({collection:d,token:f})=>{let A=`$${d}.${f}`;t.find(D=>D.token.name===A)||r.push({type:"INVALID_PRIMITIVE_TOKEN",message:`Referenced token '${A}' not found`,tokenName:i,collection:s,mode:a,value:l})})}),{isValid:r.length===0,errors:r,warnings:n}}function j(e,t){let r=[],n=[];e.forEach(a=>{let l=q(a,t);r.push(...l.errors),n.push(...l.warnings)});let o={kind:"TokenCollections",metadata:{id:"temp",name:"temp"},data:Array.from(t.values())},i=b(e,o),s=K(i.tokens,i.tokens);return r.push(...s.errors),n.push(...s.warnings),{isValid:r.length===0,errors:r,warnings:n}}function S(e,t){let r=new Map(t.data.map(o=>[o.name,o])),n=j(e,r);if(!n.isValid){let o=n.errors.map(i=>` ${i.message}`).join(` +`);throw new Error(`Token validation failed: +${o}`)}return b(e,t)}function E(e,t){return{resolve(r){return h(r,e,t)},resolveTokenRef(r){let n=`$${r.collection}.${r.token}`;if(!e.find(s=>s.token.name===n))throw new Error(`Token not found: ${n}`);return`var(${t(n)})`}}}function h(e,t,r){switch(e.kind){case"TokenRef":{let n=`$${e.collection}.${e.token}`;if(!t.find(s=>s.token.name===n))throw new Error(`Token not found: ${n}`);return{kind:"StringValue",value:`var(${r(n)})`}}case"ShadowLayer":return{kind:"ShadowLayer",color:h(e.color,t,r),offsetX:h(e.offsetX,t,r),offsetY:h(e.offsetY,t,r),blur:h(e.blur,t,r),spread:h(e.spread,t,r)};case"Shadow":return{kind:"Shadow",layers:e.layers.map(n=>h(n,t,r))};default:return e}}function v(e){return typeof e=="string"?e:typeof e=="number"?e.toString():p(e)}function C(e,t,r,n){let o=String(e);if(!o.startsWith("$")){let a=V(o);if(a.isValid&&a.value){let c=E(t,u=>r(u,n)).resolve(a.value);return v(c)}return e}let i=V(o);if(!i.isValid||!i.value)throw new Error(`Invalid token reference: ${o}`);return E(t,a=>r(a,n)).resolveTokenRef(i.value)}function g(e,t){let r=e.replace(/^\$/,"").replace(/\./g,"-");return t?`--${t}-${r}`:`--${r}`}function J(e,t,r,n){let o=e.values.find(a=>a.mode===t);if(!o)throw new Error(`No value found for token '${e.token.name}' in mode '${t}'`);let i=C(o.value,r,g,n);return`${g(e.token.name,n)}: ${v(i)};`}function Q(e,t,r,n,o){let i=t.map(s=>` ${J(s,r,n,o)}`).join(` `);return`${e} { ${i} -}`}function O(e,t){let{prefix:r,banner:n="",selectors:o={}}=t,{tokens:i,collections:a}=e,s=a.flatMap(l=>{let c=i.filter(u=>u.token.collection===l.name);return l.modes.map(u=>{var p;let d=((p=o[l.name])==null?void 0:p[u])||":root";return Q(d,c,u,i,r)})}).join(` +}`}function F(e,t){let{prefix:r,banner:n="",selectors:o={}}=t,{tokens:i,collections:s}=e,a=s.flatMap(l=>{let c=i.filter(u=>u.token.collection===l.name);return l.modes.map(u=>{var f;let d=((f=o[l.name])==null?void 0:f[u])||":root";return Q(d,c,u,i,r)})}).join(` -`);return`${n}${s}`}function ee(e,t,r={}){let n=b(e,t);return O(n,r)}var S={generateCssVariables:ee,generateFromAst:O,createVarName:R};function C(e,t){let r=e.replace(/^\$/,"");return r=r.replace(/\./g,"-"),t?`--${t}-${r}`:`--${r}`}function te(e,t,r){let{prefix:n}=r,o=[],i=new Set;return e.forEach(a=>{if(i.has(a.token.name))return;i.add(a.token.name);let s=a.values.find(d=>d.mode===t);if(!s)return;let l=$(s.value,e,C,n),c=V(l);a.token.name==="$number.1"&&o.push(` --spacing: ${c};`);let u=C(a.token.name,n);o.push(` ${u}: ${c};`)}),o.length>0?`@theme { +`);return`${n}${a}`}function ee(e,t,r={}){let n=S(e,t);return F(n,r)}var z={generateCssVariables:ee,generateFromAst:F,createVarName:g};function te(e,t,r){let{prefix:n}=r,o=[],i=[],s=new Set;e.forEach(l=>{if(s.has(l.token.name))return;s.add(l.token.name);let c=l.values.find(D=>D.mode===t);if(!c)return;let u=C(c.value,e,g,n),d=v(u),f=typeof d=="string"&&d.includes("var("),N=` ${g(l.token.name,n)}: ${d};`;f?i.push(N):o.push(N)});let a=[];return o.length>0&&a.push(`@theme { ${o.join(` `)} -}`:""}function re(){return["@utility bg-* { background-color: --value(--color-*); }","@utility text-* { color: --value(--color-*); }","@utility border-* { border-color: --value(--color-*); }","@utility fill-* { fill: --value(--color-*); }","@utility stroke-* { stroke: --value(--color-*); }","@utility p-* { padding: --value(--spacing-*); }","@utility px-* { padding-left: --value(--spacing-*); padding-right: --value(--spacing-*); }","@utility py-* { padding-top: --value(--spacing-*); padding-bottom: --value(--spacing-*); }","@utility pt-* { padding-top: --value(--spacing-*); }","@utility pr-* { padding-right: --value(--spacing-*); }","@utility pb-* { padding-bottom: --value(--spacing-*); }","@utility pl-* { padding-left: --value(--spacing-*); }","@utility m-* { margin: --value(--spacing-*); }","@utility mx-* { margin-left: --value(--spacing-*); margin-right: --value(--spacing-*); }","@utility my-* { margin-top: --value(--spacing-*); margin-bottom: --value(--spacing-*); }","@utility mt-* { margin-top: --value(--spacing-*); }","@utility mr-* { margin-right: --value(--spacing-*); }","@utility mb-* { margin-bottom: --value(--spacing-*); }","@utility ml-* { margin-left: --value(--spacing-*); }","@utility w-* { width: --value(--spacing-*); }","@utility h-* { height: --value(--spacing-*); }","@utility min-w-* { min-width: --value(--spacing-*); }","@utility min-h-* { min-height: --value(--spacing-*); }","@utility max-w-* { max-width: --value(--spacing-*); }","@utility max-h-* { max-height: --value(--spacing-*); }","@utility gap-* { gap: --value(--spacing-*); }","@utility space-x-* { & > :not(:last-child) { --tw-space-x-reverse: 0; margin-inline-start: calc(--value(--spacing-*) * var(--tw-space-x-reverse)); margin-inline-end: calc(--value(--spacing-*) * calc(1 - var(--tw-space-x-reverse))); } }","@utility space-y-* { & > :not(:last-child) { --tw-space-y-reverse: 0; margin-block-start: calc(--value(--spacing-*) * var(--tw-space-y-reverse)); margin-block-end: calc(--value(--spacing-*) * calc(1 - var(--tw-space-y-reverse))); } }","@utility space-x-reverse { & > :not(:last-child) { --tw-space-x-reverse: 1; } }","@utility space-y-reverse { & > :not(:last-child) { --tw-space-y-reverse: 1; } }","@utility rounded-* { border-radius: --value(--spacing-*); }","@utility font-* { font-weight: --value(--font-weight-*); }","@utility tracking-* { letter-spacing: --value(--tracking-*); }","@utility shadow-* { box-shadow: --value(--shadow-*); }","@utility border-width-* { border-width: --value(--border-width-*); }","@utility z-* { z-index: --value(--z-index-*); }"]}function L(e,t){let{banner:r=""}=t,{tokens:n,collections:o}=e,i=[];r&&i.push(r),o.forEach(s=>{let l=n.filter(c=>c.token.collection===s.name);s.modes.forEach(c=>{let u=te(l,c,t);u&&(i.push(u),i.push(""))})});let a=re();return a.length>0&&(i.push(...a),i.push("")),i.join(` -`)}function ne(e,t,r={}){let n=b(e,t);return L(n,r)}var I={generateTailwindCSS:ne,generateFromAst:L,createVarName:C};var ae=oe(import.meta.url),le=ae.resolve("@cocso-ui/baseframe-sources"),ce=w.dirname(le);function F(){process.stdout.write(` +}`),i.length>0&&a.push(`@theme inline { +${i.join(` +`)} +}`),a.join(` + +`)}function re(){return["@utility bg-* { background-color: --value(--color-*); }","@utility text-* { color: --value(--color-*); }","@utility border-* { border-color: --value(--color-*); }","@utility fill-* { fill: --value(--color-*); }","@utility stroke-* { stroke: --value(--color-*); }","@utility p-* { padding: --value(--spacing-*); }","@utility px-* { padding-left: --value(--spacing-*); padding-right: --value(--spacing-*); }","@utility py-* { padding-top: --value(--spacing-*); padding-bottom: --value(--spacing-*); }","@utility pt-* { padding-top: --value(--spacing-*); }","@utility pr-* { padding-right: --value(--spacing-*); }","@utility pb-* { padding-bottom: --value(--spacing-*); }","@utility pl-* { padding-left: --value(--spacing-*); }","@utility m-* { margin: --value(--spacing-*); }","@utility mx-* { margin-left: --value(--spacing-*); margin-right: --value(--spacing-*); }","@utility my-* { margin-top: --value(--spacing-*); margin-bottom: --value(--spacing-*); }","@utility mt-* { margin-top: --value(--spacing-*); }","@utility mr-* { margin-right: --value(--spacing-*); }","@utility mb-* { margin-bottom: --value(--spacing-*); }","@utility ml-* { margin-left: --value(--spacing-*); }","@utility w-* { width: --value(--spacing-*); }","@utility h-* { height: --value(--spacing-*); }","@utility min-w-* { min-width: --value(--spacing-*); }","@utility min-h-* { min-height: --value(--spacing-*); }","@utility max-w-* { max-width: --value(--spacing-*); }","@utility max-h-* { max-height: --value(--spacing-*); }","@utility gap-* { gap: --value(--spacing-*); }","@utility space-x-* { & > :not(:last-child) { --tw-space-x-reverse: 0; margin-inline-start: calc(--value(--spacing-*) * var(--tw-space-x-reverse)); margin-inline-end: calc(--value(--spacing-*) * calc(1 - var(--tw-space-x-reverse))); } }","@utility space-y-* { & > :not(:last-child) { --tw-space-y-reverse: 0; margin-block-start: calc(--value(--spacing-*) * var(--tw-space-y-reverse)); margin-block-end: calc(--value(--spacing-*) * calc(1 - var(--tw-space-y-reverse))); } }","@utility space-x-reverse { & > :not(:last-child) { --tw-space-x-reverse: 1; } }","@utility space-y-reverse { & > :not(:last-child) { --tw-space-y-reverse: 1; } }","@utility rounded-* { border-radius: --value(--spacing-*); }","@utility font-* { font-weight: --value(--font-weight-*); }","@utility tracking-* { letter-spacing: --value(--tracking-*); }","@utility shadow-* { box-shadow: --value(--shadow-*); }","@utility border-width-* { border-width: --value(--border-width-*); }","@utility z-* { z-index: --value(--z-index-*); }"]}function M(e,t){let{banner:r=""}=t,{tokens:n,collections:o}=e,i=[];r&&i.push(r),o.forEach(a=>{let l=n.filter(c=>c.token.collection===a.name);a.modes.forEach(c=>{let u=te(l,c,t);u&&(i.push(u),i.push(""))})});let s=re();return s.length>0&&(i.push(...s),i.push("")),i.join(` +`)}function ne(e,t,r={}){let n=S(e,t);return M(n,r)}var P={generateTailwindCSS:ne,generateFromAst:M,createVarName:g};var ae=oe(import.meta.url),le=ae.resolve("@cocso-ui/baseframe-sources"),ce=I.dirname(le);function _(){process.stdout.write(` \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2588\u2588\u2554\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 @@ -16,4 +23,4 @@ ${o.join(` \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2550\u255D \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D -`)}function ue(e){let t=[];function r(n){let o=h.readdirSync(n);for(let i of o){let a=w.join(n,i),s=h.statSync(a);s.isDirectory()?r(a):s.isFile()&&/\.ya?ml$/.test(i)&&t.push(a)}}return r(e),t}function j(){let e=ue(ce),t=[],r=null;for(let n of e)try{let o=h.readFileSync(n,"utf-8"),i=ie.parse(o);i.kind==="Tokens"?t.push(i):i.kind==="TokenCollections"&&(r=i)}catch(o){console.warn(` \u274E failed to parse ${n}:`,o)}return r||(console.error(" \u274E collections.yaml not found"),process.exit(1)),{tokens:t,collections:r}}function de(e,t){let{tokens:r,collections:n}=j(),o=S.generateCssVariables(r,n,{prefix:t,banner:"",selectors:{global:{default:":root"}}});h.ensureDirSync(e);let i=w.join(e,"token.css");h.writeFileSync(i,o,"utf-8"),console.log(` \u2705 Generated CSS variables: ${i}`)}function fe(e,t){let{tokens:r,collections:n}=j(),o=I.generateTailwindCSS(r,n,{prefix:t,banner:""});h.ensureDirSync(e);let i=w.join(e,"tailwind4.css");h.writeFileSync(i,o,"utf-8"),console.log(` \u2705 Generated TailwindCSS 4.0 configuration: ${i}`)}se(process.argv.slice(2)).command("css-vars [dir] [prefix]","Generate CSS variables",e=>e.positional("dir",{describe:"Output directory",type:"string",default:"./dist/"}).option("prefix",{describe:"CSS variable prefix",type:"string"}),e=>{F(),de(e.dir,e.prefix)}).command("tailwindcss [dir] [prefix]","Generate TailwindCSS 4.0 configuration",e=>e.positional("dir",{describe:"Output directory",type:"string",default:"./dist/"}).option("prefix",{describe:"CSS variable prefix",type:"string"}),e=>{F(),fe(e.dir,e.prefix)}).demandCommand(1,"You need to specify a command.").showHelpOnFail(!0).help().argv; +`)}function ue(e){let t=[];function r(n){let o=k.readdirSync(n);for(let i of o){let s=I.join(n,i),a=k.statSync(s);a.isDirectory()?r(s):a.isFile()&&/\.ya?ml$/.test(i)&&t.push(s)}}return r(e),t}function Y(){let e=ue(ce),t=[],r=null;for(let n of e)try{let o=k.readFileSync(n,"utf-8"),i=ie.parse(o);i.kind==="Tokens"?t.push(i):i.kind==="TokenCollections"&&(r=i)}catch(o){console.warn(` \u274E failed to parse ${n}:`,o)}return r||(console.error(" \u274E collections.yaml not found"),process.exit(1)),{tokens:t,collections:r}}function de(e,t){let{tokens:r,collections:n}=Y(),o=z.generateCssVariables(r,n,{prefix:t,banner:"",selectors:{global:{default:":root"}}});k.ensureDirSync(e);let i=I.join(e,"token.css");k.writeFileSync(i,o,"utf-8"),console.log(` \u2705 Generated CSS variables: ${i}`)}function fe(e,t){let{tokens:r,collections:n}=Y(),o=P.generateTailwindCSS(r,n,{prefix:t,banner:""});k.ensureDirSync(e);let i=I.join(e,"tailwind4.css");k.writeFileSync(i,o,"utf-8"),console.log(` \u2705 Generated TailwindCSS 4.0 configuration: ${i}`)}se(process.argv.slice(2)).command("css-vars [dir] [prefix]","Generate CSS variables",e=>e.positional("dir",{describe:"Output directory",type:"string",default:"./dist/"}).option("prefix",{describe:"CSS variable prefix",type:"string"}),e=>{_(),de(e.dir,e.prefix)}).command("tailwindcss [dir] [prefix]","Generate TailwindCSS 4.0 configuration",e=>e.positional("dir",{describe:"Output directory",type:"string",default:"./dist/"}).option("prefix",{describe:"CSS variable prefix",type:"string"}),e=>{_(),fe(e.dir,e.prefix)}).demandCommand(1,"You need to specify a command.").showHelpOnFail(!0).help().argv; diff --git a/ecosystem/baseframe/package.json b/ecosystem/baseframe/package.json index cd70a29b..e2f2c902 100644 --- a/ecosystem/baseframe/package.json +++ b/ecosystem/baseframe/package.json @@ -13,7 +13,9 @@ "build": "node build.js", "check-types": "tsc --noEmit", "lint": "biome lint .", - "lint:fix": "biome lint . --fix" + "lint:fix": "biome lint . --fix", + "test": "vitest run", + "test:watch": "vitest" }, "dependencies": { "fs-extra": "^11.3.2", @@ -24,7 +26,8 @@ "@types/fs-extra": "^11.0.4", "@types/yargs": "^17.0.35", "esbuild": "^0.25.12", - "typescript": "catalog:core" + "typescript": "catalog:core", + "vitest": "^4.0.18" }, "publishConfig": { "access": "public" diff --git a/ecosystem/baseframe/src/core/builders/__tests__/css-vars.test.ts b/ecosystem/baseframe/src/core/builders/__tests__/css-vars.test.ts new file mode 100644 index 00000000..c63b777f --- /dev/null +++ b/ecosystem/baseframe/src/core/builders/__tests__/css-vars.test.ts @@ -0,0 +1,177 @@ +import { describe, expect, it } from 'vitest'; +import type { Ast, Collections, Token, TokenDecl } from '../../types'; +import { generateCssVariables, generateFromAst } from '../css-vars'; + +const collections: Collections = { + kind: 'TokenCollections', + metadata: { id: 'test', name: 'Test' }, + data: [ + { name: 'color', modes: ['default', 'dark'] }, + { name: 'spacing', modes: ['default'] }, + ], +}; + +const validTokens: Token[] = [ + { + kind: 'Tokens', + metadata: { id: 'color', name: 'Color', description: '' }, + data: { + collection: 'color', + tokens: { + '$color.primary': { values: { default: '#ff0000', dark: '#cc0000' } }, + '$color.secondary': { values: { default: '#00ff00', dark: '#00cc00' } }, + }, + }, + }, + { + kind: 'Tokens', + metadata: { id: 'spacing', name: 'Spacing', description: '' }, + data: { + collection: 'spacing', + tokens: { + '$spacing.1': { values: { default: '4px' } }, + }, + }, + }, +]; + +function createAst(): Ast { + const tokens: TokenDecl[] = [ + { + token: { name: '$color.primary', collection: 'color' }, + values: [ + { mode: 'default', value: '#ff0000' }, + { mode: 'dark', value: '#cc0000' }, + ], + }, + { + token: { name: '$spacing.1', collection: 'spacing' }, + values: [{ mode: 'default', value: '4px' }], + }, + ]; + + return { + collections: [ + { name: 'color', modes: ['default', 'dark'] }, + { name: 'spacing', modes: ['default'] }, + ], + tokens, + }; +} + +describe('generateFromAst', () => { + it('generates CSS variables with :root selector', () => { + const ast = createAst(); + const result = generateFromAst(ast, {}); + expect(result).toContain(':root'); + expect(result).toContain('--color-primary'); + expect(result).toContain('#ff0000'); + }); + + it('generates separate rules per mode', () => { + const ast = createAst(); + const result = generateFromAst(ast, {}); + const rootCount = (result.match(/:root/g) || []).length; + expect(rootCount).toBeGreaterThanOrEqual(2); + }); + + it('applies custom selectors', () => { + const ast = createAst(); + const result = generateFromAst(ast, { + selectors: { + color: { + default: ':root', + dark: '[data-theme="dark"]', + }, + }, + }); + expect(result).toContain('[data-theme="dark"]'); + expect(result).toContain('#cc0000'); + }); + + it('places dark mode values under correct selector', () => { + const ast = createAst(); + const result = generateFromAst(ast, { + selectors: { + color: { + default: ':root', + dark: '[data-theme="dark"]', + }, + }, + }); + const darkBlock = result.slice(result.indexOf('[data-theme="dark"]')); + expect(darkBlock).toContain('#cc0000'); + expect(darkBlock).not.toContain('#ff0000'); + }); + + it('applies prefix to variable names', () => { + const ast = createAst(); + const result = generateFromAst(ast, { prefix: 'ds' }); + expect(result).toContain('--ds-color-primary'); + expect(result).toContain('--ds-spacing-1'); + }); + + it('prepends banner', () => { + const ast = createAst(); + const banner = '/* Auto-generated */\n'; + const result = generateFromAst(ast, { banner }); + expect(result.startsWith(banner)).toBe(true); + }); + + it('generates valid CSS structure', () => { + const ast = createAst(); + const result = generateFromAst(ast, {}); + const openBraces = (result.match(/{/g) || []).length; + const closeBraces = (result.match(/}/g) || []).length; + expect(openBraces).toBe(closeBraces); + }); +}); + +describe('generateCssVariables', () => { + it('generates end-to-end from tokens and collections', () => { + const result = generateCssVariables(validTokens, collections); + expect(result).toContain('--color-primary'); + expect(result).toContain('--spacing-1'); + expect(result).toContain('#ff0000'); + expect(result).toContain('4px'); + }); + + it('applies options', () => { + const result = generateCssVariables(validTokens, collections, { prefix: 'ds' }); + expect(result).toContain('--ds-color-primary'); + }); + + it('throws on invalid tokens', () => { + const invalidTokens: Token[] = [ + { + kind: 'Tokens', + metadata: { id: 'unknown', name: 'Unknown', description: '' }, + data: { + collection: 'unknown', + tokens: { '$unknown.foo': { values: { default: '#000000' } } }, + }, + }, + ]; + + expect(() => generateCssVariables(invalidTokens, collections)).toThrow(); + }); + + it('resolves token references to var()', () => { + const tokensWithRef: Token[] = [ + { + kind: 'Tokens', + metadata: { id: 'color', name: 'Color', description: '' }, + data: { + collection: 'color', + tokens: { + '$color.base': { values: { default: '#ff0000', dark: '#cc0000' } }, + '$color.primary': { values: { default: '$color.base', dark: '$color.base' } }, + }, + }, + }, + ]; + + const result = generateCssVariables(tokensWithRef, collections); + expect(result).toContain('var(--color-base)'); + }); +}); diff --git a/ecosystem/baseframe/src/core/builders/__tests__/tailwind.test.ts b/ecosystem/baseframe/src/core/builders/__tests__/tailwind.test.ts new file mode 100644 index 00000000..5963dcd1 --- /dev/null +++ b/ecosystem/baseframe/src/core/builders/__tests__/tailwind.test.ts @@ -0,0 +1,170 @@ +import { describe, expect, it } from 'vitest'; +import type { Ast, Collections, Token, TokenDecl } from '../../types'; +import { generateFromAst, generateTailwindCSS } from '../tailwind'; + +const collections: Collections = { + kind: 'TokenCollections', + metadata: { id: 'test', name: 'Test' }, + data: [ + { name: 'color', modes: ['default'] }, + { name: 'spacing', modes: ['default'] }, + ], +}; + +const validTokens: Token[] = [ + { + kind: 'Tokens', + metadata: { id: 'color', name: 'Color', description: '' }, + data: { + collection: 'color', + tokens: { + '$color.primary': { values: { default: '#ff0000' } }, + }, + }, + }, + { + kind: 'Tokens', + metadata: { id: 'spacing', name: 'Spacing', description: '' }, + data: { + collection: 'spacing', + tokens: { + '$spacing.1': { values: { default: '4px' } }, + }, + }, + }, +]; + +function createAst(): Ast { + const tokens: TokenDecl[] = [ + { + token: { name: '$color.primary', collection: 'color' }, + values: [{ mode: 'default', value: '#ff0000' }], + }, + { + token: { name: '$spacing.1', collection: 'spacing' }, + values: [{ mode: 'default', value: '4px' }], + }, + ]; + + return { + collections: [ + { name: 'color', modes: ['default'] }, + { name: 'spacing', modes: ['default'] }, + ], + tokens, + }; +} + +describe('generateFromAst', () => { + it('generates @theme block with primitive vars', () => { + const ast = createAst(); + const result = generateFromAst(ast, {}); + expect(result).toContain('@theme {'); + expect(result).toContain('--color-primary: #ff0000;'); + expect(result).toContain('--spacing-1: 4px;'); + }); + + it('generates @theme inline for vars referencing other vars', () => { + const tokens: TokenDecl[] = [ + { + token: { name: '$color.base', collection: 'color' }, + values: [{ mode: 'default', value: '#ff0000' }], + }, + { + token: { name: '$color.primary', collection: 'color' }, + values: [{ mode: 'default', value: '$color.base' }], + }, + ]; + const ast: Ast = { + collections: [{ name: 'color', modes: ['default'] }], + tokens, + }; + + const result = generateFromAst(ast, {}); + expect(result).toContain('@theme inline {'); + expect(result).toContain('var(--color-base)'); + }); + + it('applies prefix to variable names', () => { + const ast = createAst(); + const result = generateFromAst(ast, { prefix: 'ds' }); + expect(result).toContain('--ds-color-primary'); + expect(result).toContain('--ds-spacing-1'); + }); + + it('prepends banner', () => { + const ast = createAst(); + const banner = '/* Tailwind theme */'; + const result = generateFromAst(ast, { banner }); + expect(result.startsWith(banner)).toBe(true); + }); + + it('generates utility definitions', () => { + const ast = createAst(); + const result = generateFromAst(ast, {}); + expect(result).toContain('@utility bg-*'); + expect(result).toContain('@utility text-*'); + expect(result).toContain('@utility p-*'); + expect(result).toContain('@utility rounded-*'); + expect(result).toContain('@utility shadow-*'); + }); + + it('deduplicates tokens across modes', () => { + const tokens: TokenDecl[] = [ + { + token: { name: '$color.primary', collection: 'color' }, + values: [ + { mode: 'default', value: '#ff0000' }, + { mode: 'dark', value: '#cc0000' }, + ], + }, + ]; + const ast: Ast = { + collections: [{ name: 'color', modes: ['default', 'dark'] }], + tokens, + }; + + const result = generateFromAst(ast, {}); + const themeBlock = result.slice(result.indexOf('@theme {'), result.indexOf('}') + 1); + const matches = themeBlock.match(/--color-primary/g) || []; + expect(matches.length).toBe(1); + }); +}); + +describe('generateTailwindCSS', () => { + it('generates end-to-end from tokens and collections', () => { + const result = generateTailwindCSS(validTokens, collections); + expect(result).toContain('@theme'); + expect(result).toContain('--color-primary'); + expect(result).toContain('--spacing-1'); + expect(result).toContain('@utility'); + }); + + it('applies options', () => { + const result = generateTailwindCSS(validTokens, collections, { prefix: 'ds' }); + expect(result).toContain('--ds-color-primary'); + }); + + it('throws on invalid tokens', () => { + const invalidTokens: Token[] = [ + { + kind: 'Tokens', + metadata: { id: 'unknown', name: 'Unknown', description: '' }, + data: { + collection: 'unknown', + tokens: { '$unknown.foo': { values: { default: '#000000' } } }, + }, + }, + ]; + + expect(() => generateTailwindCSS(invalidTokens, collections)).toThrow(); + }); + + it('generates valid output structure', () => { + const result = generateTailwindCSS(validTokens, collections); + expect(result).toContain('@theme {'); + expect(result).toContain('@utility bg-*'); + expect(result).not.toContain('undefined'); + expect(result).not.toContain('NaN'); + }); +}); diff --git a/ecosystem/baseframe/src/core/builders/css-vars.ts b/ecosystem/baseframe/src/core/builders/css-vars.ts index 79998060..70cf3646 100644 --- a/ecosystem/baseframe/src/core/builders/css-vars.ts +++ b/ecosystem/baseframe/src/core/builders/css-vars.ts @@ -1,6 +1,6 @@ import { buildValidatedAst } from '../transforms'; import type { Ast, Collections, Token, TokenDecl } from '../types'; -import { resolveTokenValue, toCssValue } from './utils'; +import { createVarName, resolveTokenValue, toCssValue } from './utils'; export interface CssVarsOptions { prefix?: string; @@ -12,11 +12,6 @@ export interface CssVarsOptions { }; } -function createVarName(name: string, prefix?: string): string { - const clean = name.replace(/^\$/, '').replace(/\./g, '-'); - return prefix ? `--${prefix}-${clean}` : `--${clean}`; -} - function createDeclaration( token: TokenDecl, mode: string, diff --git a/ecosystem/baseframe/src/core/builders/tailwind.ts b/ecosystem/baseframe/src/core/builders/tailwind.ts index 8a75973a..cf44aa69 100644 --- a/ecosystem/baseframe/src/core/builders/tailwind.ts +++ b/ecosystem/baseframe/src/core/builders/tailwind.ts @@ -1,22 +1,16 @@ import { buildValidatedAst } from '../transforms'; import type { Ast, Collections, Token, TokenDecl } from '../types'; -import { resolveTokenValue, toCssValue } from './utils'; +import { createVarName, resolveTokenValue, toCssValue } from './utils'; export interface TailwindOptions { prefix?: string; banner?: string; } -function createVarName(name: string, prefix?: string): string { - let clean = name.replace(/^\$/, ''); - clean = clean.replace(/\./g, '-'); - - return prefix ? `--${prefix}-${clean}` : `--${clean}`; -} - function createTheme(tokens: TokenDecl[], mode: string, options: TailwindOptions): string { const { prefix } = options; - const vars: string[] = []; + const primitiveVars: string[] = []; + const inlineVars: string[] = []; const processed = new Set(); tokens.forEach(token => { @@ -32,16 +26,29 @@ function createTheme(tokens: TokenDecl[], mode: string, options: TailwindOptions const resolved = resolveTokenValue(value.value, tokens, createVarName, prefix); const css = toCssValue(resolved); - - if (token.token.name === '$number.1') { - vars.push(` --spacing: ${css};`); - } + const hasVarRef = typeof css === 'string' && css.includes('var('); const varName = createVarName(token.token.name, prefix); - vars.push(` ${varName}: ${css};`); + const line = ` ${varName}: ${css};`; + + if (hasVarRef) { + inlineVars.push(line); + } else { + primitiveVars.push(line); + } }); - return vars.length > 0 ? `@theme {\n${vars.join('\n')}\n}` : ''; + const parts: string[] = []; + + if (primitiveVars.length > 0) { + parts.push(`@theme {\n${primitiveVars.join('\n')}\n}`); + } + + if (inlineVars.length > 0) { + parts.push(`@theme inline {\n${inlineVars.join('\n')}\n}`); + } + + return parts.join('\n\n'); } function createUtilities(): string[] { diff --git a/ecosystem/baseframe/src/core/builders/utils/__tests__/css.test.ts b/ecosystem/baseframe/src/core/builders/utils/__tests__/css.test.ts new file mode 100644 index 00000000..b9ec46d7 --- /dev/null +++ b/ecosystem/baseframe/src/core/builders/utils/__tests__/css.test.ts @@ -0,0 +1,101 @@ +import { describe, expect, it } from 'vitest'; +import type { TokenDecl, Value } from '../../../types'; +import { resolveTokenValue, toCssValue } from '../css'; + +describe('toCssValue', () => { + it('returns string value as-is', () => { + expect(toCssValue('16px')).toBe('16px'); + }); + + it('converts number to string', () => { + expect(toCssValue(42)).toBe('42'); + }); + + it('converts HexColor value', () => { + const value: Value = { kind: 'HexColor', value: '#ff0000' }; + expect(toCssValue(value)).toBe('#ff0000'); + }); + + it('converts SizeValue', () => { + const value: Value = { kind: 'SizeValue', value: 16, unit: 'px' }; + expect(toCssValue(value)).toBe('16px'); + }); + + it('converts RgbColor value', () => { + const value: Value = { kind: 'RgbColor', r: 255, g: 0, b: 0 }; + expect(toCssValue(value)).toBe('rgb(255, 0, 0)'); + }); + + it('converts NumberValue', () => { + const value: Value = { kind: 'NumberValue', value: 1.5 }; + expect(toCssValue(value)).toBe('1.5'); + }); + + it('converts StringValue', () => { + const value: Value = { kind: 'StringValue', value: 'auto' }; + expect(toCssValue(value)).toBe('auto'); + }); + + it('converts DurationValue', () => { + const value: Value = { kind: 'DurationValue', value: 300, unit: 'ms' }; + expect(toCssValue(value)).toBe('300ms'); + }); + + it('converts RgbaColor value', () => { + const value: Value = { kind: 'RgbaColor', r: 0, g: 0, b: 0, a: 0.5 }; + expect(toCssValue(value)).toBe('rgba(0, 0, 0, 0.5)'); + }); +}); + +describe('resolveTokenValue', () => { + const allTokens: TokenDecl[] = [ + { + token: { name: '$color.primary', collection: 'color' }, + values: [{ mode: 'default', value: '#ff0000' }], + }, + { + token: { name: '$spacing.1', collection: 'spacing' }, + values: [{ mode: 'default', value: '4px' }], + }, + ]; + + const resolver = (name: string, prefix?: string) => { + const clean = name.replace(/^\$/, '').replace(/\./g, '-'); + return prefix ? `--${prefix}-${clean}` : `--${clean}`; + }; + + it('resolves token reference to var()', () => { + const result = resolveTokenValue('$color.primary', allTokens, resolver); + expect(result).toBe('var(--color-primary)'); + }); + + it('resolves token reference with prefix', () => { + const result = resolveTokenValue('$color.primary', allTokens, resolver, 'ds'); + expect(result).toBe('var(--ds-color-primary)'); + }); + + it('returns plain string values unchanged', () => { + expect(resolveTokenValue('auto', allTokens, resolver)).toBe('auto'); + }); + + it('converts number values to string via parseValue', () => { + expect(resolveTokenValue(42, allTokens, resolver)).toBe('42'); + }); + + it('resolves shadow with embedded token refs', () => { + const result = resolveTokenValue('0px 4px 8px 0px $color.primary', allTokens, resolver); + expect(result).toContain('var(--color-primary)'); + }); + + it('returns plain hex color unchanged', () => { + expect(resolveTokenValue('#ff0000', allTokens, resolver)).toBe('#ff0000'); + }); + + it('returns size value unchanged', () => { + expect(resolveTokenValue('16px', allTokens, resolver)).toBe('16px'); + }); + + it('throws for invalid token reference', () => { + expect(() => resolveTokenValue('$nonexistent.ref', allTokens, resolver)).toThrow(); + }); +}); diff --git a/ecosystem/baseframe/src/core/builders/utils/__tests__/naming.test.ts b/ecosystem/baseframe/src/core/builders/utils/__tests__/naming.test.ts new file mode 100644 index 00000000..a716c0f1 --- /dev/null +++ b/ecosystem/baseframe/src/core/builders/utils/__tests__/naming.test.ts @@ -0,0 +1,36 @@ +import { describe, expect, it } from 'vitest'; +import { createVarName } from '../naming'; + +describe('createVarName', () => { + it('strips $ prefix and replaces dots with hyphens', () => { + expect(createVarName('$color.primary')).toBe('--color-primary'); + }); + + it('handles nested paths', () => { + expect(createVarName('$color.semantic.primary')).toBe('--color-semantic-primary'); + }); + + it('adds prefix when provided', () => { + expect(createVarName('$color.primary', 'ds')).toBe('--ds-color-primary'); + }); + + it('handles name without $ prefix', () => { + expect(createVarName('color.primary')).toBe('--color-primary'); + }); + + it('handles name without $ prefix with custom prefix', () => { + expect(createVarName('color.primary', 'ds')).toBe('--ds-color-primary'); + }); + + it('handles single segment name', () => { + expect(createVarName('$color')).toBe('--color'); + }); + + it('handles name with hyphens', () => { + expect(createVarName('$my-collection.my-token')).toBe('--my-collection-my-token'); + }); + + it('handles name with underscores', () => { + expect(createVarName('$my_collection.my_token')).toBe('--my_collection-my_token'); + }); +}); diff --git a/ecosystem/baseframe/src/core/builders/utils/css.ts b/ecosystem/baseframe/src/core/builders/utils/css.ts index 969fff16..6ae353ee 100644 --- a/ecosystem/baseframe/src/core/builders/utils/css.ts +++ b/ecosystem/baseframe/src/core/builders/utils/css.ts @@ -21,9 +21,7 @@ export function resolveTokenValue( if (!text.startsWith('$')) { const parsed = parseValue(text); if (parsed.isValid && parsed.value) { - const tokenResolver = createTokenResolver(allTokens, 'default', name => - resolver(name, prefix), - ); + const tokenResolver = createTokenResolver(allTokens, name => resolver(name, prefix)); const resolved = tokenResolver.resolve(parsed.value); return toCssValue(resolved); } @@ -35,6 +33,6 @@ export function resolveTokenValue( throw new Error(`Invalid token reference: ${text}`); } - const tokenResolver = createTokenResolver(allTokens, 'default', name => resolver(name, prefix)); + const tokenResolver = createTokenResolver(allTokens, name => resolver(name, prefix)); return tokenResolver.resolveTokenRef(parsed.value as TokenRef); } diff --git a/ecosystem/baseframe/src/core/builders/utils/index.ts b/ecosystem/baseframe/src/core/builders/utils/index.ts index 5be79f04..049bb181 100644 --- a/ecosystem/baseframe/src/core/builders/utils/index.ts +++ b/ecosystem/baseframe/src/core/builders/utils/index.ts @@ -1 +1,2 @@ export * from './css'; +export * from './naming'; diff --git a/ecosystem/baseframe/src/core/builders/utils/naming.ts b/ecosystem/baseframe/src/core/builders/utils/naming.ts new file mode 100644 index 00000000..4da93d37 --- /dev/null +++ b/ecosystem/baseframe/src/core/builders/utils/naming.ts @@ -0,0 +1,4 @@ +export function createVarName(name: string, prefix?: string): string { + const clean = name.replace(/^\$/, '').replace(/\./g, '-'); + return prefix ? `--${prefix}-${clean}` : `--${clean}`; +} diff --git a/ecosystem/baseframe/src/core/parsers/__tests__/ast.test.ts b/ecosystem/baseframe/src/core/parsers/__tests__/ast.test.ts new file mode 100644 index 00000000..2dc49aba --- /dev/null +++ b/ecosystem/baseframe/src/core/parsers/__tests__/ast.test.ts @@ -0,0 +1,109 @@ +import { describe, expect, it } from 'vitest'; +import type { Collections, Token } from '../../types'; +import { buildAst } from '../ast'; + +const collections: Collections = { + kind: 'TokenCollections', + metadata: { id: 'test', name: 'Test' }, + data: [ + { name: 'color', modes: ['default', 'dark'] }, + { name: 'spacing', modes: ['default'] }, + ], +}; + +describe('buildAst', () => { + it('builds AST from single token file', () => { + const tokens: Token[] = [ + { + kind: 'Tokens', + metadata: { id: 'color', name: 'Color', description: '' }, + data: { + collection: 'color', + tokens: { + '$color.primary': { + values: { default: '#ff0000', dark: '#cc0000' }, + }, + }, + }, + }, + ]; + + const ast = buildAst(tokens, collections); + + expect(ast.collections).toEqual([ + { name: 'color', modes: ['default', 'dark'] }, + { name: 'spacing', modes: ['default'] }, + ]); + expect(ast.tokens).toHaveLength(1); + expect(ast.tokens[0]).toEqual({ + token: { name: '$color.primary', collection: 'color' }, + values: [ + { mode: 'default', value: '#ff0000' }, + { mode: 'dark', value: '#cc0000' }, + ], + }); + }); + + it('builds AST from multiple token files', () => { + const tokens: Token[] = [ + { + kind: 'Tokens', + metadata: { id: 'color', name: 'Color', description: '' }, + data: { + collection: 'color', + tokens: { + '$color.primary': { values: { default: '#ff0000', dark: '#cc0000' } }, + '$color.secondary': { values: { default: '#00ff00', dark: '#00cc00' } }, + }, + }, + }, + { + kind: 'Tokens', + metadata: { id: 'spacing', name: 'Spacing', description: '' }, + data: { + collection: 'spacing', + tokens: { + '$spacing.1': { values: { default: '4px' } }, + }, + }, + }, + ]; + + const ast = buildAst(tokens, collections); + expect(ast.tokens).toHaveLength(3); + expect(ast.tokens.map(t => t.token.name)).toEqual([ + '$color.primary', + '$color.secondary', + '$spacing.1', + ]); + }); + + it('handles empty token list', () => { + const ast = buildAst([], collections); + expect(ast.tokens).toEqual([]); + expect(ast.collections).toHaveLength(2); + }); + + it('preserves token value types', () => { + const tokens: Token[] = [ + { + kind: 'Tokens', + metadata: { id: 'spacing', name: 'Spacing', description: '' }, + data: { + collection: 'spacing', + tokens: { + '$spacing.1': { values: { default: '4px' } }, + '$spacing.scale': { values: { default: 1.5 } }, + }, + }, + }, + ]; + + const ast = buildAst(tokens, collections); + const stringToken = ast.tokens.find(t => t.token.name === '$spacing.1'); + const numberToken = ast.tokens.find(t => t.token.name === '$spacing.scale'); + + expect(stringToken?.values[0].value).toBe('4px'); + expect(numberToken?.values[0].value).toBe(1.5); + }); +}); diff --git a/ecosystem/baseframe/src/core/parsers/__tests__/primitives.test.ts b/ecosystem/baseframe/src/core/parsers/__tests__/primitives.test.ts new file mode 100644 index 00000000..47b542ee --- /dev/null +++ b/ecosystem/baseframe/src/core/parsers/__tests__/primitives.test.ts @@ -0,0 +1,213 @@ +import { describe, expect, it } from 'vitest'; +import { + parseDuration, + parseHex, + parseRgb, + parseRgba, + parseSize, + parseTokenRef, +} from '../primitives'; + +describe('parseHex', () => { + it.each([ + ['#fff', '#fff'], + ['#FFF', '#FFF'], + ['#000', '#000'], + ['#ffff', '#ffff'], + ['#ffffff', '#ffffff'], + ['#FFFFFF', '#FFFFFF'], + ['#00000000', '#00000000'], + ['#aaBBcc', '#aaBBcc'], + ['#aaBBccDD', '#aaBBccDD'], + ])('parses %s', (input, expected) => { + const result = parseHex(input); + expect(result.isValid).toBe(true); + expect(result.value).toEqual({ kind: 'HexColor', value: expected }); + }); + + it.each([ + '', + 'fff', + '#gg0000', + '#ff', + '#fffff', + '#fffffffff', + 'rgb(0,0,0)', + '#', + ])('rejects %s', input => { + const result = parseHex(input); + expect(result.isValid).toBe(false); + expect(result.error).toBeDefined(); + }); +}); + +describe('parseRgb', () => { + it('parses valid rgb', () => { + const result = parseRgb('rgb(255, 128, 0)'); + expect(result).toEqual({ + isValid: true, + value: { kind: 'RgbColor', r: 255, g: 128, b: 0 }, + }); + }); + + it('parses rgb with no spaces', () => { + const result = parseRgb('rgb(0,0,0)'); + expect(result).toEqual({ + isValid: true, + value: { kind: 'RgbColor', r: 0, g: 0, b: 0 }, + }); + }); + + it('parses rgb with extra spaces', () => { + const result = parseRgb('rgb( 10 , 20 , 30 )'); + expect(result).toEqual({ + isValid: true, + value: { kind: 'RgbColor', r: 10, g: 20, b: 30 }, + }); + }); + + it('rejects out-of-range values', () => { + const result = parseRgb('rgb(256, 0, 0)'); + expect(result.isValid).toBe(false); + }); + + it('rejects negative values', () => { + expect(parseRgb('rgb(-1, 0, 0)').isValid).toBe(false); + }); + + it.each([ + 'rgb()', + 'rgb(1, 2)', + 'rgb(1, 2, 3, 4)', + 'rgba(0, 0, 0)', + 'not-a-color', + ])('rejects %s', input => { + expect(parseRgb(input).isValid).toBe(false); + }); +}); + +describe('parseRgba', () => { + it('parses valid rgba', () => { + const result = parseRgba('rgba(255, 128, 0, 0.5)'); + expect(result).toEqual({ + isValid: true, + value: { kind: 'RgbaColor', r: 255, g: 128, b: 0, a: 0.5 }, + }); + }); + + it('parses alpha = 0', () => { + const result = parseRgba('rgba(0, 0, 0, 0)'); + expect(result.isValid).toBe(true); + expect(result.value).toEqual({ kind: 'RgbaColor', r: 0, g: 0, b: 0, a: 0 }); + }); + + it('parses alpha = 1', () => { + const result = parseRgba('rgba(0, 0, 0, 1)'); + expect(result.isValid).toBe(true); + expect(result.value).toEqual({ kind: 'RgbaColor', r: 0, g: 0, b: 0, a: 1 }); + }); + + it('rejects alpha > 1', () => { + expect(parseRgba('rgba(0, 0, 0, 1.5)').isValid).toBe(false); + }); + + it('rejects out-of-range rgb', () => { + expect(parseRgba('rgba(256, 0, 0, 0.5)').isValid).toBe(false); + }); + + it.each(['rgba()', 'rgba(1, 2, 3)', 'rgb(1, 2, 3, 0.5)'])('rejects %s', input => { + expect(parseRgba(input).isValid).toBe(false); + }); +}); + +describe('parseTokenRef', () => { + it('parses simple token ref', () => { + const result = parseTokenRef('$color.primary'); + expect(result).toEqual({ + isValid: true, + value: { kind: 'TokenRef', collection: 'color', token: 'primary' }, + }); + }); + + it('parses nested collection ref', () => { + const result = parseTokenRef('$color.semantic.primary'); + expect(result).toEqual({ + isValid: true, + value: { kind: 'TokenRef', collection: 'color.semantic', token: 'primary' }, + }); + }); + + it('parses ref with hyphens', () => { + const result = parseTokenRef('$my-collection.my-token'); + expect(result).toEqual({ + isValid: true, + value: { kind: 'TokenRef', collection: 'my-collection', token: 'my-token' }, + }); + }); + + it('parses ref with underscores', () => { + const result = parseTokenRef('$my_collection.my_token'); + expect(result).toEqual({ + isValid: true, + value: { kind: 'TokenRef', collection: 'my_collection', token: 'my_token' }, + }); + }); + + it.each([ + '$', + '$single', + 'no-dollar.ref', + '', + '$$double.ref', + '$collection.', + '$.token', + ])('rejects %s', input => { + expect(parseTokenRef(input).isValid).toBe(false); + }); +}); + +describe('parseSize', () => { + it.each([ + ['10px', 10, 'px'], + ['1.5rem', 1.5, 'rem'], + ['2em', 2, 'em'], + ['100vw', 100, 'vw'], + ['50vh', 50, 'vh'], + ['75%', 75, '%'], + ['0px', 0, 'px'], + ['-10px', -10, 'px'], + ['.5rem', 0.5, 'rem'], + ] as const)('parses %s → %d%s', (input, value, unit) => { + const result = parseSize(input); + expect(result.isValid).toBe(true); + expect(result.value).toEqual({ kind: 'SizeValue', value, unit }); + }); + + it.each(['10', 'px', '10xyz', '', 'auto', '10 px'])('rejects %s', input => { + expect(parseSize(input).isValid).toBe(false); + }); +}); + +describe('parseDuration', () => { + it.each([ + ['100ms', 100, 'ms'], + ['1s', 1, 's'], + ['0.3s', 0.3, 's'], + ['250ms', 250, 'ms'], + ['0ms', 0, 'ms'], + ] as const)('parses %s → %d%s', (input, value, unit) => { + const result = parseDuration(input); + expect(result.isValid).toBe(true); + expect(result.value).toEqual({ kind: 'DurationValue', value, unit }); + }); + + it('allows negative duration (parser does not enforce non-negative)', () => { + const result = parseDuration('-100ms'); + expect(result.isValid).toBe(true); + expect(result.value).toEqual({ kind: 'DurationValue', value: -100, unit: 'ms' }); + }); + + it.each(['100', 'ms', '1m', '', '1sec'])('rejects %s', input => { + expect(parseDuration(input).isValid).toBe(false); + }); +}); diff --git a/ecosystem/baseframe/src/core/parsers/__tests__/shadow.test.ts b/ecosystem/baseframe/src/core/parsers/__tests__/shadow.test.ts new file mode 100644 index 00000000..9737fd5e --- /dev/null +++ b/ecosystem/baseframe/src/core/parsers/__tests__/shadow.test.ts @@ -0,0 +1,127 @@ +import { describe, expect, it } from 'vitest'; +import { parseColor, parseShadow } from '../shadow'; + +describe('parseColor', () => { + it('parses hex color', () => { + const result = parseColor('#ff0000'); + expect(result.isValid).toBe(true); + expect(result.value).toEqual({ kind: 'HexColor', value: '#ff0000' }); + }); + + it('parses rgb color', () => { + const result = parseColor('rgb(255, 0, 0)'); + expect(result.isValid).toBe(true); + expect(result.value).toEqual({ kind: 'RgbColor', r: 255, g: 0, b: 0 }); + }); + + it('parses rgba color', () => { + const result = parseColor('rgba(0, 0, 0, 0.5)'); + expect(result.isValid).toBe(true); + expect(result.value).toEqual({ kind: 'RgbaColor', r: 0, g: 0, b: 0, a: 0.5 }); + }); + + it('parses token ref as color', () => { + const result = parseColor('$color.primary'); + expect(result.isValid).toBe(true); + expect(result.value).toEqual({ kind: 'TokenRef', collection: 'color', token: 'primary' }); + }); + + it('passes through var() references', () => { + const result = parseColor('var(--my-color)'); + expect(result.isValid).toBe(true); + expect(result.value).toEqual({ kind: 'StringValue', value: 'var(--my-color)' }); + }); + + it('rejects empty string', () => { + expect(parseColor('').isValid).toBe(false); + }); + + it('rejects invalid color', () => { + expect(parseColor('not-a-color').isValid).toBe(false); + }); + + it('rejects named CSS color', () => { + expect(parseColor('red').isValid).toBe(false); + }); +}); + +describe('parseShadow', () => { + it('parses single layer with hex color', () => { + const result = parseShadow('0px 4px 8px 0px #000000'); + expect(result.isValid).toBe(true); + expect(result.value).toMatchObject({ + kind: 'Shadow', + layers: [ + { + kind: 'ShadowLayer', + offsetX: { kind: 'SizeValue', value: 0, unit: 'px' }, + offsetY: { kind: 'SizeValue', value: 4, unit: 'px' }, + blur: { kind: 'SizeValue', value: 8, unit: 'px' }, + spread: { kind: 'SizeValue', value: 0, unit: 'px' }, + color: { kind: 'HexColor', value: '#000000' }, + }, + ], + }); + }); + + it('rejects rgba color in shadow (comma-separated args conflict with layer separator)', () => { + const result = parseShadow('2px 4px 6px 0px rgba(0, 0, 0, 0.1)'); + expect(result.isValid).toBe(false); + }); + + it('rejects shadow with rem units (parser guard only allows px or $)', () => { + const result = parseShadow('0rem 4rem 8rem 0rem #000000'); + expect(result.isValid).toBe(false); + }); + + it('parses multi-layer shadow', () => { + const result = parseShadow('0px 1px 2px 0px #000000, 0px 4px 8px 0px #111111'); + expect(result.isValid).toBe(true); + expect(result.value).toMatchObject({ kind: 'Shadow' }); + expect((result.value as { layers: unknown[] }).layers).toHaveLength(2); + }); + + it('parses shadow with token ref dimensions', () => { + const result = parseShadow('$spacing.1 $spacing.2 $spacing.3 0px #000000'); + expect(result.isValid).toBe(true); + expect(result.value).toMatchObject({ + kind: 'Shadow', + layers: [ + { + kind: 'ShadowLayer', + offsetX: { kind: 'TokenRef', collection: 'spacing', token: '1' }, + offsetY: { kind: 'TokenRef', collection: 'spacing', token: '2' }, + blur: { kind: 'TokenRef', collection: 'spacing', token: '3' }, + spread: { kind: 'SizeValue', value: 0, unit: 'px' }, + color: { kind: 'HexColor', value: '#000000' }, + }, + ], + }); + }); + + it('parses shadow with token ref color', () => { + const result = parseShadow('0px 4px 8px 0px $color.shadow'); + expect(result.isValid).toBe(true); + expect(result.value).toMatchObject({ + kind: 'Shadow', + layers: [ + { + kind: 'ShadowLayer', + color: { kind: 'TokenRef', collection: 'color', token: 'shadow' }, + }, + ], + }); + }); + + it('rejects value without px or $', () => { + expect(parseShadow('auto').isValid).toBe(false); + }); + + it('rejects shadow with too few parts', () => { + expect(parseShadow('0px 4px').isValid).toBe(false); + }); + + it('rejects shadow with invalid color part', () => { + expect(parseShadow('0px 4px 8px 0px not-a-color').isValid).toBe(false); + }); +}); diff --git a/ecosystem/baseframe/src/core/parsers/__tests__/value.test.ts b/ecosystem/baseframe/src/core/parsers/__tests__/value.test.ts new file mode 100644 index 00000000..3059a010 --- /dev/null +++ b/ecosystem/baseframe/src/core/parsers/__tests__/value.test.ts @@ -0,0 +1,160 @@ +import { describe, expect, it } from 'vitest'; +import type { Value } from '../../types'; +import { parseValue, valueToString } from '../value'; + +describe('parseValue', () => { + it('parses number input', () => { + const result = parseValue(42); + expect(result).toEqual({ + isValid: true, + value: { kind: 'NumberValue', value: 42 }, + }); + }); + + it('parses float number input', () => { + const result = parseValue(1.5); + expect(result).toEqual({ + isValid: true, + value: { kind: 'NumberValue', value: 1.5 }, + }); + }); + + it('parses hex color string', () => { + const result = parseValue('#ff0000'); + expect(result.isValid).toBe(true); + expect(result.value?.kind).toBe('HexColor'); + }); + + it('parses rgb color string', () => { + const result = parseValue('rgb(255, 0, 0)'); + expect(result.isValid).toBe(true); + expect(result.value?.kind).toBe('RgbColor'); + }); + + it('parses rgba color string', () => { + const result = parseValue('rgba(0, 0, 0, 0.5)'); + expect(result.isValid).toBe(true); + expect(result.value?.kind).toBe('RgbaColor'); + }); + + it('parses token ref string', () => { + const result = parseValue('$color.primary'); + expect(result.isValid).toBe(true); + expect(result.value?.kind).toBe('TokenRef'); + }); + + it('parses shadow string', () => { + const result = parseValue('0px 4px 8px 0px #000000'); + expect(result.isValid).toBe(true); + expect(result.value?.kind).toBe('Shadow'); + }); + + it('parses size string', () => { + const result = parseValue('16px'); + expect(result.isValid).toBe(true); + expect(result.value).toEqual({ kind: 'SizeValue', value: 16, unit: 'px' }); + }); + + it('parses duration string', () => { + const result = parseValue('300ms'); + expect(result.isValid).toBe(true); + expect(result.value).toEqual({ kind: 'DurationValue', value: 300, unit: 'ms' }); + }); + + it('falls back to StringValue for unrecognized input', () => { + const result = parseValue('auto'); + expect(result).toEqual({ + isValid: true, + value: { kind: 'StringValue', value: 'auto' }, + }); + }); + + it('trims whitespace', () => { + const result = parseValue(' 16px '); + expect(result.isValid).toBe(true); + expect(result.value).toEqual({ kind: 'SizeValue', value: 16, unit: 'px' }); + }); + + it('prioritizes hex over size for ambiguous input', () => { + const result = parseValue('#abc'); + expect(result.value?.kind).toBe('HexColor'); + }); + + it('prioritizes token ref over shadow for $ prefix', () => { + const result = parseValue('$color.primary'); + expect(result.value?.kind).toBe('TokenRef'); + }); +}); + +describe('valueToString', () => { + it.each<[string, Value]>([ + ['#ff0000', { kind: 'HexColor', value: '#ff0000' }], + ['rgb(255, 0, 0)', { kind: 'RgbColor', r: 255, g: 0, b: 0 }], + ['rgba(0, 0, 0, 0.5)', { kind: 'RgbaColor', r: 0, g: 0, b: 0, a: 0.5 }], + ['$color.primary', { kind: 'TokenRef', collection: 'color', token: 'primary' }], + ['16px', { kind: 'SizeValue', value: 16, unit: 'px' }], + ['1.5rem', { kind: 'SizeValue', value: 1.5, unit: 'rem' }], + ['300ms', { kind: 'DurationValue', value: 300, unit: 'ms' }], + ['0.3s', { kind: 'DurationValue', value: 0.3, unit: 's' }], + ['42', { kind: 'NumberValue', value: 42 }], + ['auto', { kind: 'StringValue', value: 'auto' }], + ])('serializes to %s', (expected, value) => { + expect(valueToString(value)).toBe(expected); + }); + + it('serializes ShadowLayer', () => { + const layer: Value = { + kind: 'ShadowLayer', + offsetX: { kind: 'SizeValue', value: 0, unit: 'px' }, + offsetY: { kind: 'SizeValue', value: 4, unit: 'px' }, + blur: { kind: 'SizeValue', value: 8, unit: 'px' }, + spread: { kind: 'SizeValue', value: 0, unit: 'px' }, + color: { kind: 'HexColor', value: '#000000' }, + }; + expect(valueToString(layer)).toBe('0px 4px 8px 0px #000000'); + }); + + it('serializes Shadow with multiple layers', () => { + const shadow: Value = { + kind: 'Shadow', + layers: [ + { + kind: 'ShadowLayer', + offsetX: { kind: 'SizeValue', value: 0, unit: 'px' }, + offsetY: { kind: 'SizeValue', value: 1, unit: 'px' }, + blur: { kind: 'SizeValue', value: 2, unit: 'px' }, + spread: { kind: 'SizeValue', value: 0, unit: 'px' }, + color: { kind: 'HexColor', value: '#000000' }, + }, + { + kind: 'ShadowLayer', + offsetX: { kind: 'SizeValue', value: 0, unit: 'px' }, + offsetY: { kind: 'SizeValue', value: 4, unit: 'px' }, + blur: { kind: 'SizeValue', value: 8, unit: 'px' }, + spread: { kind: 'SizeValue', value: 0, unit: 'px' }, + color: { kind: 'HexColor', value: '#111111' }, + }, + ], + }; + expect(valueToString(shadow)).toBe('0px 1px 2px 0px #000000, 0px 4px 8px 0px #111111'); + }); + + it('roundtrips parsed values', () => { + const inputs = [ + '#ff0000', + 'rgb(255, 0, 0)', + 'rgba(0, 0, 0, 0.5)', + '16px', + '300ms', + '$color.primary', + '$color.semantic.primary', + ]; + for (const input of inputs) { + const parsed = parseValue(input); + expect(parsed.isValid).toBe(true); + if (parsed.value) { + expect(valueToString(parsed.value)).toBe(input); + } + } + }); +}); diff --git a/ecosystem/baseframe/src/core/parsers/primitives.ts b/ecosystem/baseframe/src/core/parsers/primitives.ts new file mode 100644 index 00000000..31f26483 --- /dev/null +++ b/ecosystem/baseframe/src/core/parsers/primitives.ts @@ -0,0 +1,118 @@ +import type { ParseResult } from '../types'; + +export function parseHex(value: string): ParseResult { + const regex = /^#([A-Fa-f0-9]{3}|[A-Fa-f0-9]{4}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$/; + + if (!regex.test(value)) { + return { isValid: false, error: `Invalid hex color: ${value}` }; + } + + return { isValid: true, value: { kind: 'HexColor', value: value as `#${string}` } }; +} + +export function parseRgb(value: string): ParseResult { + const regex = /^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/; + const match = value.match(regex); + + if (!match) { + return { isValid: false, error: `Invalid rgb color: ${value}` }; + } + + const [, r, g, b] = match; + const red = Number.parseInt(r, 10); + const green = Number.parseInt(g, 10); + const blue = Number.parseInt(b, 10); + + if (red < 0 || red > 255 || green < 0 || green > 255 || blue < 0 || blue > 255) { + return { isValid: false, error: `Invalid RGB values: ${value}` }; + } + + return { isValid: true, value: { kind: 'RgbColor', r: red, g: green, b: blue } }; +} + +export function parseRgba(value: string): ParseResult { + const regex = /^rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*([01]?\.?\d*)\s*\)$/; + const match = value.match(regex); + + if (!match) { + return { isValid: false, error: `Invalid rgba color: ${value}` }; + } + + const [, r, g, b, a] = match; + const red = Number.parseInt(r, 10); + const green = Number.parseInt(g, 10); + const blue = Number.parseInt(b, 10); + const alpha = Number.parseFloat(a); + + if (red < 0 || red > 255 || green < 0 || green > 255 || blue < 0 || blue > 255) { + return { isValid: false, error: `Invalid RGB values: ${value}` }; + } + + if (alpha < 0 || alpha > 1) { + return { isValid: false, error: `Invalid alpha value: ${value}` }; + } + + return { isValid: true, value: { kind: 'RgbaColor', r: red, g: green, b: blue, a: alpha } }; +} + +export function parseTokenRef(value: string): ParseResult { + const regex = /^\$[a-zA-Z0-9][a-zA-Z0-9_-]*(?:\.[a-zA-Z0-9][a-zA-Z0-9_-]*)+$/; + + if (!regex.test(value)) { + return { isValid: false, error: `Invalid token reference: ${value}` }; + } + + const clean = value.substring(1); + const parts = clean.split('.'); + + if (parts.length < 2) { + return { isValid: false, error: `Invalid token format: ${value}` }; + } + + const token = parts[parts.length - 1]; + const collection = parts.slice(0, -1).join('.'); + + return { isValid: true, value: { kind: 'TokenRef', collection, token } }; +} + +export function parseSize(value: string): ParseResult { + const regex = /^([+-]?\d*\.?\d+)(px|rem|em|vw|vh|%)$/; + const match = value.match(regex); + + if (!match) { + return { isValid: false, error: `Invalid size: ${value}` }; + } + + const [, numValue, unit] = match; + const num = Number.parseFloat(numValue); + + if (Number.isNaN(num)) { + return { isValid: false, error: `Invalid size value: ${value}` }; + } + + return { + isValid: true, + value: { kind: 'SizeValue', value: num, unit: unit as 'px' | 'rem' | 'em' | 'vw' | 'vh' | '%' }, + }; +} + +export function parseDuration(value: string): ParseResult { + const regex = /^([+-]?\d*\.?\d+)(ms|s)$/; + const match = value.match(regex); + + if (!match) { + return { isValid: false, error: `Invalid duration: ${value}` }; + } + + const [, numValue, unit] = match; + const num = Number.parseFloat(numValue); + + if (Number.isNaN(num)) { + return { isValid: false, error: `Invalid duration value: ${value}` }; + } + + return { + isValid: true, + value: { kind: 'DurationValue', value: num, unit: unit as 'ms' | 's' }, + }; +} diff --git a/ecosystem/baseframe/src/core/parsers/shadow.ts b/ecosystem/baseframe/src/core/parsers/shadow.ts new file mode 100644 index 00000000..f22068cf --- /dev/null +++ b/ecosystem/baseframe/src/core/parsers/shadow.ts @@ -0,0 +1,97 @@ +import type { ParseResult, RgbaColor, ShadowLayer, SizeValue, TokenRef } from '../types'; +import { parseHex, parseRgb, parseRgba, parseSize, parseTokenRef } from './primitives'; + +export function parseColor(value: string): ParseResult { + if (!value) { + return { isValid: false, error: 'Missing color value' }; + } + + if (value.startsWith('$')) { + return parseTokenRef(value); + } + + if (value.startsWith('var(')) { + return { isValid: true, value: { kind: 'StringValue', value } }; + } + + const hex = parseHex(value); + if (hex.isValid) return hex; + + const rgb = parseRgb(value); + if (rgb.isValid) return rgb; + + const rgba = parseRgba(value); + if (rgba.isValid) return rgba; + + return { isValid: false, error: `Invalid color: ${value}` }; +} + +function parseSizeOrTokenRef(value: string): ParseResult { + if (value.startsWith('$')) { + return parseTokenRef(value); + } + return parseSize(value); +} + +function parseShadowLayer(value: string): ParseResult { + const parts = value.trim().split(/\s+/); + + if (parts.length < 4) { + return { isValid: false, error: `Invalid shadow layer format: ${value}` }; + } + + try { + const x = parseSizeOrTokenRef(parts[0]); + const y = parseSizeOrTokenRef(parts[1]); + const blur = parseSizeOrTokenRef(parts[2]); + const spread = parseSizeOrTokenRef(parts[3]); + + if (!x.isValid || !y.isValid || !blur.isValid || !spread.isValid) { + return { isValid: false, error: `Invalid shadow dimensions: ${value}` }; + } + + const colorPart = parts.slice(4).join(' ').trim(); + const color = parseColor(colorPart); + + if (!color.isValid) { + return { isValid: false, error: `Invalid shadow color: ${colorPart}` }; + } + + return { + isValid: true, + value: { + kind: 'ShadowLayer', + color: color.value as RgbaColor, + offsetX: x.value as SizeValue | TokenRef, + offsetY: y.value as SizeValue | TokenRef, + blur: blur.value as SizeValue | TokenRef, + spread: spread.value as SizeValue | TokenRef, + }, + }; + } catch (_error) { + return { isValid: false, error: `Invalid shadow layer: ${value}` }; + } +} + +export function parseShadow(value: string): ParseResult { + if (!value.includes('px') && !value.includes('$')) { + return { isValid: false, error: `Invalid shadow format: ${value}` }; + } + + try { + const layerStrings = value.split(',').map(s => s.trim()); + const layers: ShadowLayer[] = []; + + for (const layerStr of layerStrings) { + const layer = parseShadowLayer(layerStr); + if (!layer.isValid || !layer.value) { + return { isValid: false, error: `Invalid shadow layer: ${layerStr}` }; + } + layers.push(layer.value as ShadowLayer); + } + + return { isValid: true, value: { kind: 'Shadow', layers } }; + } catch (_error) { + return { isValid: false, error: `Invalid shadow format: ${value}` }; + } +} diff --git a/ecosystem/baseframe/src/core/parsers/value.ts b/ecosystem/baseframe/src/core/parsers/value.ts index 5f568525..650942b2 100644 --- a/ecosystem/baseframe/src/core/parsers/value.ts +++ b/ecosystem/baseframe/src/core/parsers/value.ts @@ -1,4 +1,13 @@ -import type { ParseResult, RgbaColor, ShadowLayer, SizeValue, TokenRef, Value } from '../types'; +import type { ParseResult, Value } from '../types'; +import { + parseDuration, + parseHex, + parseRgb, + parseRgba, + parseSize, + parseTokenRef, +} from './primitives'; +import { parseShadow } from './shadow'; export function parseValue(value: string | number): ParseResult { const str = String(value).trim(); @@ -63,215 +72,3 @@ export function valueToString(value: Value): string { return String(value); } } - -function parseHex(value: string): ParseResult { - const regex = /^#([A-Fa-f0-9]{3}|[A-Fa-f0-9]{4}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$/; - - if (!regex.test(value)) { - return { isValid: false, error: `Invalid hex color: ${value}` }; - } - - return { isValid: true, value: { kind: 'HexColor', value: value as `#${string}` } }; -} - -function parseRgb(value: string): ParseResult { - const regex = /^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/; - const match = value.match(regex); - - if (!match) { - return { isValid: false, error: `Invalid rgb color: ${value}` }; - } - - const [, r, g, b] = match; - const red = Number.parseInt(r, 10); - const green = Number.parseInt(g, 10); - const blue = Number.parseInt(b, 10); - - if (red < 0 || red > 255 || green < 0 || green > 255 || blue < 0 || blue > 255) { - return { isValid: false, error: `Invalid RGB values: ${value}` }; - } - - return { isValid: true, value: { kind: 'RgbColor', r: red, g: green, b: blue } }; -} - -function parseRgba(value: string): ParseResult { - const regex = /^rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*([01]?\.?\d*)\s*\)$/; - const match = value.match(regex); - - if (!match) { - return { isValid: false, error: `Invalid rgba color: ${value}` }; - } - - const [, r, g, b, a] = match; - const red = Number.parseInt(r, 10); - const green = Number.parseInt(g, 10); - const blue = Number.parseInt(b, 10); - const alpha = Number.parseFloat(a); - - if (red < 0 || red > 255 || green < 0 || green > 255 || blue < 0 || blue > 255) { - return { isValid: false, error: `Invalid RGB values: ${value}` }; - } - - if (alpha < 0 || alpha > 1) { - return { isValid: false, error: `Invalid alpha value: ${value}` }; - } - - return { isValid: true, value: { kind: 'RgbaColor', r: red, g: green, b: blue, a: alpha } }; -} - -function parseTokenRef(value: string): ParseResult { - const regex = /^\$[a-zA-Z0-9][a-zA-Z0-9_-]*(?:\.[a-zA-Z0-9][a-zA-Z0-9_-]*)+$/; - - if (!regex.test(value)) { - return { isValid: false, error: `Invalid token reference: ${value}` }; - } - - const clean = value.substring(1); - const parts = clean.split('.'); - - if (parts.length < 2) { - return { isValid: false, error: `Invalid token format: ${value}` }; - } - - const token = parts[parts.length - 1]; - const collection = parts.slice(0, -1).join('.'); - - return { isValid: true, value: { kind: 'TokenRef', collection, token } }; -} - -function parseSize(value: string): ParseResult { - const regex = /^([+-]?\d*\.?\d+)(px|rem|em|vw|vh|%)$/; - const match = value.match(regex); - - if (!match) { - return { isValid: false, error: `Invalid size: ${value}` }; - } - - const [, numValue, unit] = match; - const num = Number.parseFloat(numValue); - - if (Number.isNaN(num)) { - return { isValid: false, error: `Invalid size value: ${value}` }; - } - - return { - isValid: true, - value: { kind: 'SizeValue', value: num, unit: unit as 'px' | 'rem' | 'em' | 'vw' | 'vh' | '%' }, - }; -} - -function parseDuration(value: string): ParseResult { - const regex = /^([+-]?\d*\.?\d+)(ms|s)$/; - const match = value.match(regex); - - if (!match) { - return { isValid: false, error: `Invalid duration: ${value}` }; - } - - const [, numValue, unit] = match; - const num = Number.parseFloat(numValue); - - if (Number.isNaN(num)) { - return { isValid: false, error: `Invalid duration value: ${value}` }; - } - - return { - isValid: true, - value: { kind: 'DurationValue', value: num, unit: unit as 'ms' | 's' }, - }; -} - -function parseColor(value: string): ParseResult { - if (!value) { - return { isValid: false, error: 'Missing color value' }; - } - - if (value.startsWith('$')) { - return parseTokenRef(value); - } - - if (value.startsWith('var(')) { - return { isValid: true, value: { kind: 'StringValue', value } }; - } - - const hex = parseHex(value); - if (hex.isValid) return hex; - - const rgb = parseRgb(value); - if (rgb.isValid) return rgb; - - const rgba = parseRgba(value); - if (rgba.isValid) return rgba; - - return { isValid: false, error: `Invalid color: ${value}` }; -} - -function parseShadow(value: string): ParseResult { - if (!value.includes('px') && !value.includes('$')) { - return { isValid: false, error: `Invalid shadow format: ${value}` }; - } - - try { - const layerStrings = value.split(',').map(s => s.trim()); - const layers: ShadowLayer[] = []; - - for (const layerStr of layerStrings) { - const layer = parseShadowLayer(layerStr); - if (!layer.isValid || !layer.value) { - return { isValid: false, error: `Invalid shadow layer: ${layerStr}` }; - } - layers.push(layer.value as ShadowLayer); - } - - return { isValid: true, value: { kind: 'Shadow', layers } }; - } catch (_error) { - return { isValid: false, error: `Invalid shadow format: ${value}` }; - } -} - -function parseShadowLayer(value: string): ParseResult { - const parts = value.trim().split(/\s+/); - - if (parts.length < 4) { - return { isValid: false, error: `Invalid shadow layer format: ${value}` }; - } - - try { - const x = parseSizeOrTokenRef(parts[0]); - const y = parseSizeOrTokenRef(parts[1]); - const blur = parseSizeOrTokenRef(parts[2]); - const spread = parseSizeOrTokenRef(parts[3]); - - if (!x.isValid || !y.isValid || !blur.isValid || !spread.isValid) { - return { isValid: false, error: `Invalid shadow dimensions: ${value}` }; - } - - const colorPart = parts.slice(4).join(' ').trim(); - const color = parseColor(colorPart); - - if (!color.isValid) { - return { isValid: false, error: `Invalid shadow color: ${colorPart}` }; - } - - return { - isValid: true, - value: { - kind: 'ShadowLayer', - color: color.value as RgbaColor, - offsetX: x.value as SizeValue | TokenRef, - offsetY: y.value as SizeValue | TokenRef, - blur: blur.value as SizeValue | TokenRef, - spread: spread.value as SizeValue | TokenRef, - }, - }; - } catch (_error) { - return { isValid: false, error: `Invalid shadow layer: ${value}` }; - } -} - -function parseSizeOrTokenRef(value: string): ParseResult { - if (value.startsWith('$')) { - return parseTokenRef(value); - } - return parseSize(value); -} diff --git a/ecosystem/baseframe/src/core/transforms/__tests__/build.test.ts b/ecosystem/baseframe/src/core/transforms/__tests__/build.test.ts new file mode 100644 index 00000000..feea004c --- /dev/null +++ b/ecosystem/baseframe/src/core/transforms/__tests__/build.test.ts @@ -0,0 +1,141 @@ +import { describe, expect, it } from 'vitest'; +import type { Collections, Token } from '../../types'; +import { buildValidatedAst } from '../build'; + +const collections: Collections = { + kind: 'TokenCollections', + metadata: { id: 'test', name: 'Test' }, + data: [ + { name: 'color', modes: ['default', 'dark'] }, + { name: 'spacing', modes: ['default'] }, + ], +}; + +describe('buildValidatedAst', () => { + it('builds AST from valid tokens', () => { + const tokens: Token[] = [ + { + kind: 'Tokens', + metadata: { id: 'color', name: 'Color', description: '' }, + data: { + collection: 'color', + tokens: { + '$color.primary': { values: { default: '#ff0000', dark: '#cc0000' } }, + }, + }, + }, + ]; + + const ast = buildValidatedAst(tokens, collections); + expect(ast.tokens).toHaveLength(1); + expect(ast.collections).toHaveLength(2); + expect(ast.tokens[0].token.name).toBe('$color.primary'); + }); + + it('throws on invalid collection', () => { + const tokens: Token[] = [ + { + kind: 'Tokens', + metadata: { id: 'unknown', name: 'Unknown', description: '' }, + data: { + collection: 'unknown', + tokens: { + '$unknown.foo': { values: { default: '#000000' } }, + }, + }, + }, + ]; + + expect(() => buildValidatedAst(tokens, collections)).toThrow('Token validation failed'); + }); + + it('throws on missing modes', () => { + const tokens: Token[] = [ + { + kind: 'Tokens', + metadata: { id: 'color', name: 'Color', description: '' }, + data: { + collection: 'color', + tokens: { + '$color.primary': { values: { default: '#ff0000' } }, + }, + }, + }, + ]; + + expect(() => buildValidatedAst(tokens, collections)).toThrow('Token validation failed'); + }); + + it('throws on invalid token reference', () => { + const tokens: Token[] = [ + { + kind: 'Tokens', + metadata: { id: 'color', name: 'Color', description: '' }, + data: { + collection: 'color', + tokens: { + '$color.primary': { values: { default: '$nonexistent.ref', dark: '#cc0000' } }, + }, + }, + }, + ]; + + expect(() => buildValidatedAst(tokens, collections)).toThrow('Token validation failed'); + }); + + it('includes error details in thrown message', () => { + const tokens: Token[] = [ + { + kind: 'Tokens', + metadata: { id: 'unknown', name: 'Unknown', description: '' }, + data: { + collection: 'unknown', + tokens: { + '$unknown.foo': { values: { default: '#000000' } }, + }, + }, + }, + ]; + + try { + buildValidatedAst(tokens, collections); + expect.unreachable('should have thrown'); + } catch (error) { + expect((error as Error).message).toContain('unknown'); + } + }); + + it('builds AST from multiple valid token files', () => { + const tokens: Token[] = [ + { + kind: 'Tokens', + metadata: { id: 'color', name: 'Color', description: '' }, + data: { + collection: 'color', + tokens: { + '$color.primary': { values: { default: '#ff0000', dark: '#cc0000' } }, + }, + }, + }, + { + kind: 'Tokens', + metadata: { id: 'spacing', name: 'Spacing', description: '' }, + data: { + collection: 'spacing', + tokens: { + '$spacing.1': { values: { default: '4px' } }, + }, + }, + }, + ]; + + const ast = buildValidatedAst(tokens, collections); + expect(ast.tokens).toHaveLength(2); + }); + + it('builds AST from empty token list', () => { + const ast = buildValidatedAst([], collections); + expect(ast.tokens).toHaveLength(0); + expect(ast.collections).toHaveLength(2); + }); +}); diff --git a/ecosystem/baseframe/src/core/transforms/__tests__/resolve.test.ts b/ecosystem/baseframe/src/core/transforms/__tests__/resolve.test.ts new file mode 100644 index 00000000..3115e93d --- /dev/null +++ b/ecosystem/baseframe/src/core/transforms/__tests__/resolve.test.ts @@ -0,0 +1,157 @@ +import { describe, expect, it } from 'vitest'; +import type { TokenDecl, Value } from '../../types'; +import { createTokenResolver } from '../resolve'; + +const allTokens: TokenDecl[] = [ + { + token: { name: '$color.primary', collection: 'color' }, + values: [{ mode: 'default', value: '#ff0000' }], + }, + { + token: { name: '$spacing.1', collection: 'spacing' }, + values: [{ mode: 'default', value: '4px' }], + }, +]; + +const namer = (name: string) => `--${name.replace(/^\$/, '').replace(/\./g, '-')}`; + +describe('createTokenResolver', () => { + describe('resolveTokenRef', () => { + it('resolves existing token ref to var()', () => { + const resolver = createTokenResolver(allTokens, namer); + const ref = { kind: 'TokenRef' as const, collection: 'color', token: 'primary' }; + expect(resolver.resolveTokenRef(ref)).toBe('var(--color-primary)'); + }); + + it('throws for non-existent token ref', () => { + const resolver = createTokenResolver(allTokens, namer); + const ref = { kind: 'TokenRef' as const, collection: 'color', token: 'missing' }; + expect(() => resolver.resolveTokenRef(ref)).toThrow('Token not found: $color.missing'); + }); + }); + + describe('resolve', () => { + it('resolves TokenRef to StringValue with var()', () => { + const resolver = createTokenResolver(allTokens, namer); + const value: Value = { kind: 'TokenRef', collection: 'color', token: 'primary' }; + const resolved = resolver.resolve(value); + expect(resolved).toEqual({ kind: 'StringValue', value: 'var(--color-primary)' }); + }); + + it('returns HexColor unchanged', () => { + const resolver = createTokenResolver(allTokens, namer); + const value: Value = { kind: 'HexColor', value: '#ff0000' }; + expect(resolver.resolve(value)).toEqual(value); + }); + + it('returns SizeValue unchanged', () => { + const resolver = createTokenResolver(allTokens, namer); + const value: Value = { kind: 'SizeValue', value: 16, unit: 'px' }; + expect(resolver.resolve(value)).toEqual(value); + }); + + it('returns NumberValue unchanged', () => { + const resolver = createTokenResolver(allTokens, namer); + const value: Value = { kind: 'NumberValue', value: 42 }; + expect(resolver.resolve(value)).toEqual(value); + }); + + it('returns StringValue unchanged', () => { + const resolver = createTokenResolver(allTokens, namer); + const value: Value = { kind: 'StringValue', value: 'auto' }; + expect(resolver.resolve(value)).toEqual(value); + }); + + it('returns DurationValue unchanged', () => { + const resolver = createTokenResolver(allTokens, namer); + const value: Value = { kind: 'DurationValue', value: 300, unit: 'ms' }; + expect(resolver.resolve(value)).toEqual(value); + }); + + it('returns RgbColor unchanged', () => { + const resolver = createTokenResolver(allTokens, namer); + const value: Value = { kind: 'RgbColor', r: 255, g: 0, b: 0 }; + expect(resolver.resolve(value)).toEqual(value); + }); + + it('returns RgbaColor unchanged', () => { + const resolver = createTokenResolver(allTokens, namer); + const value: Value = { kind: 'RgbaColor', r: 0, g: 0, b: 0, a: 0.5 }; + expect(resolver.resolve(value)).toEqual(value); + }); + + it('resolves ShadowLayer with token ref color', () => { + const resolver = createTokenResolver(allTokens, namer); + const value: Value = { + kind: 'ShadowLayer', + offsetX: { kind: 'SizeValue', value: 0, unit: 'px' }, + offsetY: { kind: 'SizeValue', value: 4, unit: 'px' }, + blur: { kind: 'SizeValue', value: 8, unit: 'px' }, + spread: { kind: 'SizeValue', value: 0, unit: 'px' }, + color: { kind: 'TokenRef', collection: 'color', token: 'primary' }, + }; + + const resolved = resolver.resolve(value); + expect(resolved).toMatchObject({ + kind: 'ShadowLayer', + color: { kind: 'StringValue', value: 'var(--color-primary)' }, + offsetX: { kind: 'SizeValue', value: 0, unit: 'px' }, + offsetY: { kind: 'SizeValue', value: 4, unit: 'px' }, + blur: { kind: 'SizeValue', value: 8, unit: 'px' }, + spread: { kind: 'SizeValue', value: 0, unit: 'px' }, + }); + }); + + it('resolves ShadowLayer with token ref dimensions', () => { + const resolver = createTokenResolver(allTokens, namer); + const value: Value = { + kind: 'ShadowLayer', + offsetX: { kind: 'TokenRef', collection: 'spacing', token: '1' }, + offsetY: { kind: 'SizeValue', value: 4, unit: 'px' }, + blur: { kind: 'SizeValue', value: 8, unit: 'px' }, + spread: { kind: 'SizeValue', value: 0, unit: 'px' }, + color: { kind: 'HexColor', value: '#000000' }, + }; + + const resolved = resolver.resolve(value); + expect(resolved).toMatchObject({ + kind: 'ShadowLayer', + offsetX: { kind: 'StringValue', value: 'var(--spacing-1)' }, + }); + }); + + it('resolves Shadow with nested token refs', () => { + const resolver = createTokenResolver(allTokens, namer); + const value: Value = { + kind: 'Shadow', + layers: [ + { + kind: 'ShadowLayer', + offsetX: { kind: 'SizeValue', value: 0, unit: 'px' }, + offsetY: { kind: 'SizeValue', value: 4, unit: 'px' }, + blur: { kind: 'SizeValue', value: 8, unit: 'px' }, + spread: { kind: 'SizeValue', value: 0, unit: 'px' }, + color: { kind: 'TokenRef', collection: 'color', token: 'primary' }, + }, + ], + }; + + const resolved = resolver.resolve(value); + expect(resolved).toMatchObject({ + kind: 'Shadow', + layers: [ + { + kind: 'ShadowLayer', + color: { kind: 'StringValue', value: 'var(--color-primary)' }, + }, + ], + }); + }); + + it('throws when resolving non-existent token ref', () => { + const resolver = createTokenResolver(allTokens, namer); + const value: Value = { kind: 'TokenRef', collection: 'color', token: 'missing' }; + expect(() => resolver.resolve(value)).toThrow('Token not found'); + }); + }); +}); diff --git a/ecosystem/baseframe/src/core/transforms/__tests__/validate.test.ts b/ecosystem/baseframe/src/core/transforms/__tests__/validate.test.ts new file mode 100644 index 00000000..67494199 --- /dev/null +++ b/ecosystem/baseframe/src/core/transforms/__tests__/validate.test.ts @@ -0,0 +1,209 @@ +import { describe, expect, it } from 'vitest'; +import type { Collection, Token } from '../../types'; +import { validateAllTokens } from '../validate'; + +function createDefinitions( + entries: Array<{ name: string; modes: string[] }>, +): Map { + return new Map(entries.map(e => [e.name, e])); +} + +const definitions = createDefinitions([ + { name: 'color', modes: ['default', 'dark'] }, + { name: 'spacing', modes: ['default'] }, +]); + +describe('validateAllTokens', () => { + it('validates correct tokens', () => { + const tokens: Token[] = [ + { + kind: 'Tokens', + metadata: { id: 'color', name: 'Color', description: '' }, + data: { + collection: 'color', + tokens: { + '$color.primary': { values: { default: '#ff0000', dark: '#cc0000' } }, + }, + }, + }, + ]; + + const result = validateAllTokens(tokens, definitions); + expect(result.isValid).toBe(true); + expect(result.errors).toHaveLength(0); + }); + + it('reports invalid collection', () => { + const tokens: Token[] = [ + { + kind: 'Tokens', + metadata: { id: 'unknown', name: 'Unknown', description: '' }, + data: { + collection: 'unknown', + tokens: { + '$unknown.foo': { values: { default: '#000000' } }, + }, + }, + }, + ]; + + const result = validateAllTokens(tokens, definitions); + expect(result.isValid).toBe(false); + expect(result.errors.some(e => e.type === 'INVALID_COLLECTION')).toBe(true); + }); + + it('reports missing modes', () => { + const tokens: Token[] = [ + { + kind: 'Tokens', + metadata: { id: 'color', name: 'Color', description: '' }, + data: { + collection: 'color', + tokens: { + '$color.primary': { values: { default: '#ff0000' } }, + }, + }, + }, + ]; + + const result = validateAllTokens(tokens, definitions); + expect(result.isValid).toBe(false); + expect(result.errors.some(e => e.type === 'MISSING_MODE')).toBe(true); + }); + + it('reports invalid token references', () => { + const tokens: Token[] = [ + { + kind: 'Tokens', + metadata: { id: 'color', name: 'Color', description: '' }, + data: { + collection: 'color', + tokens: { + '$color.primary': { values: { default: '$nonexistent.ref', dark: '#cc0000' } }, + }, + }, + }, + ]; + + const result = validateAllTokens(tokens, definitions); + expect(result.isValid).toBe(false); + expect(result.errors.some(e => e.type === 'INVALID_PRIMITIVE_TOKEN')).toBe(true); + }); + + it('validates valid token references', () => { + const tokens: Token[] = [ + { + kind: 'Tokens', + metadata: { id: 'color', name: 'Color', description: '' }, + data: { + collection: 'color', + tokens: { + '$color.base': { values: { default: '#ff0000', dark: '#cc0000' } }, + '$color.primary': { values: { default: '$color.base', dark: '$color.base' } }, + }, + }, + }, + ]; + + const result = validateAllTokens(tokens, definitions); + expect(result.isValid).toBe(true); + expect(result.errors).toHaveLength(0); + }); + + it('validates multiple collections', () => { + const tokens: Token[] = [ + { + kind: 'Tokens', + metadata: { id: 'color', name: 'Color', description: '' }, + data: { + collection: 'color', + tokens: { + '$color.primary': { values: { default: '#ff0000', dark: '#cc0000' } }, + }, + }, + }, + { + kind: 'Tokens', + metadata: { id: 'spacing', name: 'Spacing', description: '' }, + data: { + collection: 'spacing', + tokens: { + '$spacing.1': { values: { default: '4px' } }, + }, + }, + }, + ]; + + const result = validateAllTokens(tokens, definitions); + expect(result.isValid).toBe(true); + expect(result.errors).toHaveLength(0); + }); + + it('validates empty token list', () => { + const result = validateAllTokens([], definitions); + expect(result.isValid).toBe(true); + expect(result.errors).toHaveLength(0); + }); + + it('ignores extra modes not in definition (validator only checks missing modes)', () => { + const tokens: Token[] = [ + { + kind: 'Tokens', + metadata: { id: 'spacing', name: 'Spacing', description: '' }, + data: { + collection: 'spacing', + tokens: { + '$spacing.1': { values: { default: '4px', dark: '8px' } }, + }, + }, + }, + ]; + + const result = validateAllTokens(tokens, definitions); + expect(result.isValid).toBe(true); + }); + + it('accumulates errors from multiple tokens', () => { + const tokens: Token[] = [ + { + kind: 'Tokens', + metadata: { id: 'color', name: 'Color', description: '' }, + data: { + collection: 'color', + tokens: { + '$color.a': { values: { default: '#ff0000' } }, + '$color.b': { values: { default: '#00ff00' } }, + }, + }, + }, + ]; + + const result = validateAllTokens(tokens, definitions); + expect(result.isValid).toBe(false); + expect(result.errors.filter(e => e.type === 'MISSING_MODE').length).toBeGreaterThanOrEqual(2); + }); + + it('validates shadow with token references', () => { + const tokens: Token[] = [ + { + kind: 'Tokens', + metadata: { id: 'color', name: 'Color', description: '' }, + data: { + collection: 'color', + tokens: { + '$color.shadow': { values: { default: '#000000', dark: '#111111' } }, + '$color.elevated': { + values: { + default: '0px 4px 8px 0px $color.shadow', + dark: '0px 4px 8px 0px $color.shadow', + }, + }, + }, + }, + }, + ]; + + const result = validateAllTokens(tokens, definitions); + expect(result.isValid).toBe(true); + }); +}); diff --git a/ecosystem/baseframe/src/core/transforms/build.ts b/ecosystem/baseframe/src/core/transforms/build.ts index 66508559..f098e835 100644 --- a/ecosystem/baseframe/src/core/transforms/build.ts +++ b/ecosystem/baseframe/src/core/transforms/build.ts @@ -8,18 +8,10 @@ export function buildValidatedAst(tokens: Token[], collections: Collections): As const validation = validateAllTokens(tokens, collectionMap); if (!validation.isValid) { - console.error('Token validation failed:'); - validation.errors.forEach((error: ValidationError) => { - console.error(` ${error.message}`); - }); - throw new Error('Token validation failed. Please fix the errors above.'); - } - - if (validation.warnings.length > 0) { - console.warn('Token validation warnings:'); - validation.warnings.forEach((warning: string) => { - console.warn(` ${warning}`); - }); + const details = validation.errors + .map((error: ValidationError) => ` ${error.message}`) + .join('\n'); + throw new Error(`Token validation failed:\n${details}`); } return buildAst(tokens, collections); diff --git a/ecosystem/baseframe/src/core/transforms/resolve.ts b/ecosystem/baseframe/src/core/transforms/resolve.ts index 683935e3..6467f76f 100644 --- a/ecosystem/baseframe/src/core/transforms/resolve.ts +++ b/ecosystem/baseframe/src/core/transforms/resolve.ts @@ -8,12 +8,11 @@ export interface TokenResolver { export function createTokenResolver( allTokens: TokenDecl[], - mode: string, cssVarNamer: (tokenName: string) => string, ): TokenResolver { return { resolve(value: Value): Value { - return resolveValue(value, allTokens, mode, cssVarNamer); + return resolveValue(value, allTokens, cssVarNamer); }, resolveTokenRef(tokenRef: TokenRef): string { const fullName = `$${tokenRef.collection}.${tokenRef.token}`; @@ -32,7 +31,6 @@ export function createTokenResolver( function resolveValue( value: Value, allTokens: TokenDecl[], - mode: string, cssVarNamer: (tokenName: string) => string, ): Value { switch (value.kind) { @@ -49,18 +47,18 @@ function resolveValue( case 'ShadowLayer': return { kind: 'ShadowLayer', - color: resolveValue(value.color, allTokens, mode, cssVarNamer) as ColorValue, - offsetX: resolveValue(value.offsetX, allTokens, mode, cssVarNamer) as SizeValue | TokenRef, - offsetY: resolveValue(value.offsetY, allTokens, mode, cssVarNamer) as SizeValue | TokenRef, - blur: resolveValue(value.blur, allTokens, mode, cssVarNamer) as SizeValue | TokenRef, - spread: resolveValue(value.spread, allTokens, mode, cssVarNamer) as SizeValue | TokenRef, + color: resolveValue(value.color, allTokens, cssVarNamer) as ColorValue, + offsetX: resolveValue(value.offsetX, allTokens, cssVarNamer) as SizeValue | TokenRef, + offsetY: resolveValue(value.offsetY, allTokens, cssVarNamer) as SizeValue | TokenRef, + blur: resolveValue(value.blur, allTokens, cssVarNamer) as SizeValue | TokenRef, + spread: resolveValue(value.spread, allTokens, cssVarNamer) as SizeValue | TokenRef, }; case 'Shadow': return { kind: 'Shadow', layers: value.layers.map( - layer => resolveValue(layer, allTokens, mode, cssVarNamer) as ShadowLayer, + layer => resolveValue(layer, allTokens, cssVarNamer) as ShadowLayer, ), }; diff --git a/ecosystem/baseframe/vitest.config.ts b/ecosystem/baseframe/vitest.config.ts new file mode 100644 index 00000000..6ec74eee --- /dev/null +++ b/ecosystem/baseframe/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + include: ['src/**/*.test.ts'], + }, +}); diff --git a/package.json b/package.json index 923b2d71..6a181c50 100644 --- a/package.json +++ b/package.json @@ -24,5 +24,5 @@ "engines": { "node": ">=22" }, - "packageManager": "pnpm@10.19.3" + "packageManager": "pnpm@10.29.3" } diff --git a/packages/baseframe/primitive/font-weight.yaml b/packages/baseframe/primitive/font-weight.yaml new file mode 100644 index 00000000..e6f632ca --- /dev/null +++ b/packages/baseframe/primitive/font-weight.yaml @@ -0,0 +1,35 @@ +kind: Tokens +metadata: + id: font-weight + name: Font Weight + description: primitive font-weight tokens for Baseframe +data: + collection: global + tokens: + $font-weight.thin: + values: + default: "100" + $font-weight.extralight: + values: + default: "200" + $font-weight.light: + values: + default: "300" + $font-weight.regular: + values: + default: "400" + $font-weight.medium: + values: + default: "500" + $font-weight.semibold: + values: + default: "600" + $font-weight.bold: + values: + default: "700" + $font-weight.extrabold: + values: + default: "800" + $font-weight.black: + values: + default: "900" diff --git a/packages/baseframe/primitive/radius.yaml b/packages/baseframe/primitive/radius.yaml new file mode 100644 index 00000000..ca5b9100 --- /dev/null +++ b/packages/baseframe/primitive/radius.yaml @@ -0,0 +1,32 @@ +kind: Tokens +metadata: + id: radius + name: Radius + description: primitive border-radius tokens for Baseframe +data: + collection: global + tokens: + $radius.none: + values: + default: "0" + $radius.xs: + values: + default: "2px" + $radius.sm: + values: + default: "4px" + $radius.md: + values: + default: "6px" + $radius.lg: + values: + default: "8px" + $radius.xl: + values: + default: "12px" + $radius.full: + values: + default: "9999px" + $radius.circle: + values: + default: "100%" diff --git a/packages/baseframe/semantic/color.yaml b/packages/baseframe/semantic/color.yaml index 6ae0d957..f04ec8ab 100644 --- a/packages/baseframe/semantic/color.yaml +++ b/packages/baseframe/semantic/color.yaml @@ -18,10 +18,10 @@ data: $color.text.primary: values: - default: $color.neutral.950 + default: $color.neutral-950 $color.text.secondary: values: - default: $color.neutral.600 + default: $color.neutral-600 $color.text.tertiary: values: - default: $color.neutral.400 + default: $color.neutral-400 diff --git a/packages/css/tailwind4.css b/packages/css/tailwind4.css index 7f7443ed..4fa59bf9 100644 --- a/packages/css/tailwind4.css +++ b/packages/css/tailwind4.css @@ -1,123 +1,145 @@ @theme { - --color-white: var(--ds-color-white); - --color-white-alpha-5: var(--ds-color-white-alpha-5); - --color-white-alpha-10: var(--ds-color-white-alpha-10); - --color-white-alpha-20: var(--ds-color-white-alpha-20); - --color-white-alpha-30: var(--ds-color-white-alpha-30); - --color-white-alpha-40: var(--ds-color-white-alpha-40); - --color-white-alpha-50: var(--ds-color-white-alpha-50); - --color-white-alpha-60: var(--ds-color-white-alpha-60); - --color-white-alpha-70: var(--ds-color-white-alpha-70); - --color-white-alpha-80: var(--ds-color-white-alpha-80); - --color-white-alpha-90: var(--ds-color-white-alpha-90); - - --color-black: var(--ds-color-black); - --color-black-alpha-5: var(--ds-color-black-alpha-5); - --color-black-alpha-10: var(--ds-color-black-alpha-10); - --color-black-alpha-20: var(--ds-color-black-alpha-20); - --color-black-alpha-30: var(--ds-color-black-alpha-30); - --color-black-alpha-40: var(--ds-color-black-alpha-40); - --color-black-alpha-50: var(--ds-color-black-alpha-50); - --color-black-alpha-60: var(--ds-color-black-alpha-60); - --color-black-alpha-70: var(--ds-color-black-alpha-70); - --color-black-alpha-80: var(--ds-color-black-alpha-80); - --color-black-alpha-90: var(--ds-color-black-alpha-90); - - --color-neutral-50: var(--ds-color-neutral-50); - --color-neutral-100: var(--ds-color-neutral-100); - --color-neutral-200: var(--ds-color-neutral-200); - --color-neutral-300: var(--ds-color-neutral-300); - --color-neutral-400: var(--ds-color-neutral-400); - --color-neutral-500: var(--ds-color-neutral-500); - --color-neutral-600: var(--ds-color-neutral-600); - --color-neutral-700: var(--ds-color-neutral-700); - --color-neutral-800: var(--ds-color-neutral-800); - --color-neutral-900: var(--ds-color-neutral-900); - --color-neutral-950: var(--ds-color-neutral-950); - - --color-primary-50: var(--ds-color-primary-50); - --color-primary-100: var(--ds-color-primary-100); - --color-primary-200: var(--ds-color-primary-200); - --color-primary-300: var(--ds-color-primary-300); - --color-primary-400: var(--ds-color-primary-400); - --color-primary-500: var(--ds-color-primary-500); - --color-primary-600: var(--ds-color-primary-600); - --color-primary-700: var(--ds-color-primary-700); - --color-primary-800: var(--ds-color-primary-800); - --color-primary-900: var(--ds-color-primary-900); - --color-primary-950: var(--ds-color-primary-950); - - --color-danger-50: var(--ds-color-danger-50); - --color-danger-100: var(--ds-color-danger-100); - --color-danger-200: var(--ds-color-danger-200); - --color-danger-300: var(--ds-color-danger-300); - --color-danger-400: var(--ds-color-danger-400); - --color-danger-500: var(--ds-color-danger-500); - --color-danger-600: var(--ds-color-danger-600); - --color-danger-700: var(--ds-color-danger-700); - --color-danger-800: var(--ds-color-danger-800); - --color-danger-900: var(--ds-color-danger-900); - --color-danger-950: var(--ds-color-danger-950); - - --color-warning-50: var(--ds-color-warning-50); - --color-warning-100: var(--ds-color-warning-100); - --color-warning-200: var(--ds-color-warning-200); - --color-warning-300: var(--ds-color-warning-300); - --color-warning-400: var(--ds-color-warning-400); - --color-warning-500: var(--ds-color-warning-500); - --color-warning-600: var(--ds-color-warning-600); - --color-warning-700: var(--ds-color-warning-700); - --color-warning-800: var(--ds-color-warning-800); - --color-warning-900: var(--ds-color-warning-900); - --color-warning-950: var(--ds-color-warning-950); - - --color-success-50: var(--ds-color-success-50); - --color-success-100: var(--ds-color-success-100); - --color-success-200: var(--ds-color-success-200); - --color-success-300: var(--ds-color-success-300); - --color-success-400: var(--ds-color-success-400); - --color-success-500: var(--ds-color-success-500); - --color-success-600: var(--ds-color-success-600); - --color-success-700: var(--ds-color-success-700); - --color-success-800: var(--ds-color-success-800); - --color-success-900: var(--ds-color-success-900); - --color-success-950: var(--ds-color-success-950); - - --z-index-modal-content: var(--ds-z-index-modal-content); - --color-alpha-shadow1: var(--ds-color-alpha-shadow1); - --color-alpha-shadow2: var(--ds-color-alpha-shadow2); - --color-alpha-shadow3: var(--ds-color-alpha-shadow3); - - --color-text-primary: var(--ds-color-text-primary); - --color-text-secondary: var(--ds-color-text-secondary); - --color-text-tertiary: var(--ds-color-text-tertiary); - - --font-weight-thin: var(--ds-font-weight-thin); - --font-weight-extralight: var(--ds-font-weight-extralight); - --font-weight-light: var(--ds-font-weight-light); - --font-weight-regular: var(--ds-font-weight-regular); - --font-weight-medium: var(--ds-font-weight-medium); - --font-weight-semibold: var(--ds-font-weight-semibold); - --font-weight-bold: var(--ds-font-weight-bold); - --font-weight-extrabold: var(--ds-font-weight-extrabold); - --font-weight-black: var(--ds-font-weight-black); - - --shadow-y-3: var(--ds-shadow-y-3); - --shadow-y-4: var(--ds-shadow-y-4); - --shadow-blur-3: var(--ds-shadow-blur-3); - --shadow-blur-4: var(--ds-shadow-blur-4); - - --shadow-1: var(--ds-shadow-1); - --shadow-2: var(--ds-shadow-2); - --shadow-3: var(--ds-shadow-3); - --shadow-4: var(--ds-shadow-4); + --ds-color-white: #FFFFFF; + --ds-color-white-alpha-5: rgba(255, 255, 255, 0.05); + --ds-color-white-alpha-10: rgba(255, 255, 255, 0.1); + --ds-color-white-alpha-20: rgba(255, 255, 255, 0.2); + --ds-color-white-alpha-30: rgba(255, 255, 255, 0.3); + --ds-color-white-alpha-40: rgba(255, 255, 255, 0.4); + --ds-color-white-alpha-50: rgba(255, 255, 255, 0.5); + --ds-color-white-alpha-60: rgba(255, 255, 255, 0.6); + --ds-color-white-alpha-70: rgba(255, 255, 255, 0.7); + --ds-color-white-alpha-80: rgba(255, 255, 255, 0.8); + --ds-color-white-alpha-90: rgba(255, 255, 255, 0.9); + --ds-color-black: #000000; + --ds-color-black-alpha-5: rgba(0, 0, 0, 0.05); + --ds-color-black-alpha-10: rgba(0, 0, 0, 0.1); + --ds-color-black-alpha-20: rgba(0, 0, 0, 0.2); + --ds-color-black-alpha-30: rgba(0, 0, 0, 0.3); + --ds-color-black-alpha-40: rgba(0, 0, 0, 0.4); + --ds-color-black-alpha-50: rgba(0, 0, 0, 0.5); + --ds-color-black-alpha-60: rgba(0, 0, 0, 0.6); + --ds-color-black-alpha-70: rgba(0, 0, 0, 0.7); + --ds-color-black-alpha-80: rgba(0, 0, 0, 0.8); + --ds-color-black-alpha-90: rgba(0, 0, 0, 0.9); + --ds-color-neutral-50: #F4F5F6; + --ds-color-neutral-100: #E6E8EA; + --ds-color-neutral-200: #CDD1D5; + --ds-color-neutral-300: #B1B8BE; + --ds-color-neutral-400: #8A949E; + --ds-color-neutral-500: #6D7882; + --ds-color-neutral-600: #58616A; + --ds-color-neutral-700: #464C53; + --ds-color-neutral-800: #33363D; + --ds-color-neutral-900: #1E2124; + --ds-color-neutral-950: #131416; + --ds-color-primary-50: #ECF2FE; + --ds-color-primary-100: #D8E5FD; + --ds-color-primary-200: #B1CEFB; + --ds-color-primary-300: #86AFF9; + --ds-color-primary-400: #4C87F6; + --ds-color-primary-500: #256EF4; + --ds-color-primary-600: #0B50D0; + --ds-color-primary-700: #083891; + --ds-color-primary-800: #052561; + --ds-color-primary-900: #03163A; + --ds-color-primary-950: #020F27; + --ds-color-danger-50: #FDEFEC; + --ds-color-danger-100: #FCDFD9; + --ds-color-danger-200: #F7AFA1; + --ds-color-danger-300: #F48771; + --ds-color-danger-400: #F05F42; + --ds-color-danger-500: #DE3412; + --ds-color-danger-600: #BD2C0F; + --ds-color-danger-700: #8A240F; + --ds-color-danger-800: #5C180A; + --ds-color-danger-900: #390D05; + --ds-color-danger-950: #260903; + --ds-color-warning-50: #FFF3DB; + --ds-color-warning-100: #FFE0A3; + --ds-color-warning-200: #FFC95C; + --ds-color-warning-300: #FFB114; + --ds-color-warning-400: #C78500; + --ds-color-warning-500: #9E6A00; + --ds-color-warning-600: #8A5C00; + --ds-color-warning-700: #614100; + --ds-color-warning-800: #422C00; + --ds-color-warning-900: #2E1F00; + --ds-color-warning-950: #241800; + --ds-color-success-50: #EAF6EC; + --ds-color-success-100: #D8EEDD; + --ds-color-success-200: #A9DAB4; + --ds-color-success-300: #7EC88E; + --ds-color-success-400: #3FA654; + --ds-color-success-500: #228738; + --ds-color-success-600: #267337; + --ds-color-success-700: #285D33; + --ds-color-success-800: #1F4727; + --ds-color-success-900: #122B18; + --ds-color-success-950: #0E2012; + --ds-font-weight-thin: 100; + --ds-font-weight-extralight: 200; + --ds-font-weight-light: 300; + --ds-font-weight-regular: 400; + --ds-font-weight-medium: 500; + --ds-font-weight-semibold: 600; + --ds-font-weight-bold: 700; + --ds-font-weight-extrabold: 800; + --ds-font-weight-black: 900; + --ds-radius-none: 0; + --ds-radius-xs: 2px; + --ds-radius-sm: 4px; + --ds-radius-md: 6px; + --ds-radius-lg: 8px; + --ds-radius-xl: 12px; + --ds-radius-full: 9999px; + --ds-radius-circle: 100%; + --ds-shadow-y-3: 8px; + --ds-shadow-y-4: 16px; + --ds-shadow-blur-3: 16px; + --ds-shadow-blur-4: 24px; + --ds-spacing-0: 0px; + --ds-spacing-1: 1px; + --ds-spacing-2: 2px; + --ds-spacing-3: 4px; + --ds-spacing-4: 6px; + --ds-spacing-5: 8px; + --ds-spacing-6: 10px; + --ds-spacing-7: 12px; + --ds-spacing-8: 16px; + --ds-spacing-9: 20px; + --ds-spacing-10: 24px; + --ds-spacing-11: 28px; + --ds-spacing-12: 32px; + --ds-spacing-13: 36px; + --ds-spacing-14: 40px; + --ds-spacing-15: 44px; + --ds-spacing-16: 48px; + --ds-spacing-17: 56px; + --ds-spacing-18: 64px; + --ds-spacing-19: 72px; + --ds-spacing-20: 80px; + --ds-spacing-21: 96px; + --ds-spacing-max: 1000px; + --ds-z-index-behind: -1; + --ds-z-index-base: 0; + --ds-z-index-above: 1; + --ds-z-index-header: 50; + --ds-z-index-overlay: 100; + --ds-z-index-modal: 200; + --ds-z-index-modal-content: 250; + --ds-color-alpha-shadow1: rgba(0, 0, 0, 0.05); + --ds-color-alpha-shadow2: rgba(0, 0, 0, 0.08); + --ds-color-alpha-shadow3: rgba(0, 0, 0, 0.12); +} - --z-index-behind: var(--ds-z-index-behind); - --z-index-base: var(--ds-z-index-base); - --z-index-above: var(--ds-z-index-above); - --z-index-header: var(--ds-z-index-header); - --z-index-overlay: var(--ds-z-index-overlay); - --z-index-modal: var(--ds-z-index-modal); +@theme inline { + --ds-shadow-1: 0px 1px 2px 0px var(--ds-color-alpha-shadow1), 0px 0px 2px 0px var(--ds-color-alpha-shadow1); + --ds-shadow-2: 0px 0px 2px 0px var(--ds-color-alpha-shadow1), 0px 4px 8px 0px var(--ds-color-alpha-shadow2); + --ds-shadow-3: 0px 0px 2px 0px var(--ds-color-alpha-shadow2), 0px var(--ds-shadow-y-3) var(--ds-shadow-blur-3) 0px var(--ds-color-alpha-shadow3); + --ds-shadow-4: 0px 0px 2px 0px var(--ds-color-alpha-shadow2), 0px var(--ds-shadow-y-4) var(--ds-shadow-blur-4) 0px var(--ds-color-alpha-shadow3); + --ds-color-text-primary: var(--ds-color-neutral-950); + --ds-color-text-secondary: var(--ds-color-neutral-600); + --ds-color-text-tertiary: var(--ds-color-neutral-400); } @utility bg-* { background-color: --value(--color-*); } @@ -125,6 +147,34 @@ @utility border-* { border-color: --value(--color-*); } @utility fill-* { fill: --value(--color-*); } @utility stroke-* { stroke: --value(--color-*); } +@utility p-* { padding: --value(--spacing-*); } +@utility px-* { padding-left: --value(--spacing-*); padding-right: --value(--spacing-*); } +@utility py-* { padding-top: --value(--spacing-*); padding-bottom: --value(--spacing-*); } +@utility pt-* { padding-top: --value(--spacing-*); } +@utility pr-* { padding-right: --value(--spacing-*); } +@utility pb-* { padding-bottom: --value(--spacing-*); } +@utility pl-* { padding-left: --value(--spacing-*); } +@utility m-* { margin: --value(--spacing-*); } +@utility mx-* { margin-left: --value(--spacing-*); margin-right: --value(--spacing-*); } +@utility my-* { margin-top: --value(--spacing-*); margin-bottom: --value(--spacing-*); } +@utility mt-* { margin-top: --value(--spacing-*); } +@utility mr-* { margin-right: --value(--spacing-*); } +@utility mb-* { margin-bottom: --value(--spacing-*); } +@utility ml-* { margin-left: --value(--spacing-*); } +@utility w-* { width: --value(--spacing-*); } +@utility h-* { height: --value(--spacing-*); } +@utility min-w-* { min-width: --value(--spacing-*); } +@utility min-h-* { min-height: --value(--spacing-*); } +@utility max-w-* { max-width: --value(--spacing-*); } +@utility max-h-* { max-height: --value(--spacing-*); } +@utility gap-* { gap: --value(--spacing-*); } +@utility space-x-* { & > :not(:last-child) { --tw-space-x-reverse: 0; margin-inline-start: calc(--value(--spacing-*) * var(--tw-space-x-reverse)); margin-inline-end: calc(--value(--spacing-*) * calc(1 - var(--tw-space-x-reverse))); } } +@utility space-y-* { & > :not(:last-child) { --tw-space-y-reverse: 0; margin-block-start: calc(--value(--spacing-*) * var(--tw-space-y-reverse)); margin-block-end: calc(--value(--spacing-*) * calc(1 - var(--tw-space-y-reverse))); } } +@utility space-x-reverse { & > :not(:last-child) { --tw-space-x-reverse: 1; } } +@utility space-y-reverse { & > :not(:last-child) { --tw-space-y-reverse: 1; } } +@utility rounded-* { border-radius: --value(--spacing-*); } @utility font-* { font-weight: --value(--font-weight-*); } +@utility tracking-* { letter-spacing: --value(--tracking-*); } @utility shadow-* { box-shadow: --value(--shadow-*); } +@utility border-width-* { border-width: --value(--border-width-*); } @utility z-* { z-index: --value(--z-index-*); } diff --git a/packages/css/token.css b/packages/css/token.css index 28fb59ea..4bd9d288 100644 --- a/packages/css/token.css +++ b/packages/css/token.css @@ -10,7 +10,6 @@ --ds-color-white-alpha-70: rgba(255, 255, 255, 0.7); --ds-color-white-alpha-80: rgba(255, 255, 255, 0.8); --ds-color-white-alpha-90: rgba(255, 255, 255, 0.9); - --ds-color-black: #000000; --ds-color-black-alpha-5: rgba(0, 0, 0, 0.05); --ds-color-black-alpha-10: rgba(0, 0, 0, 0.1); @@ -22,7 +21,6 @@ --ds-color-black-alpha-70: rgba(0, 0, 0, 0.7); --ds-color-black-alpha-80: rgba(0, 0, 0, 0.8); --ds-color-black-alpha-90: rgba(0, 0, 0, 0.9); - --ds-color-neutral-50: #F4F5F6; --ds-color-neutral-100: #E6E8EA; --ds-color-neutral-200: #CDD1D5; @@ -34,7 +32,6 @@ --ds-color-neutral-800: #33363D; --ds-color-neutral-900: #1E2124; --ds-color-neutral-950: #131416; - --ds-color-primary-50: #ECF2FE; --ds-color-primary-100: #D8E5FD; --ds-color-primary-200: #B1CEFB; @@ -46,7 +43,6 @@ --ds-color-primary-800: #052561; --ds-color-primary-900: #03163A; --ds-color-primary-950: #020F27; - --ds-color-danger-50: #FDEFEC; --ds-color-danger-100: #FCDFD9; --ds-color-danger-200: #F7AFA1; @@ -58,7 +54,6 @@ --ds-color-danger-800: #5C180A; --ds-color-danger-900: #390D05; --ds-color-danger-950: #260903; - --ds-color-warning-50: #FFF3DB; --ds-color-warning-100: #FFE0A3; --ds-color-warning-200: #FFC95C; @@ -70,7 +65,6 @@ --ds-color-warning-800: #422C00; --ds-color-warning-900: #2E1F00; --ds-color-warning-950: #241800; - --ds-color-success-50: #EAF6EC; --ds-color-success-100: #D8EEDD; --ds-color-success-200: #A9DAB4; @@ -82,15 +76,6 @@ --ds-color-success-800: #1F4727; --ds-color-success-900: #122B18; --ds-color-success-950: #0E2012; - - --ds-color-alpha-shadow1: rgba(0, 0, 0, 0.05); - --ds-color-alpha-shadow2: rgba(0, 0, 0, 0.08); - --ds-color-alpha-shadow3: rgba(0, 0, 0, 0.12); - - --ds-color-text-primary: var(--ds-color-neutral-950); - --ds-color-text-secondary: var(--ds-color-neutral-600); - --ds-color-text-tertiary: var(--ds-color-neutral-400); - --ds-font-weight-thin: 100; --ds-font-weight-extralight: 200; --ds-font-weight-light: 300; @@ -100,7 +85,14 @@ --ds-font-weight-bold: 700; --ds-font-weight-extrabold: 800; --ds-font-weight-black: 900; - + --ds-radius-none: 0; + --ds-radius-xs: 2px; + --ds-radius-sm: 4px; + --ds-radius-md: 6px; + --ds-radius-lg: 8px; + --ds-radius-xl: 12px; + --ds-radius-full: 9999px; + --ds-radius-circle: 100%; --ds-shadow-y-3: 8px; --ds-shadow-y-4: 16px; --ds-shadow-blur-3: 16px; @@ -109,15 +101,6 @@ --ds-shadow-2: 0px 0px 2px 0px var(--ds-color-alpha-shadow1), 0px 4px 8px 0px var(--ds-color-alpha-shadow2); --ds-shadow-3: 0px 0px 2px 0px var(--ds-color-alpha-shadow2), 0px var(--ds-shadow-y-3) var(--ds-shadow-blur-3) 0px var(--ds-color-alpha-shadow3); --ds-shadow-4: 0px 0px 2px 0px var(--ds-color-alpha-shadow2), 0px var(--ds-shadow-y-4) var(--ds-shadow-blur-4) 0px var(--ds-color-alpha-shadow3); - - --ds-z-index-behind: -1; - --ds-z-index-base: 0; - --ds-z-index-above: 1; - --ds-z-index-header: 50; - --ds-z-index-overlay: 100; - --ds-z-index-modal: 200; - --ds-z-index-modal-content: 250; - --ds-spacing-0: 0px; --ds-spacing-1: 1px; --ds-spacing-2: 2px; @@ -141,4 +124,17 @@ --ds-spacing-20: 80px; --ds-spacing-21: 96px; --ds-spacing-max: 1000px; -} + --ds-z-index-behind: -1; + --ds-z-index-base: 0; + --ds-z-index-above: 1; + --ds-z-index-header: 50; + --ds-z-index-overlay: 100; + --ds-z-index-modal: 200; + --ds-z-index-modal-content: 250; + --ds-color-alpha-shadow1: rgba(0, 0, 0, 0.05); + --ds-color-alpha-shadow2: rgba(0, 0, 0, 0.08); + --ds-color-alpha-shadow3: rgba(0, 0, 0, 0.12); + --ds-color-text-primary: var(--ds-color-neutral-950); + --ds-color-text-secondary: var(--ds-color-neutral-600); + --ds-color-text-tertiary: var(--ds-color-neutral-400); +} \ No newline at end of file diff --git a/packages/react/package.json b/packages/react/package.json index 16366f76..50f78c3a 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -57,7 +57,7 @@ "@rollup/plugin-babel": "catalog:build", "@rollup/plugin-commonjs": "catalog:build", "@rollup/plugin-node-resolve": "catalog:build", - "@storybook/react": "^10.1.4", + "@storybook/react": "^10.2.8", "@types/react": "catalog:types", "@types/react-dom": "catalog:types", "react": "catalog:core", diff --git a/packages/react/src/accordion/Accordion.stories.tsx b/packages/react/src/accordion/Accordion.stories.tsx index daf84fff..a8280f14 100644 --- a/packages/react/src/accordion/Accordion.stories.tsx +++ b/packages/react/src/accordion/Accordion.stories.tsx @@ -36,56 +36,96 @@ export default meta; type Story = StoryObj; -export const AllVariants: Story = { +export const Default: Story = { + parameters: { + docs: { description: { story: '가장 기본적인 Accordion 사용법입니다. 하나의 항목만 열 수 있습니다.' } }, + }, + render: () => ( +
+ + + + 첫 번째 아이템 + + +
첫 번째 아이템의 내용입니다.
+
+
+ + + 두 번째 아이템 + + +
두 번째 아이템의 내용입니다.
+
+
+ + + 세 번째 아이템 + + +
세 번째 아이템의 내용입니다.
+
+
+
+
+ ), +}; + +export const Single: Story = { parameters: { controls: { disable: true }, + docs: { description: { story: 'single 타입은 한 번에 하나의 항목만 열 수 있습니다. collapsible 옵션으로 열린 항목을 다시 닫을 수 있습니다.' } }, }, - args: {}, render: () => ( -
-
-

Single Type

- - - - 첫 번째 아이템 - - -
첫 번째 아이템의 내용입니다.
-
-
- - - 두 번째 아이템 - - -
두 번째 아이템의 내용입니다.
-
-
-
-
+
+ + + + 첫 번째 아이템 + + +
첫 번째 아이템의 내용입니다. 다른 항목을 열면 이 항목은 닫힙니다.
+
+
+ + + 두 번째 아이템 + + +
두 번째 아이템의 내용입니다.
+
+
+
+
+ ), +}; -
-

Multiple Type

- - - - Multiple 첫 번째 - - -
Multiple 첫 번째 내용입니다.
-
-
- - - Multiple 두 번째 - - -
Multiple 두 번째 내용입니다.
-
-
-
-
+export const Multiple: Story = { + parameters: { + controls: { disable: true }, + docs: { description: { story: 'multiple 타입은 여러 항목을 동시에 열 수 있습니다.' } }, + }, + render: () => ( +
+ + + + 첫 번째 아이템 + + +
첫 번째 내용입니다. 다른 항목과 동시에 열 수 있습니다.
+
+
+ + + 두 번째 아이템 + + +
두 번째 내용입니다.
+
+
+
), }; diff --git a/packages/react/src/badge/Badge.stories.tsx b/packages/react/src/badge/Badge.stories.tsx index 59f9b2bf..88d3f98e 100644 --- a/packages/react/src/badge/Badge.stories.tsx +++ b/packages/react/src/badge/Badge.stories.tsx @@ -43,13 +43,38 @@ export default meta; type Story = StoryObj; -export const AllVariants: Story = { +const rowStyle = { display: 'flex', gap: 12, alignItems: 'center', flexWrap: 'wrap' } as const; + +export const Default: Story = { + parameters: { + docs: { description: { story: '가장 기본적인 Badge 사용법입니다.' } }, + }, + args: { + children: 'Badge', + }, +}; + +export const Sizes: Story = { + parameters: { + controls: { disable: true }, + docs: { description: { story: '사용 가능한 모든 사이즈를 비교합니다.' } }, + }, + render: () => ( +
+ Small + Medium + Large +
+ ), +}; + +export const Variants: Story = { parameters: { controls: { disable: true }, + docs: { description: { story: '사용 가능한 모든 variant 스타일을 비교합니다.' } }, }, - args: {}, render: () => ( -
+
Default Primary Success diff --git a/packages/react/src/badge/Badge.tsx b/packages/react/src/badge/Badge.tsx index d866f979..7ca7b453 100644 --- a/packages/react/src/badge/Badge.tsx +++ b/packages/react/src/badge/Badge.tsx @@ -1,13 +1,13 @@ -import { clsx } from 'clsx'; +import { clsx as cx } from 'clsx'; import { type ComponentProps, type CSSProperties, forwardRef } from 'react'; import { match } from 'ts-pattern'; -import { colors, type ResponsiveFontSize } from '../token'; +import { colors, type ResponsiveFontSize, radius } from '../token'; import { Typography } from '../typography'; import styles from './Badge.module.css'; -type BadgeSize = 'sm' | 'md' | 'lg'; +export type BadgeSize = 'sm' | 'md' | 'lg'; -type BadgeVariant = 'default' | 'danger' | 'primary' | 'success' | 'warning'; +export type BadgeVariant = 'default' | 'danger' | 'primary' | 'success' | 'warning'; export interface BadgeProps extends ComponentProps<'div'> { size?: BadgeSize; @@ -19,7 +19,7 @@ export const Badge = forwardRef( const style = { ..._style, '--cocso-badge-padding': getPadding(size), - '--cocso-badge-border-radius': '6px', + '--cocso-badge-border-radius': radius.md, '--cocso-badge-bg-color': getBackgroundColor(variant), } as CSSProperties; @@ -27,7 +27,7 @@ export const Badge = forwardRef( const fontSize = getFontSize(size); return ( -
+
; -export const AllVariants: Story = { +const sectionStyle = { marginBottom: 12, fontSize: 14, fontWeight: 600, color: '#666' } as const; +const rowStyle = { display: 'flex', gap: 12, alignItems: 'center', flexWrap: 'wrap' } as const; +const containerStyle = { display: 'flex', flexDirection: 'column', gap: 24 } as const; + +export const Default: Story = { + parameters: { + docs: { description: { story: '가장 기본적인 Button 사용법입니다.' } }, + }, + args: { + children: 'Button', + }, +}; + +export const Sizes: Story = { parameters: { controls: { disable: true }, + docs: { description: { story: '사용 가능한 모든 사이즈를 비교합니다.' } }, }, - args: {}, render: () => ( -
- {/* Variants */} -
-

Variants

-
- - - - - - - -
-
- - {/* Sizes */} -
-

Sizes

-
- - - - - -
-
- - {/* With Icons */} -
-

With Icons

-
- - - - -
-
+
+ + + + +
+ ), +}; - {/* States */} -
-

States

-
- - - - -
-
+export const Variants: Story = { + parameters: { + controls: { disable: true }, + docs: { description: { story: '사용 가능한 모든 variant 스타일을 비교합니다.' } }, + }, + render: () => ( +
+ + + + + + + +
+ ), +}; - {/* Shapes */} -
-

Shapes

-
- - - -
-
+export const Shapes: Story = { + parameters: { + controls: { disable: true }, + docs: { description: { story: '사용 가능한 모든 shape을 비교합니다.' } }, + }, + render: () => ( +
+ + +
), }; -export const SizeWithIcons: Story = { +export const States: Story = { parameters: { controls: { disable: true }, - docs: { - description: { - story: '모든 사이즈에서 prefix, suffix 아이콘과의 조합을 보여줍니다. xs 사이즈에서 개선된 간격을 확인할 수 있습니다.', - }, - }, + docs: { description: { story: 'disabled, loading 등 다양한 상태를 비교합니다.' } }, }, render: () => ( -
- {/* Text Only */} -
-

Text Only

-
- - - - - -
-
+
+ + + + +
+ ), +}; - {/* With Prefix */} +export const WithIcons: Story = { + parameters: { + controls: { disable: true }, + docs: { description: { story: 'prefix, suffix 아이콘과의 조합을 모든 사이즈에서 보여줍니다.' } }, + }, + render: () => ( +
-

With Prefix Icon

-
+

With Prefix Icon

+
-
- {/* With Suffix */}
-

With Suffix Icon

-
+

With Suffix Icon

+
-
- {/* With Both Icons */}
-

With Both Icons

-
+

With Both Icons

+
-
- {/* Icon Only */}
-

Icon Only (SVG Only)

-
+

Icon Only (SVG Only)

+
- -
-
-
- ), -}; - -export const VariantShowcase: Story = { - parameters: { - controls: { disable: true }, - docs: { - description: { - story: '모든 variant에서 다양한 조합을 보여줍니다.', - }, - }, - }, - render: () => ( -
- {['primary', 'secondary', 'tertiary', 'success', 'error', 'warning', 'neutral'].map((variant) => ( -
-

- {variant} -

-
- - - - - - - -
-
- ))} -
- ), -}; - -export const StatesCombinations: Story = { - parameters: { - controls: { disable: true }, - docs: { - description: { - story: '다양한 상태 조합에서의 버튼 동작을 보여줍니다.', - }, - }, - }, - render: () => ( -
-
-

Loading States

-
- - - - -
-
- -
-

Disabled States

-
- - - - -
-
- -
-

Shapes with Icons

-
- - - - -
diff --git a/packages/react/src/button/Button.tsx b/packages/react/src/button/Button.tsx index bfc37441..784f8ef2 100644 --- a/packages/react/src/button/Button.tsx +++ b/packages/react/src/button/Button.tsx @@ -4,17 +4,17 @@ import type { ComponentPropsWithoutRef, CSSProperties, ReactElement, ReactNode } import { cloneElement, forwardRef, isValidElement } from 'react'; import { match } from 'ts-pattern'; import { Spinner } from '../spinner'; -import { colors, type FontWeight, fontWeight } from '../token'; +import { colors, type FontWeight, fontWeight, radius, spacing } from '../token'; import styles from './Button.module.css'; -export type ButtonSize = 'xl' | 'lg' | 'md' | 'sm' | 'xs'; +export type ButtonSize = 'lg' | 'md' | 'sm' | 'xs'; export type ButtonVariant = | 'primary' | 'secondary' | 'tertiary' | 'success' - | 'error' + | 'danger' | 'warning' | 'neutral'; @@ -56,7 +56,7 @@ export const Button = forwardRef( const style = { ..._style, ...getSizeStyles(size), - '--cocso-button-font-color': getColor(variant), + '--cocso-button-font-color': getFontColor(variant), '--cocso-button-font-weight': fontWeight[weight], '--cocso-button-border': getBorder(variant), '--cocso-button-border-radius': getBorderRadius(shape, size), @@ -117,34 +117,30 @@ export const Button = forwardRef( const getSizeStyles = (size: ButtonSize) => { const height = match(size) - .with('xl', () => 56) - .with('lg', () => 48) - .with('md', () => 40) - .with('sm', () => 32) - .with('xs', () => 28) + .with('lg', () => spacing.s16) + .with('md', () => spacing.s14) + .with('sm', () => spacing.s12) + .with('xs', () => spacing.s11) .exhaustive(); const inlinePadding = match(size) - .with('xl', () => 16) .with('lg', () => 14) .with('md', () => 10) .with('sm', () => 8) .with('xs', () => 6) .exhaustive(); const contentPadding = match(size) - .with('xl', () => '0 6px') .with('lg', () => '0 6px') .with('md', () => '0 6px') .with('sm', () => '0 2px') .with('xs', () => '0') .exhaustive(); const fontSize = match(size) - .with('xl', () => 16) .with('lg', () => 16) .with('xs', () => 12) .otherwise(() => 14); return { - '--cocso-button-height': `${height}px`, + '--cocso-button-height': height, '--cocso-button-padding-inline': `${inlinePadding}px`, '--cocso-button-content-padding': contentPadding, '--cocso-button-font-size': `${fontSize}px`, @@ -155,17 +151,17 @@ const getBorderRadius = (shape: ButtonShape, size: ButtonSize) => { return match(shape) .with('square', () => { return match(size) - .with('xs', () => '4px') - .otherwise(() => '6px'); + .with('xs', () => radius.sm) + .otherwise(() => radius.md); }) - .with('circle', () => '100%') - .with('rounded', () => '100px') + .with('circle', () => radius.circle) + .with('rounded', () => radius.full) .exhaustive(); }; -const getColor = (variant: ButtonVariant) => { +const getFontColor = (variant: ButtonVariant) => { return match(variant) - .with('primary', 'success', 'error', 'neutral', () => colors.white) + .with('primary', 'success', 'danger', 'neutral', () => colors.white) .with('secondary', 'tertiary', 'warning', () => colors.neutral950) .exhaustive(); }; @@ -182,7 +178,7 @@ const getBackgroundColor = (variant: ButtonVariant) => { .with('secondary', () => colors.white) .with('tertiary', () => colors.transparent) .with('success', () => colors.success500) - .with('error', () => colors.danger500) + .with('danger', () => colors.danger500) .with('warning', () => colors.warning300) .with('neutral', () => colors.neutral950) .exhaustive(); @@ -194,7 +190,7 @@ const getBackgroundColorHover = (variant: ButtonVariant) => { .with('secondary', () => colors.neutral50) .with('tertiary', () => colors.neutral50) .with('success', () => colors.success600) - .with('error', () => colors.danger600) + .with('danger', () => colors.danger600) .with('warning', () => colors.warning400) .with('neutral', () => colors.neutral800) .exhaustive(); @@ -206,7 +202,7 @@ const getBackgroundColorActive = (variant: ButtonVariant) => { .with('secondary', () => colors.neutral100) .with('tertiary', () => colors.neutral100) .with('success', () => colors.success700) - .with('error', () => colors.danger700) + .with('danger', () => colors.danger700) .with('warning', () => colors.warning500) .with('neutral', () => colors.neutral700) .exhaustive(); diff --git a/packages/react/src/checkbox/Checkbox.module.css b/packages/react/src/checkbox/Checkbox.module.css index ce95f0dd..b538536b 100644 --- a/packages/react/src/checkbox/Checkbox.module.css +++ b/packages/react/src/checkbox/Checkbox.module.css @@ -14,7 +14,7 @@ color: var(--cocso-checkbox-color); cursor: pointer; border: 1px solid var(--cocso-checkbox-border-color); - border-radius: 4px; + border-radius: var(--ds-radius-sm); background-color: var(--cocso-checkbox-bg-color); opacity: 1; transition: color 0.15s ease-in-out, border-color 0.15s ease-in-out, background-color 0.15s ease-in-out; diff --git a/packages/react/src/checkbox/Checkbox.stories.tsx b/packages/react/src/checkbox/Checkbox.stories.tsx index 0d3f01ec..be678c2b 100644 --- a/packages/react/src/checkbox/Checkbox.stories.tsx +++ b/packages/react/src/checkbox/Checkbox.stories.tsx @@ -61,9 +61,36 @@ export default meta; type Story = StoryObj; -export const AllVariants: Story = { +const columnStyle = { display: 'flex', flexDirection: 'column', gap: 8, alignItems: 'flex-start' } as const; + +export const Default: Story = { + parameters: { + docs: { description: { story: '가장 기본적인 Checkbox 사용법입니다.' } }, + }, + render: () => { + const [status, setStatus] = useState('off'); + return ; + }, +}; + +export const Sizes: Story = { + parameters: { + controls: { disable: true }, + docs: { description: { story: '사용 가능한 모든 사이즈를 비교합니다.' } }, + }, + render: () => ( +
+ {}} label="Small" /> + {}} label="Medium" /> + {}} label="Large" /> +
+ ), +}; + +export const States: Story = { parameters: { controls: { disable: true }, + docs: { description: { story: 'on, off, intermediate 세 가지 상태를 비교합니다.' } }, }, render: () => { const [onState, setOnState] = useState('on'); @@ -71,39 +98,31 @@ export const AllVariants: Story = { const [intermediateState, setIntermediateState] = useState('intermediate'); return ( -
-
-

States

- - - -
- -
-

Sizes

- {}} label="Small" /> - {}} label="Medium" /> - {}} label="Large" /> -
- -
-

Disabled

- {}} label="Disabled Checked" disabled /> - {}} label="Disabled Unchecked" disabled /> -
+
+ + +
); }, }; +export const Disabled: Story = { + parameters: { + controls: { disable: true }, + docs: { description: { story: '비활성화 상태를 보여줍니다.' } }, + }, + render: () => ( +
+ {}} label="Disabled Checked" disabled /> + {}} label="Disabled Unchecked" disabled /> +
+ ), +}; + export const Playground: Story = { render: args => { const [status, setStatus] = useState(args.status || 'off'); - return ; }, args: { diff --git a/packages/react/src/checkbox/Checkbox.tsx b/packages/react/src/checkbox/Checkbox.tsx index fef8d3f8..4dae2669 100644 --- a/packages/react/src/checkbox/Checkbox.tsx +++ b/packages/react/src/checkbox/Checkbox.tsx @@ -1,4 +1,4 @@ -import { CheckIcon } from '@cocso-ui/react-icons'; +import { CheckIcon, CheckIndeterminateSmallIcon } from '@cocso-ui/react-icons'; import * as CheckboxPrimitive from '@radix-ui/react-checkbox'; import { clsx as cx } from 'clsx'; import { @@ -89,7 +89,7 @@ export const Checkbox = forwardRef, style={{ opacity: status === 'intermediate' ? 1 : 0 }} aria-hidden="true" > - +
diff --git a/packages/react/src/day-picker/DayPicker.module.css b/packages/react/src/day-picker/DayPicker.module.css index d84a197b..460f0ed4 100644 --- a/packages/react/src/day-picker/DayPicker.module.css +++ b/packages/react/src/day-picker/DayPicker.module.css @@ -86,7 +86,7 @@ text-align: center; cursor: pointer; line-height: 1.7rem; - border-radius: 8px; + border-radius: var(--ds-radius-lg); } :global(.react-datepicker__day:hover) { @@ -111,6 +111,6 @@ } :global(.react-datepicker__day--disabled) { - opacity: 0.3; + opacity: 0.4; cursor: not-allowed; } diff --git a/packages/react/src/day-picker/DayPicker.stories.tsx b/packages/react/src/day-picker/DayPicker.stories.tsx index 00963f92..44ab95c0 100644 --- a/packages/react/src/day-picker/DayPicker.stories.tsx +++ b/packages/react/src/day-picker/DayPicker.stories.tsx @@ -50,6 +50,9 @@ const formatValue = (value: Date | undefined) => { }; export const Default: Story = { + parameters: { + docs: { description: { story: '가장 기본적인 DayPicker 사용법입니다. 버튼을 클릭하면 달력이 열립니다.' } }, + }, render: () => { const [value, setValue] = useState(); @@ -71,6 +74,10 @@ export const Default: Story = { }; export const ButtonVariants: Story = { + parameters: { + controls: { disable: true }, + docs: { description: { story: '다양한 버튼 variant를 트리거로 사용하는 예시입니다.' } }, + }, render: () => { const [value1, setValue1] = useState(); const [value2, setValue2] = useState(); @@ -104,6 +111,10 @@ export const ButtonVariants: Story = { }; export const WithSelect: Story = { + parameters: { + controls: { disable: true }, + docs: { description: { story: 'Select 컴포넌트를 트리거로 사용하는 예시입니다.' } }, + }, render: () => { const [value, setValue] = useState(); @@ -124,6 +135,10 @@ export const WithSelect: Story = { }; export const Disabled: Story = { + parameters: { + controls: { disable: true }, + docs: { description: { story: '비활성화된 DayPicker입니다. 클릭해도 달력이 열리지 않습니다.' } }, + }, render: () => { const [value, setValue] = useState(new Date(2024, 5, 15)); @@ -143,6 +158,10 @@ export const Disabled: Story = { }; export const FormExample: Story = { + parameters: { + controls: { disable: true }, + docs: { description: { story: '폼 내에서 시작/종료 날짜를 선택하는 실용적인 예시입니다.' } }, + }, render: () => { const [formData, setFormData] = useState({ startDate: undefined as Date | undefined, @@ -196,6 +215,10 @@ export const FormExample: Story = { }; export const WithPreselectedValue: Story = { + parameters: { + controls: { disable: true }, + docs: { description: { story: '초기값이 설정된 DayPicker입니다.' } }, + }, render: () => { const [value, setValue] = useState(new Date(2024, 11, 25)); @@ -215,6 +238,10 @@ export const WithPreselectedValue: Story = { }; export const WithTodayHighlight: Story = { + parameters: { + controls: { disable: true }, + docs: { description: { story: '오늘 날짜가 자동으로 하이라이트되는 달력입니다.' } }, + }, render: () => { const [value, setValue] = useState(); @@ -240,6 +267,10 @@ export const WithTodayHighlight: Story = { }; export const KoreanLocale: Story = { + parameters: { + controls: { disable: true }, + docs: { description: { story: '한국어 로케일이 적용된 달력입니다. 헤더가 "2025년 9월" 형식으로 표시됩니다.' } }, + }, render: () => { const [value, setValue] = useState(); @@ -265,6 +296,10 @@ export const KoreanLocale: Story = { }; export const MonthPickerStyle: Story = { + parameters: { + controls: { disable: true }, + docs: { description: { story: 'MonthPicker와 동일한 스타일의 커스텀 헤더와 테마가 적용된 달력입니다.' } }, + }, render: () => { const [value, setValue] = useState(); @@ -290,6 +325,10 @@ export const MonthPickerStyle: Story = { }; export const DateFormats: Story = { + parameters: { + controls: { disable: true }, + docs: { description: { story: '다양한 날짜 포맷과 버튼 크기를 조합한 예시입니다.' } }, + }, render: () => { const [value1, setValue1] = useState(); const [value2, setValue2] = useState(); diff --git a/packages/react/src/dropdown/Dropdown.stories.tsx b/packages/react/src/dropdown/Dropdown.stories.tsx index 1378b926..03c55c0b 100644 --- a/packages/react/src/dropdown/Dropdown.stories.tsx +++ b/packages/react/src/dropdown/Dropdown.stories.tsx @@ -30,42 +30,47 @@ export default meta; type Story = StoryObj; -export const AllVariants: Story = { +export const Default: Story = { parameters: { - controls: { disable: true }, + docs: { description: { story: '가장 기본적인 Dropdown 사용법입니다. 버튼을 클릭하면 메뉴가 열립니다.' } }, }, - args: {}, render: () => ( -
- - - - - - - 메뉴 아이템 1 - 메뉴 아이템 2 - 메뉴 아이템 3 - - - + + + + + + + 메뉴 아이템 1 + 메뉴 아이템 2 + 메뉴 아이템 3 + + + + ), +}; - - - - - - - 편집 - 복사 - 비활성화 - 삭제 - - - -
+export const IconTrigger: Story = { + parameters: { + controls: { disable: true }, + docs: { description: { story: '아이콘 버튼을 트리거로 사용하는 드롭다운입니다. 비활성화된 항목도 포함됩니다.' } }, + }, + render: () => ( + + + + + + + 편집 + 복사 + 비활성화 + 삭제 + + + ), }; diff --git a/packages/react/src/link/Link.stories.tsx b/packages/react/src/link/Link.stories.tsx index 1978186f..1b3d4786 100644 --- a/packages/react/src/link/Link.stories.tsx +++ b/packages/react/src/link/Link.stories.tsx @@ -67,63 +67,58 @@ export default meta; type Story = StoryObj; -export const AllVariants: Story = { +const columnStyle = { display: 'flex', flexDirection: 'column', gap: 8, alignItems: 'flex-start' } as const; + +export const Default: Story = { + parameters: { + docs: { description: { story: '가장 기본적인 Link 사용법입니다.' } }, + }, + args: { + children: 'Link', + href: '#', + }, +}; + +export const Sizes: Story = { parameters: { controls: { disable: true }, + docs: { description: { story: '사용 가능한 모든 사이즈를 비교합니다.' } }, }, - args: {}, render: () => ( -
-
-

Sizes

- - Extra Small Link - - - Small Link - - - Medium Link - - - Large Link - -
+
+ Extra Small Link + Small Link + Medium Link + Large Link +
+ ), +}; -
-

Font Weights

- - Light Link - - - Normal Link - - - Medium Link - - - Semibold Link - - - Bold Link - -
+export const Weights: Story = { + parameters: { + controls: { disable: true }, + docs: { description: { story: '사용 가능한 폰트 굵기를 비교합니다.' } }, + }, + render: () => ( +
+ Light Link + Normal Link + Medium Link + Semibold Link + Bold Link +
+ ), +}; -
-

Indicator

- - With Indicator - - - Without Indicator - -
+export const WithIndicator: Story = { + parameters: { + controls: { disable: true }, + docs: { description: { story: 'indicator 속성의 on/off를 비교합니다.' } }, + }, + render: () => ( +
+ With Indicator + Without Indicator
), }; diff --git a/packages/react/src/modal/Modal.module.css b/packages/react/src/modal/Modal.module.css index 7fdd72b2..c334be3e 100644 --- a/packages/react/src/modal/Modal.module.css +++ b/packages/react/src/modal/Modal.module.css @@ -27,7 +27,7 @@ max-width: 380px; max-height: 85vh; padding: var(--ds-spacing-9); - border-radius: 12px; + border-radius: var(--ds-radius-xl); background-color: var(--ds-color-white); animation: content-show 0.4s cubic-bezier(0.16, 1, 0.3, 1); z-index: var(--ds-z-index-modal); diff --git a/packages/react/src/modal/Modal.stories.tsx b/packages/react/src/modal/Modal.stories.tsx index 90eed865..d582318d 100644 --- a/packages/react/src/modal/Modal.stories.tsx +++ b/packages/react/src/modal/Modal.stories.tsx @@ -44,54 +44,93 @@ export default meta; type Story = StoryObj; -export const AllVariants: Story = { +export const Default: Story = { + parameters: { + docs: { description: { story: '가장 기본적인 Modal 사용법입니다. 제목, 설명, 확인/취소 버튼이 포함됩니다.' } }, + }, + render: () => { + const [open, setOpen] = useState(false); + + return ( + + + + + + 기본 모달 + 기본 모달의 설명입니다. +
+ + + + +
+
+
+ ); + }, +}; + +export const Confirm: Story = { parameters: { controls: { disable: true }, + docs: { description: { story: '확인/취소 동작이 포함된 확인 모달입니다.' } }, }, render: () => { - const [basicOpen, setBasicOpen] = useState(false); - const [confirmOpen, setConfirmOpen] = useState(false); + const [open, setOpen] = useState(false); return ( -
- - - - - - 기본 모달 - 기본 모달의 설명입니다. -
- - - - -
-
-
+ + + + + + 변경 사항 저장 + 변경 사항을 저장하시겠습니까? 이 작업은 되돌릴 수 없습니다. +
+ + + + +
+
+
+ ); + }, +}; + +export const Danger: Story = { + parameters: { + controls: { disable: true }, + docs: { description: { story: '삭제 등 위험한 동작을 확인하는 모달입니다.' } }, + }, + render: () => { + const [open, setOpen] = useState(false); - - - - - - 항목 삭제 - 정말로 삭제하시겠습니까? -
- - - - - - -
-
-
-
+ return ( + + + + + + 항목 삭제 + 정말로 삭제하시겠습니까? +
+ + + + + + +
+
+
); }, }; diff --git a/packages/react/src/month-picker/MonthPicker.module.css b/packages/react/src/month-picker/MonthPicker.module.css index 7e3815bc..7df3f93f 100644 --- a/packages/react/src/month-picker/MonthPicker.module.css +++ b/packages/react/src/month-picker/MonthPicker.module.css @@ -74,7 +74,7 @@ font-size: 14px; font-weight: 500; cursor: pointer; - border-radius: 8px; + border-radius: var(--ds-radius-lg); } :global(.react-datepicker__month-text:hover) { diff --git a/packages/react/src/month-picker/MonthPicker.stories.tsx b/packages/react/src/month-picker/MonthPicker.stories.tsx index dc5942a7..dbfb0ed0 100644 --- a/packages/react/src/month-picker/MonthPicker.stories.tsx +++ b/packages/react/src/month-picker/MonthPicker.stories.tsx @@ -68,6 +68,9 @@ const formatValue = (value: Date | undefined) => { }; export const Default: Story = { + parameters: { + docs: { description: { story: '가장 기본적인 MonthPicker 사용법입니다. 버튼을 클릭하면 월 선택 패널이 열립니다.' } }, + }, render: () => { const [value, setValue] = useState(); @@ -89,6 +92,10 @@ export const Default: Story = { }; export const ButtonVariants: Story = { + parameters: { + controls: { disable: true }, + docs: { description: { story: '다양한 버튼 variant를 트리거로 사용하는 예시입니다.' } }, + }, render: () => { const [value1, setValue1] = useState(); const [value2, setValue2] = useState(); @@ -122,6 +129,10 @@ export const ButtonVariants: Story = { }; export const WithSelect: Story = { + parameters: { + controls: { disable: true }, + docs: { description: { story: 'Select 컴포넌트를 트리거로 사용하는 예시입니다.' } }, + }, render: () => { const [value, setValue] = useState(); @@ -142,6 +153,10 @@ export const WithSelect: Story = { }; export const Disabled: Story = { + parameters: { + controls: { disable: true }, + docs: { description: { story: '비활성화된 MonthPicker입니다. 클릭해도 월 선택 패널이 열리지 않습니다.' } }, + }, render: () => { const [value, setValue] = useState(new Date(2024, 5, 1)); @@ -161,6 +176,10 @@ export const Disabled: Story = { }; export const YearRange: Story = { + parameters: { + controls: { disable: true }, + docs: { description: { story: '선택 가능한 년도 범위를 제한한 예시입니다. 2020년부터 2030년까지만 선택할 수 있습니다.' } }, + }, render: () => { const [value, setValue] = useState(); @@ -186,6 +205,10 @@ export const YearRange: Story = { }; export const FormExample: Story = { + parameters: { + controls: { disable: true }, + docs: { description: { story: '폼 내에서 시작/종료 월을 선택하는 실용적인 예시입니다.' } }, + }, render: () => { const [formData, setFormData] = useState({ startMonth: undefined as Date | undefined, @@ -236,4 +259,4 @@ export const FormExample: Story = { ); }, -}; \ No newline at end of file +}; diff --git a/packages/react/src/one-time-password-field/OneTimePasswordField.stories.tsx b/packages/react/src/one-time-password-field/OneTimePasswordField.stories.tsx index 13e9c01b..e312ee6b 100644 --- a/packages/react/src/one-time-password-field/OneTimePasswordField.stories.tsx +++ b/packages/react/src/one-time-password-field/OneTimePasswordField.stories.tsx @@ -50,9 +50,30 @@ export default meta; type Story = StoryObj; -export const AllVariants: Story = { +const columnStyle = { display: 'flex', flexDirection: 'column', gap: 24, alignItems: 'center' } as const; + +export const Default: Story = { + parameters: { + docs: { description: { story: '가장 기본적인 OTP 입력 필드입니다. 기본 6자리를 지원합니다.' } }, + }, + render: () => { + const [value, setValue] = useState(''); + + return ( + + {Array.from({ length: 6 }, (_, index) => ( + // biome-ignore lint/suspicious/noArrayIndexKey: OTP inputs are statically positioned + + ))} + + ); + }, +}; + +export const Lengths: Story = { parameters: { controls: { disable: true }, + docs: { description: { story: '4자리, 6자리, 8자리 길이를 비교합니다.' } }, }, render: () => { const [value4, setValue4] = useState(''); @@ -60,9 +81,9 @@ export const AllVariants: Story = { const [value8, setValue8] = useState(''); return ( -
+
-

4 Digits

+
4자리
{Array.from({ length: 4 }, (_, index) => ( // biome-ignore lint/suspicious/noArrayIndexKey: OTP inputs are statically positioned @@ -72,7 +93,7 @@ export const AllVariants: Story = {
-

6 Digits

+
6자리
{Array.from({ length: 6 }, (_, index) => ( // biome-ignore lint/suspicious/noArrayIndexKey: OTP inputs are statically positioned @@ -82,7 +103,7 @@ export const AllVariants: Story = {
-

8 Digits

+
8자리
{Array.from({ length: 8 }, (_, index) => ( // biome-ignore lint/suspicious/noArrayIndexKey: OTP inputs are statically positioned @@ -95,6 +116,21 @@ export const AllVariants: Story = { }, }; +export const Disabled: Story = { + parameters: { + controls: { disable: true }, + docs: { description: { story: '비활성화 상태를 보여줍니다.' } }, + }, + render: () => ( + {}} disabled> + {Array.from({ length: 6 }, (_, index) => ( + // biome-ignore lint/suspicious/noArrayIndexKey: OTP inputs are statically positioned + + ))} + + ), +}; + export const Playground: Story = { render: args => { const [value, setValue] = useState(''); diff --git a/packages/react/src/pagination/Pagination.stories.tsx b/packages/react/src/pagination/Pagination.stories.tsx index c7aa682c..ce15b1d1 100644 --- a/packages/react/src/pagination/Pagination.stories.tsx +++ b/packages/react/src/pagination/Pagination.stories.tsx @@ -51,32 +51,24 @@ export default meta; type Story = StoryObj; -export const AllVariants: Story = { +export const Default: Story = { parameters: { - controls: { disable: true }, + docs: { description: { story: '가장 기본적인 Pagination 사용법입니다. 적은 수의 페이지를 표시합니다.' } }, }, - args: {}, render: () => { - const [page1, setPage1] = useState(1); - const [page2, setPage2] = useState(15); - - return ( -
-
-

- Few Pages (5 total) -

- -
+ const [page, setPage] = useState(1); + return ; + }, +}; -
-

- Many Pages (100 total) -

- -
-
- ); +export const ManyPages: Story = { + parameters: { + controls: { disable: true }, + docs: { description: { story: '많은 페이지가 있을 때 ellipsis(...)가 표시되는 모습을 보여줍니다.' } }, + }, + render: () => { + const [page, setPage] = useState(15); + return ; }, }; diff --git a/packages/react/src/popover/Popover.stories.tsx b/packages/react/src/popover/Popover.stories.tsx index 58ff2009..090fbdc5 100644 --- a/packages/react/src/popover/Popover.stories.tsx +++ b/packages/react/src/popover/Popover.stories.tsx @@ -29,50 +29,55 @@ export default meta; type Story = StoryObj; -export const AllVariants: Story = { +export const Default: Story = { parameters: { - controls: { disable: true }, + docs: { description: { story: '가장 기본적인 Popover 사용법입니다. 버튼을 클릭하면 팝오버가 열립니다.' } }, }, - args: {}, render: () => ( -
- - - - - - -
-

팝오버 제목

-

팝오버의 내용입니다.

-
-
-
-
+ + + + + + +
+

팝오버 제목

+

팝오버의 내용입니다.

+
+
+
+
+ ), +}; - - - - - - -
-

설정

- -
-
-
-
-
+export const WithForm: Story = { + parameters: { + controls: { disable: true }, + docs: { description: { story: '폼 요소가 포함된 팝오버입니다. 간단한 입력 필드를 팝오버 안에 배치할 수 있습니다.' } }, + }, + render: () => ( + + + + + + +
+

설정

+ +
+
+
+
), }; diff --git a/packages/react/src/radio-group/RadioGroup.module.css b/packages/react/src/radio-group/RadioGroup.module.css index 2772db9d..96673145 100644 --- a/packages/react/src/radio-group/RadioGroup.module.css +++ b/packages/react/src/radio-group/RadioGroup.module.css @@ -5,7 +5,7 @@ } .root[data-disabled] { - opacity: 0.5; + opacity: 0.4; pointer-events: none; } @@ -23,7 +23,7 @@ } .item[data-disabled] { - opacity: 0.5; + opacity: 0.4; pointer-events: none; } diff --git a/packages/react/src/radio-group/RadioGroup.stories.tsx b/packages/react/src/radio-group/RadioGroup.stories.tsx index a9172a3f..bed104f7 100644 --- a/packages/react/src/radio-group/RadioGroup.stories.tsx +++ b/packages/react/src/radio-group/RadioGroup.stories.tsx @@ -51,56 +51,66 @@ export default meta; type Story = StoryObj; -export const AllVariants: Story = { +const columnStyle = { display: 'flex', flexDirection: 'column', gap: 12 } as const; + +export const Default: Story = { parameters: { - controls: { disable: true }, + docs: { description: { story: '가장 기본적인 RadioGroup 사용법입니다.' } }, }, render: () => { - const [value1, setValue1] = useState('option1'); - const [value2, setValue2] = useState('option2'); + const [value, setValue] = useState('option1'); return ( -
-
-

Basic

- -
- - - - -
-
- - - - -
-
- - - - -
-
+ +
+ + + + +
+
+ + + +
+
+ + + + +
+
+ ); + }, +}; + +export const Disabled: Story = { + parameters: { + controls: { disable: true }, + docs: { description: { story: '전체 그룹 비활성화와 개별 항목 비활성화를 비교합니다.' } }, + }, + render: () => { + const [value, setValue] = useState('option2'); -
-

Disabled

- -
+ return ( +
+
+
전체 비활성화
+ +
-
+
-
+
@@ -109,22 +119,22 @@ export const AllVariants: Story = {
-
-

Disabled Item

+
+
개별 항목 비활성화
-
+
-
+
-
+
@@ -163,4 +173,3 @@ export const Playground: Story = { disabled: false, }, }; - diff --git a/packages/react/src/select/Select.stories.tsx b/packages/react/src/select/Select.stories.tsx index c39cbd2e..61c22986 100644 --- a/packages/react/src/select/Select.stories.tsx +++ b/packages/react/src/select/Select.stories.tsx @@ -17,7 +17,7 @@ const meta = { size: { description: '셀렉트 크기', control: 'select', - options: ['2xs', 'xs', 'sm', 'md', 'lg', 'xl'], + options: ['xs', 'sm', 'md', 'lg'], table: { defaultValue: { summary: 'md' }, type: { summary: 'SelectSize' }, @@ -38,19 +38,29 @@ export default meta; type Story = StoryObj; -export const AllVariants: Story = { +const columnStyle = { display: 'flex', flexDirection: 'column', gap: 12, alignItems: 'flex-start' } as const; + +export const Default: Story = { + parameters: { + docs: { description: { story: '가장 기본적인 Select 사용법입니다.' } }, + }, + render: () => ( + + ), +}; + +export const Sizes: Story = { parameters: { controls: { disable: true }, + docs: { description: { story: '사용 가능한 모든 사이즈를 비교합니다.' } }, }, - args: {}, render: () => ( -
- +
-
), }; +export const Disabled: Story = { + parameters: { + controls: { disable: true }, + docs: { description: { story: '비활성화 상태를 보여줍니다.' } }, + }, + render: () => ( + + ), +}; + export const Playground: Story = { args: { size: 'md', diff --git a/packages/react/src/select/Select.tsx b/packages/react/src/select/Select.tsx index 1cfb805a..39b9bbd7 100644 --- a/packages/react/src/select/Select.tsx +++ b/packages/react/src/select/Select.tsx @@ -5,7 +5,7 @@ import { match } from 'ts-pattern'; import { spacing } from '../token'; import styles from './Select.module.css'; -type SelectSize = 'xl' | 'lg' | 'md' | 'sm' | 'xs' | '2xs'; +export type SelectSize = 'lg' | 'md' | 'sm' | 'xs'; export interface SelectProps extends Omit, 'size'> { size?: SelectSize; @@ -54,7 +54,7 @@ export const Select = forwardRef( const getStyles = (size: SelectSize) => match(size) - .with('2xs', () => ({ + .with('xs', () => ({ '--cocso-select-min-width': spacing.s11, '--cocso-select-height': spacing.s11, '--cocso-select-padding-left': spacing.s5, @@ -62,23 +62,23 @@ const getStyles = (size: SelectSize) => '--cocso-select-font-size': '12px', '--cocso-select-border-radius': spacing.s3, })) - .with('xs', () => ({ + .with('sm', () => ({ '--cocso-select-min-width': spacing.s12, '--cocso-select-height': spacing.s12, '--cocso-select-padding-left': spacing.s6, '--cocso-select-padding-right': `calc(${spacing.s7} + 16px)`, '--cocso-select-font-size': '14px', - '--cocso-select-border-radius': spacing.s3, + '--cocso-select-border-radius': spacing.s4, })) - .with('sm', () => ({ + .with('md', () => ({ '--cocso-select-min-width': spacing.s14, '--cocso-select-height': spacing.s14, '--cocso-select-padding-left': spacing.s7, '--cocso-select-padding-right': `calc(${spacing.s7} + 16px)`, '--cocso-select-font-size': '14px', - '--cocso-select-border-radius': spacing.s3, + '--cocso-select-border-radius': spacing.s4, })) - .with('md', () => ({ + .with('lg', () => ({ '--cocso-select-min-width': spacing.s16, '--cocso-select-height': spacing.s16, '--cocso-select-padding-left': spacing.s8, @@ -86,20 +86,4 @@ const getStyles = (size: SelectSize) => '--cocso-select-font-size': '16px', '--cocso-select-border-radius': spacing.s4, })) - .with('lg', () => ({ - '--cocso-select-min-width': spacing.s17, - '--cocso-select-height': spacing.s17, - '--cocso-select-padding-left': spacing.s9, - '--cocso-select-padding-right': `calc(${spacing.s9} + 16px)`, - '--cocso-select-font-size': '18px', - '--cocso-select-border-radius': spacing.s4, - })) - .with('xl', () => ({ - '--cocso-select-min-width': spacing.s18, - '--cocso-select-height': spacing.s18, - '--cocso-select-padding-left': spacing.s10, - '--cocso-select-padding-right': `calc(${spacing.s10} + 16px)`, - '--cocso-select-font-size': '18px', - '--cocso-select-border-radius': spacing.s4, - })) .exhaustive(); diff --git a/packages/react/src/spinner/Spinner.stories.tsx b/packages/react/src/spinner/Spinner.stories.tsx index 7e7a9c20..4bf3dfdc 100644 --- a/packages/react/src/spinner/Spinner.stories.tsx +++ b/packages/react/src/spinner/Spinner.stories.tsx @@ -39,30 +39,41 @@ export default meta; type Story = StoryObj; -export const AllVariants: Story = { +const rowStyle = { display: 'flex', gap: 16, alignItems: 'center', flexWrap: 'wrap' } as const; + +export const Default: Story = { parameters: { - controls: { disable: true }, + docs: { description: { story: '가장 기본적인 Spinner 사용법입니다.' } }, }, args: {}, +}; + +export const Sizes: Story = { + parameters: { + controls: { disable: true }, + docs: { description: { story: '사용 가능한 모든 사이즈를 비교합니다.' } }, + }, render: () => ( -
-
-

Sizes:

- - - - -
+
+ + + + +
+ ), +}; -
-

- Colors: -

- - -
- -
+export const Colors: Story = { + parameters: { + controls: { disable: true }, + docs: { description: { story: '사용 가능한 모든 색상을 비교합니다. white는 어두운 배경에서 사용합니다.' } }, + }, + render: () => ( +
+ + +
+
), diff --git a/packages/react/src/spinner/Spinner.tsx b/packages/react/src/spinner/Spinner.tsx index 26aa411b..930f6c82 100644 --- a/packages/react/src/spinner/Spinner.tsx +++ b/packages/react/src/spinner/Spinner.tsx @@ -3,7 +3,7 @@ import { Slot } from '@radix-ui/react-slot'; import { clsx as cx } from 'clsx'; import { type ComponentPropsWithoutRef, forwardRef } from 'react'; import { match } from 'ts-pattern'; -import { colors } from '../token'; +import { colors, spacing } from '../token'; import styles from './Spinner.module.css'; export type SpinnerSize = 'xl' | 'lg' | 'md' | 'sm'; @@ -33,10 +33,10 @@ export const Spinner = forwardRef( const getSize = (size: SpinnerSize) => match(size) - .with('xl', () => '40px') - .with('lg', () => '32px') - .with('md', () => '24px') - .with('sm', () => '16px') + .with('xl', () => spacing.s14) + .with('lg', () => spacing.s12) + .with('md', () => spacing.s10) + .with('sm', () => spacing.s8) .exhaustive(); const getBorderWidth = (size: SpinnerSize) => diff --git a/packages/react/src/stock-quantity-status/StockQuantityStatus.stories.tsx b/packages/react/src/stock-quantity-status/StockQuantityStatus.stories.tsx index fe6c84b1..6c4db720 100644 --- a/packages/react/src/stock-quantity-status/StockQuantityStatus.stories.tsx +++ b/packages/react/src/stock-quantity-status/StockQuantityStatus.stories.tsx @@ -29,15 +29,24 @@ export default meta; type Story = StoryObj; -export const AllVariants: Story = { +const columnStyle = { display: 'flex', flexDirection: 'column', gap: 12, alignItems: 'flex-start' } as const; + +export const Default: Story = { + parameters: { + docs: { description: { story: '가장 기본적인 StockQuantityStatus 사용법입니다.' } }, + }, + args: { + quantity: '보통', + }, +}; + +export const AllStatuses: Story = { parameters: { controls: { disable: true }, + docs: { description: { story: '모든 재고 상태(여유, 보통, 부족)를 비교합니다.' } }, }, - args: {}, render: () => ( -
+
diff --git a/packages/react/src/stock-quantity-status/StockQuantityStatus.tsx b/packages/react/src/stock-quantity-status/StockQuantityStatus.tsx index 4b565fb0..277ae42b 100644 --- a/packages/react/src/stock-quantity-status/StockQuantityStatus.tsx +++ b/packages/react/src/stock-quantity-status/StockQuantityStatus.tsx @@ -110,7 +110,7 @@ export const StockQuantityStatus = forwardRef +const getColor = (quantity: QuantityStatus) => match(quantity) .with('여유', () => colors.primary500) .with('보통', () => colors.success400) diff --git a/packages/react/src/switch/Switch.stories.tsx b/packages/react/src/switch/Switch.stories.tsx index 8fd1d245..42d5c2c0 100644 --- a/packages/react/src/switch/Switch.stories.tsx +++ b/packages/react/src/switch/Switch.stories.tsx @@ -32,7 +32,7 @@ const meta = { size: { description: '스위치의 크기', control: 'select', - options: ['md', 'lg'], + options: ['sm', 'md'], table: { defaultValue: { summary: 'md' }, type: { summary: 'SwitchSize' }, @@ -69,62 +69,70 @@ export default meta; type Story = StoryObj; -export const AllVariants: Story = { +const columnStyle = { display: 'flex', flexDirection: 'column', gap: 8, alignItems: 'flex-start' } as const; + +export const Default: Story = { + parameters: { + docs: { description: { story: '가장 기본적인 Switch 사용법입니다.' } }, + }, + render: () => { + const [checked, setChecked] = useState(false); + return ; + }, +}; + +export const Sizes: Story = { parameters: { controls: { disable: true }, + docs: { description: { story: '사용 가능한 모든 사이즈를 비교합니다.' } }, }, render: () => { - const [state1, setState1] = useState(true); - const [state2, setState2] = useState(false); - const [state3, setState3] = useState(true); - const [state4, setState4] = useState(true); + const [sm, setSm] = useState(true); + const [md, setMd] = useState(true); return ( -
-
-

Sizes

- - -
+
+ + +
+ ); + }, +}; -
-

- Label Positions -

- - -
+export const LabelPositions: Story = { + parameters: { + controls: { disable: true }, + docs: { description: { story: '라벨 위치(left, right)를 비교합니다.' } }, + }, + render: () => { + const [right, setRight] = useState(true); + const [left, setLeft] = useState(true); -
-

Disabled

- {}} label="Disabled On" disabled /> - {}} label="Disabled Off" disabled /> -
+ return ( +
+ +
); }, }; +export const Disabled: Story = { + parameters: { + controls: { disable: true }, + docs: { description: { story: '비활성화 상태를 보여줍니다.' } }, + }, + render: () => ( +
+ {}} label="Disabled On" disabled /> + {}} label="Disabled Off" disabled /> +
+ ), +}; + export const Playground: Story = { render: args => { const [checked, setChecked] = useState(args.checked || false); - return ; }, args: { diff --git a/packages/react/src/switch/Switch.tsx b/packages/react/src/switch/Switch.tsx index 0fa759ae..0552dd0b 100644 --- a/packages/react/src/switch/Switch.tsx +++ b/packages/react/src/switch/Switch.tsx @@ -42,8 +42,8 @@ export const Switch = forwardRef, Swit const style = { ..._style, - '--cocso-switch-width': getSwitchWidth(size), - '--cocso-switch-height': getSwitchHeight(size), + '--cocso-switch-width': getWidth(size), + '--cocso-switch-height': getHeight(size), '--cocso-switch-thumb-width': getThumbSize(size), '--cocso-switch-thumb-height': getThumbSize(size), '--cocso-switch-bg-color': colors.neutral100, @@ -73,13 +73,13 @@ export const Switch = forwardRef, Swit }, ); -const getSwitchWidth = (size: SwitchSize) => +const getWidth = (size: SwitchSize) => match(size) .with('md', () => spacing.s14) .with('sm', () => spacing.s12) .exhaustive(); -const getSwitchHeight = (size: SwitchSize) => +const getHeight = (size: SwitchSize) => match(size) .with('md', () => spacing.s10) .with('sm', () => spacing.s9) diff --git a/packages/react/src/tab/Tab.stories.tsx b/packages/react/src/tab/Tab.stories.tsx index 17bb3092..60f9c4a4 100644 --- a/packages/react/src/tab/Tab.stories.tsx +++ b/packages/react/src/tab/Tab.stories.tsx @@ -42,11 +42,10 @@ export default meta; type Story = StoryObj; -export const AllVariants: Story = { +export const Default: Story = { parameters: { - controls: { disable: true }, + docs: { description: { story: '가장 기본적인 Tab 사용법입니다. 탭을 클릭하면 해당 콘텐츠가 표시됩니다.' } }, }, - args: {}, render: () => (
@@ -69,6 +68,42 @@ export const AllVariants: Story = { ), }; +export const WithContent: Story = { + parameters: { + controls: { disable: true }, + docs: { description: { story: '다양한 콘텐츠를 포함한 탭입니다. 각 패널에 서로 다른 정보를 표시합니다.' } }, + }, + render: () => ( +
+ + + 프로필 + 설정 + 알림 + + +
+

프로필 정보

+

사용자 프로필 정보를 확인하고 수정할 수 있습니다.

+
+
+ +
+

설정

+

애플리케이션 설정을 관리합니다.

+
+
+ +
+

알림 설정

+

알림 수신 설정을 관리합니다.

+
+
+
+
+ ), +}; + export const Playground: Story = { args: { defaultValue: 'tab1', diff --git a/packages/react/src/toast/Toast.stories.tsx b/packages/react/src/toast/Toast.stories.tsx index 4c78dcb1..65d94413 100644 --- a/packages/react/src/toast/Toast.stories.tsx +++ b/packages/react/src/toast/Toast.stories.tsx @@ -44,11 +44,23 @@ export default meta; type Story = StoryObj; -export const AllVariants: Story = { +export const Default: Story = { + parameters: { + docs: { description: { story: '가장 기본적인 Toast 사용법입니다. 버튼을 클릭하면 토스트가 표시됩니다.' } }, + }, + render: () => ( +
+ + +
+ ), +}; + +export const Types: Story = { parameters: { controls: { disable: true }, + docs: { description: { story: 'success, error, warning, info 네 가지 타입의 토스트를 비교합니다.' } }, }, - args: {}, render: () => (
diff --git a/packages/react/src/token/index.ts b/packages/react/src/token/index.ts index 015e5587..2010aa88 100644 --- a/packages/react/src/token/index.ts +++ b/packages/react/src/token/index.ts @@ -1,3 +1,4 @@ export * from './color'; +export * from './radius'; export * from './spacing'; export * from './typography'; diff --git a/packages/react/src/token/radius.ts b/packages/react/src/token/radius.ts new file mode 100644 index 00000000..5d497f9d --- /dev/null +++ b/packages/react/src/token/radius.ts @@ -0,0 +1,10 @@ +export const radius = { + none: 'var(--ds-radius-none)', + xs: 'var(--ds-radius-xs)', + sm: 'var(--ds-radius-sm)', + md: 'var(--ds-radius-md)', + lg: 'var(--ds-radius-lg)', + xl: 'var(--ds-radius-xl)', + full: 'var(--ds-radius-full)', + circle: 'var(--ds-radius-circle)', +} as const; diff --git a/packages/react/src/typography/Typography.stories.tsx b/packages/react/src/typography/Typography.stories.tsx index 93bf7e81..6b7b38a0 100644 --- a/packages/react/src/typography/Typography.stories.tsx +++ b/packages/react/src/typography/Typography.stories.tsx @@ -68,73 +68,127 @@ export default meta; type Story = StoryObj; -export const AllVariants: Story = { +const containerStyle = { display: 'flex', flexDirection: 'column', gap: '24px', maxWidth: '600px' } as const; +const sectionStyle = { marginBottom: 12, fontSize: 14, fontWeight: 600, color: '#666' } as const; + +export const Default: Story = { + parameters: { + docs: { description: { story: '가장 기본적인 Typography 사용법입니다.' } }, + }, + render: () => ( + + 기본 텍스트입니다. + + ), +}; + +export const Display: Story = { parameters: { controls: { disable: true }, + docs: { description: { story: 'Display 타입의 모든 사이즈를 비교합니다. 대형 제목이나 히어로 영역에 사용됩니다.' } }, }, - args: {}, render: () => ( -
-
-

Display

- - Display Large - - - Display Medium - - - Display Small - -
+
+

Display

+ + Display Large + + + Display Medium + + + Display Small + +
+ ), +}; -
-

Heading

- - Heading XL - - - Heading Large - - - Heading Medium - - - Heading Small - - - Heading XS - -
+export const Heading: Story = { + parameters: { + controls: { disable: true }, + docs: { description: { story: 'Heading 타입의 모든 사이즈를 비교합니다. 섹션 제목에 사용됩니다.' } }, + }, + render: () => ( +
+

Heading

+ + Heading XL + + + Heading Large + + + Heading Medium + + + Heading Small + + + Heading XS + +
+ ), +}; -
-

Body

- - Body Large - - - Body Medium - - - Body Small - - - Body XS - -
+export const Body: Story = { + parameters: { + controls: { disable: true }, + docs: { description: { story: 'Body 타입의 모든 사이즈를 비교합니다. 본문 텍스트에 사용됩니다.' } }, + }, + render: () => ( +
+

Body

+ + Body Large + + + Body Medium + + + Body Small + + + Body XS + +
+ ), +}; -
-

Custom

- - Custom 24px - - - Custom 18px - - - Custom 14px - -
+export const Weights: Story = { + parameters: { + controls: { disable: true }, + docs: { description: { story: '사용 가능한 모든 폰트 굵기를 비교합니다.' } }, + }, + render: () => ( +
+

Font Weights

+ + Thin (100) + + + Extra Light (200) + + + Light (300) + + + Normal (400) + + + Medium (500) + + + Semibold (600) + + + Bold (700) + + + Extra Bold (800) + + + Black (900) +
), }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c426dc86..40a922da 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -84,8 +84,8 @@ importers: version: 19.2.1(react@19.2.1) devDependencies: '@storybook/react-vite': - specifier: ^10.1.4 - version: 10.1.4(esbuild@0.27.1)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(rollup@4.53.3)(storybook@10.1.4(@testing-library/dom@10.4.1)(prettier@3.7.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(typescript@5.9.3)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2)) + specifier: ^10.2.8 + version: 10.2.8(esbuild@0.27.1)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(rollup@4.53.3)(storybook@10.2.8(@testing-library/dom@10.4.1)(prettier@3.7.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(typescript@5.9.3)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2)) '@types/react': specifier: catalog:types version: 19.2.7 @@ -99,8 +99,8 @@ importers: specifier: ^16.5.0 version: 16.5.0 storybook: - specifier: ^10.1.4 - version: 10.1.4(@testing-library/dom@10.4.1)(prettier@3.7.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + specifier: ^10.2.8 + version: 10.2.8(@testing-library/dom@10.4.1)(prettier@3.7.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) typescript: specifier: catalog:core version: 5.9.3 @@ -196,6 +196,9 @@ importers: typescript: specifier: catalog:core version: 5.9.3 + vitest: + specifier: ^4.0.18 + version: 4.0.18(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2) packages/baseframe: {} @@ -271,8 +274,8 @@ importers: specifier: catalog:build version: 16.0.3(rollup@4.53.3) '@storybook/react': - specifier: ^10.1.4 - version: 10.1.4(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(storybook@10.1.4(@testing-library/dom@10.4.1)(prettier@3.7.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(typescript@5.9.3) + specifier: ^10.2.8 + version: 10.2.8(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(storybook@10.2.8(@testing-library/dom@10.4.1)(prettier@3.7.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(typescript@5.9.3) '@types/react': specifier: catalog:types version: 19.2.7 @@ -547,24 +550,28 @@ packages: engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] + libc: [musl] '@biomejs/cli-linux-arm64@2.3.8': resolution: {integrity: sha512-Uo1OJnIkJgSgF+USx970fsM/drtPcQ39I+JO+Fjsaa9ZdCN1oysQmy6oAGbyESlouz+rzEckLTF6DS7cWse95g==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] + libc: [glibc] '@biomejs/cli-linux-x64-musl@2.3.8': resolution: {integrity: sha512-YGLkqU91r1276uwSjiUD/xaVikdxgV1QpsicT0bIA1TaieM6E5ibMZeSyjQ/izBn4tKQthUSsVZacmoJfa3pDA==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] + libc: [musl] '@biomejs/cli-linux-x64@2.3.8': resolution: {integrity: sha512-QDPMD5bQz6qOVb3kiBui0zKZXASLo0NIQ9JVJio5RveBEFgDgsvJFUvZIbMbUZT3T00M/1wdzwWXk4GIh0KaAw==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] + libc: [glibc] '@biomejs/cli-win32-arm64@2.3.8': resolution: {integrity: sha512-H4IoCHvL1fXKDrTALeTKMiE7GGWFAraDwBYFquE/L/5r1927Te0mYIGseXi4F+lrrwhSWbSGt5qPFswNoBaCxg==} @@ -1002,89 +1009,105 @@ packages: resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-arm@1.2.4': resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-ppc64@1.2.4': resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-riscv64@1.2.4': resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} cpu: [riscv64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-s390x@1.2.4': resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-x64@1.2.4': resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linuxmusl-arm64@1.2.4': resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-libvips-linuxmusl-x64@1.2.4': resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-linux-arm64@0.34.5': resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-linux-arm@0.34.5': resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-linux-ppc64@0.34.5': resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-linux-riscv64@0.34.5': resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [riscv64] os: [linux] + libc: [glibc] '@img/sharp-linux-s390x@0.34.5': resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-linux-x64@0.34.5': resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-linuxmusl-arm64@0.34.5': resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-linuxmusl-x64@0.34.5': resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-wasm32@0.34.5': resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} @@ -1118,12 +1141,16 @@ packages: '@types/node': optional: true - '@isaacs/cliui@8.0.2': - resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} - engines: {node: '>=12'} + '@isaacs/balanced-match@4.0.1': + resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} + engines: {node: 20 || >=22} + + '@isaacs/brace-expansion@5.0.1': + resolution: {integrity: sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==} + engines: {node: 20 || >=22} - '@joshwooding/vite-plugin-react-docgen-typescript@0.6.1': - resolution: {integrity: sha512-J4BaTocTOYFkMHIra1JDWrMWpNmBl4EkplIwHEsV8aeUOtdWjwSnln9U7twjMFTAEB7mptNtSKyVi1Y2W9sDJw==} + '@joshwooding/vite-plugin-react-docgen-typescript@0.6.4': + resolution: {integrity: sha512-6PyZBYKnnVNqOSB0YFly+62R7dmov8segT27A+RVTBVd4iAE6kbW9QBJGlyR2yG4D4ohzhZSTIu7BK1UTtmFFA==} peerDependencies: typescript: '>= 4.3.x' vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 @@ -1176,24 +1203,28 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@next/swc-linux-arm64-musl@16.0.7': resolution: {integrity: sha512-+ksWNrZrthisXuo9gd1XnjHRowCbMtl/YgMpbRvFeDEqEBd523YHPWpBuDjomod88U8Xliw5DHhekBC3EOOd9g==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@next/swc-linux-x64-gnu@16.0.7': resolution: {integrity: sha512-4WtJU5cRDxpEE44Ana2Xro1284hnyVpBb62lIpU5k85D8xXxatT+rXxBgPkc7C1XwkZMWpK5rXLXTh9PFipWsA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@next/swc-linux-x64-musl@16.0.7': resolution: {integrity: sha512-HYlhqIP6kBPXalW2dbMTSuB4+8fe+j9juyxwfMwCe9kQPPeiyFn7NMjNfoFOfJ2eXkeQsoUGXg+O2SE3m4Qg2w==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@next/swc-win32-arm64-msvc@16.0.7': resolution: {integrity: sha512-EviG+43iOoBRZg9deGauXExjRphhuYmIOJ12b9sAPy0eQ6iwcPxfED2asb/s2/yiLYOdm37kPaiZu8uXSYPs0Q==} @@ -1223,10 +1254,6 @@ packages: resolution: {integrity: sha512-scSmQBD8eANlMUOglxHrN1JdSW8tDghsPuS83otqealBiIeMukCQMOf/wc0JJjDXomqwNdEQFLXLGHrU6PGxuA==} engines: {node: '>= 20.0.0'} - '@pkgjs/parseargs@0.11.0': - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} - engines: {node: '>=14'} - '@posthog/core@1.7.1': resolution: {integrity: sha512-kjK0eFMIpKo9GXIbts8VtAknsoZ18oZorANdtuTj1CbgS28t4ZVq//HAWhnxEuXRTrtkd+SUJ6Ux3j2Af8NCuA==} @@ -1772,56 +1799,67 @@ packages: resolution: {integrity: sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.53.3': resolution: {integrity: sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.53.3': resolution: {integrity: sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.53.3': resolution: {integrity: sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.53.3': resolution: {integrity: sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-gnu@4.53.3': resolution: {integrity: sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.53.3': resolution: {integrity: sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.53.3': resolution: {integrity: sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.53.3': resolution: {integrity: sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.53.3': resolution: {integrity: sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.53.3': resolution: {integrity: sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-openharmony-arm64@4.53.3': resolution: {integrity: sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==} @@ -1878,18 +1916,18 @@ packages: '@standard-schema/spec@1.0.0': resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} - '@storybook/builder-vite@10.1.4': - resolution: {integrity: sha512-3mUQoCzMuhqAIjj8fdbGlwh+GgHaFpCvU+sxL8kIxnZqflW09SuwM5kS47Y5QDzYbHAPYCPqcBFyJ4EfRuf0rw==} + '@storybook/builder-vite@10.2.8': + resolution: {integrity: sha512-+6/Lwi7W0YIbzHDh798GPp0IHUYDwp0yv0Y1eVNK/StZD0tnv4/1C28NKyP+O7JOsFsuWI1qHiDhw8kNURugZw==} peerDependencies: - storybook: ^10.1.4 + storybook: ^10.2.8 vite: ^5.0.0 || ^6.0.0 || ^7.0.0 - '@storybook/csf-plugin@10.1.4': - resolution: {integrity: sha512-nudIBYx8fBz+1j2Xn1pdfGcgMJ78N/1NFB4MYAxI3YEzxGnQwUjihOO1x3siAXPbjFGmnVHoBx7+6IpO3F70GA==} + '@storybook/csf-plugin@10.2.8': + resolution: {integrity: sha512-kKkLYhRXb33YtIPdavD2DU25sb14sqPYdcQFpyqu4TaD9truPPqW8P5PLTUgERydt/eRvRlnhauPHavU1kjsnA==} peerDependencies: esbuild: '*' rollup: '*' - storybook: ^10.1.4 + storybook: ^10.2.8 vite: '*' webpack: '*' peerDependenciesMeta: @@ -1911,27 +1949,27 @@ packages: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - '@storybook/react-dom-shim@10.1.4': - resolution: {integrity: sha512-PARu2HA5nYU1AkioNJNc430pz0oyaHFSSAdN3NEaWwkoGrCOo9ZpAXP9V7wlJANCi1pndbC84gSuHVnBXJBG6g==} + '@storybook/react-dom-shim@10.2.8': + resolution: {integrity: sha512-Xde9X3VszFV1pTXfc2ZFM89XOCGRxJD8MUIzDwkcT9xaki5a+8srs/fsXj75fMY6gMYfcL5lNRZvCqg37HOmcQ==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - storybook: ^10.1.4 + storybook: ^10.2.8 - '@storybook/react-vite@10.1.4': - resolution: {integrity: sha512-PneYbxBGArczDtDAvQu6Ug5oeDYM5SQiEDSF0i+TNN0ZKO2ROsmbGSI9/7YTFontXR2CqweIO8GyOGQOcz5K9A==} + '@storybook/react-vite@10.2.8': + resolution: {integrity: sha512-x5kmw+TPhxkQV84n4e9X0q6/rA5T8V2QQFolMuN+U93q1HX1r+GZ6g/nXaaq9ox168PhHUJZQnn+LzSQKGCMBA==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - storybook: ^10.1.4 + storybook: ^10.2.8 vite: ^5.0.0 || ^6.0.0 || ^7.0.0 - '@storybook/react@10.1.4': - resolution: {integrity: sha512-ZBMPdQ99QBv/UtlIZBerDGNsQB30ffxk6twe45FIPutSlKXD6W9r0z7rGa5UWnqmmxa9HjARRhclOFsNGkhs9g==} + '@storybook/react@10.2.8': + resolution: {integrity: sha512-nMFqQFUXq6Zg2O5SeuomyWnrIx61QfpNQMrfor8eCEzHrWNnXrrvVsz2RnHIgXN8RVyaWGDPh1srAECu/kDHXw==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - storybook: ^10.1.4 + storybook: ^10.2.8 typescript: '>= 4.9.x' peerDependenciesMeta: typescript: @@ -1983,24 +2021,28 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.1.17': resolution: {integrity: sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.1.17': resolution: {integrity: sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.1.17': resolution: {integrity: sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.1.17': resolution: {integrity: sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg==} @@ -2146,11 +2188,14 @@ packages: '@vitest/expect@3.2.4': resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} - '@vitest/mocker@3.2.4': - resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} + '@vitest/expect@4.0.18': + resolution: {integrity: sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==} + + '@vitest/mocker@4.0.18': + resolution: {integrity: sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==} peerDependencies: msw: ^2.4.9 - vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 + vite: ^6.0.0 || ^7.0.0-0 peerDependenciesMeta: msw: optional: true @@ -2160,12 +2205,27 @@ packages: '@vitest/pretty-format@3.2.4': resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} + '@vitest/pretty-format@4.0.18': + resolution: {integrity: sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==} + + '@vitest/runner@4.0.18': + resolution: {integrity: sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==} + + '@vitest/snapshot@4.0.18': + resolution: {integrity: sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==} + '@vitest/spy@3.2.4': resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} + '@vitest/spy@4.0.18': + resolution: {integrity: sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==} + '@vitest/utils@3.2.4': resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} + '@vitest/utils@4.0.18': + resolution: {integrity: sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==} + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -2236,9 +2296,6 @@ packages: bail@2.0.2: resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - baseline-browser-mapping@2.9.5: resolution: {integrity: sha512-D5vIoztZOq1XM54LUdttJVc96ggEsIfju2JBvht06pSzpckp3C7HReun67Bghzrtdsq9XdMGbSSB3v3GhMNmAA==} hasBin: true @@ -2250,9 +2307,6 @@ packages: boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} - brace-expansion@2.0.2: - resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} - braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} @@ -2262,6 +2316,10 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + bundle-name@4.1.0: + resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} + engines: {node: '>=18'} + caniuse-api@3.0.0: resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} @@ -2275,6 +2333,10 @@ packages: resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} engines: {node: '>=18'} + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -2432,6 +2494,18 @@ packages: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} + default-browser-id@5.0.1: + resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==} + engines: {node: '>=18'} + + default-browser@5.5.0: + resolution: {integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==} + engines: {node: '>=18'} + + define-lazy-prop@3.0.0: + resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} + engines: {node: '>=12'} + dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -2477,21 +2551,12 @@ packages: domutils@2.8.0: resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} - eastasianwidth@0.2.0: - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - electron-to-chromium@1.5.266: resolution: {integrity: sha512-kgWEglXvkEfMH7rxP5OSZZwnaDWT7J9EoZCujhnpLbfi0bbNtRkgdX2E3gt0Uer11c61qCYktB3hwkAS325sJg==} emoji-regex@10.6.0: resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} - emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - - emoji-regex@9.2.2: - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - empathic@2.0.0: resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==} engines: {node: '>=14'} @@ -2507,6 +2572,9 @@ packages: entities@2.2.0: resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + esast-util-from-estree@2.0.0: resolution: {integrity: sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==} @@ -2573,6 +2641,10 @@ packages: eventemitter3@4.0.7: resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} @@ -2606,10 +2678,6 @@ packages: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} - foreground-child@3.3.1: - resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} - engines: {node: '>=14'} - fs-extra@11.3.2: resolution: {integrity: sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==} engines: {node: '>=14.14'} @@ -2732,9 +2800,9 @@ packages: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} - glob@10.5.0: - resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} - hasBin: true + glob@13.0.2: + resolution: {integrity: sha512-035InabNu/c1lW0tzPhAgapKctblppqsKKG9ZaNzbr+gXwWMjXoiyGSyB9sArzrjG7jY+zntRq5ZSUYemrnWVQ==} + engines: {node: 20 || >=22} globals@16.5.0: resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} @@ -2827,14 +2895,15 @@ packages: is-decimal@2.0.1: resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + is-docker@3.0.0: + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} - is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} - is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} @@ -2842,6 +2911,11 @@ packages: is-hexadecimal@2.0.1: resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + is-inside-container@1.0.0: + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} + hasBin: true + is-module@1.0.0: resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} @@ -2864,12 +2938,13 @@ packages: resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} engines: {node: '>=0.10.0'} + is-wsl@3.1.0: + resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==} + engines: {node: '>=16'} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - jackspeak@3.4.3: - resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - jiti@2.6.1: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true @@ -2936,24 +3011,28 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.30.2: resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.30.2: resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.30.2: resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.30.2: resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} @@ -3004,9 +3083,6 @@ packages: loupe@3.2.1: resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} - lru-cache@10.4.3: - resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - lru-cache@11.2.4: resolution: {integrity: sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==} engines: {node: 20 || >=22} @@ -3196,9 +3272,9 @@ packages: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} - minimatch@9.0.5: - resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} - engines: {node: '>=16 || 14 >=14.17'} + minimatch@10.1.2: + resolution: {integrity: sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw==} + engines: {node: 20 || >=22} minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} @@ -3264,12 +3340,19 @@ packages: nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + obug@2.1.1: + resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + oniguruma-parser@0.12.1: resolution: {integrity: sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==} oniguruma-to-es@4.3.4: resolution: {integrity: sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA==} + open@10.2.0: + resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==} + engines: {node: '>=18'} + outdent@0.5.0: resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} @@ -3305,9 +3388,6 @@ packages: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} - package-json-from-dist@1.0.1: - resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - package-manager-detector@0.2.11: resolution: {integrity: sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==} @@ -3325,9 +3405,9 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - path-scurry@1.11.1: - resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} - engines: {node: '>=16 || 14 >=14.18'} + path-scurry@2.0.1: + resolution: {integrity: sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==} + engines: {node: 20 || >=22} path-to-regexp@8.3.0: resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} @@ -3336,6 +3416,9 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + pathval@2.0.1: resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} engines: {node: '>= 14.16'} @@ -3779,6 +3862,10 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + run-applescript@7.1.0: + resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} + engines: {node: '>=18'} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -3818,6 +3905,9 @@ packages: shiki@3.19.0: resolution: {integrity: sha512-77VJr3OR/VUZzPiStyRhADmO2jApMM0V2b1qf0RpfWya8Zr1PeZev5AEpPGAAKWdiYUtcZGBE4F5QvJml1PvWA==} + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} @@ -3857,8 +3947,14 @@ packages: resolution: {integrity: sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==} deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility' - storybook@10.1.4: - resolution: {integrity: sha512-FrBjm8I8O+pYEOPHcdW9xWwgXSZxte7lza9q2lN3jFN4vuW79m5j0OnTQeR8z9MmIbBTvkIpp3yMBebl53Yt5Q==} + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + + storybook@10.2.8: + resolution: {integrity: sha512-885uSIn8NQw2ZG7vy84K45lHCOSyz1DVsDV8pHiHQj3J0riCuWLNeO50lK9z98zE8kjhgTtxAAkMTy5nkmNRKQ==} hasBin: true peerDependencies: prettier: ^2 || ^3 @@ -3869,14 +3965,6 @@ packages: string-hash@1.1.3: resolution: {integrity: sha512-kJUvRUFK49aub+a7T1nNE66EJbZBMnBgoC1UbCZ5n6bsZKBRga4KgBRTMn/pFkeCZSYtNeSyMxPDM0AXWELk2A==} - string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} - - string-width@5.1.2: - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} - engines: {node: '>=12'} - string-width@7.2.0: resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} engines: {node: '>=18'} @@ -3965,6 +4053,9 @@ packages: tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + tinyexec@1.0.2: resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} engines: {node: '>=18'} @@ -3977,6 +4068,10 @@ packages: resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} engines: {node: '>=14.0.0'} + tinyrainbow@3.0.3: + resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} + engines: {node: '>=14.0.0'} + tinyspy@4.0.4: resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} engines: {node: '>=14.0.0'} @@ -4163,6 +4258,40 @@ packages: yaml: optional: true + vitest@4.0.18: + resolution: {integrity: sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.0.18 + '@vitest/browser-preview': 4.0.18 + '@vitest/browser-webdriverio': 4.0.18 + '@vitest/ui': 4.0.18 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + web-vitals@4.2.4: resolution: {integrity: sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==} @@ -4174,13 +4303,10 @@ packages: engines: {node: '>= 8'} hasBin: true - wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} - - wrap-ansi@8.1.0: - resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} - engines: {node: '>=12'} + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true wrap-ansi@9.0.2: resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} @@ -4198,6 +4324,10 @@ packages: utf-8-validate: optional: true + wsl-utils@0.1.0: + resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==} + engines: {node: '>=18'} + y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} @@ -4946,19 +5076,15 @@ snapshots: optionalDependencies: '@types/node': 24.10.1 - '@isaacs/cliui@8.0.2': + '@isaacs/balanced-match@4.0.1': {} + + '@isaacs/brace-expansion@5.0.1': dependencies: - string-width: 5.1.2 - string-width-cjs: string-width@4.2.3 - strip-ansi: 7.1.2 - strip-ansi-cjs: strip-ansi@6.0.1 - wrap-ansi: 8.1.0 - wrap-ansi-cjs: wrap-ansi@7.0.0 + '@isaacs/balanced-match': 4.0.1 - '@joshwooding/vite-plugin-react-docgen-typescript@0.6.1(typescript@5.9.3)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2))': + '@joshwooding/vite-plugin-react-docgen-typescript@0.6.4(typescript@5.9.3)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2))': dependencies: - glob: 10.5.0 - magic-string: 0.30.21 + glob: 13.0.2 react-docgen-typescript: 2.4.0(typescript@5.9.3) vite: 7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2) optionalDependencies: @@ -5069,9 +5195,6 @@ snapshots: '@orama/orama@3.1.16': {} - '@pkgjs/parseargs@0.11.0': - optional: true - '@posthog/core@1.7.1': dependencies: cross-spawn: 7.0.6 @@ -5716,22 +5839,20 @@ snapshots: '@standard-schema/spec@1.0.0': {} - '@storybook/builder-vite@10.1.4(esbuild@0.27.1)(rollup@4.53.3)(storybook@10.1.4(@testing-library/dom@10.4.1)(prettier@3.7.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2))': + '@storybook/builder-vite@10.2.8(esbuild@0.27.1)(rollup@4.53.3)(storybook@10.2.8(@testing-library/dom@10.4.1)(prettier@3.7.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2))': dependencies: - '@storybook/csf-plugin': 10.1.4(esbuild@0.27.1)(rollup@4.53.3)(storybook@10.1.4(@testing-library/dom@10.4.1)(prettier@3.7.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2)) - '@vitest/mocker': 3.2.4(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2)) - storybook: 10.1.4(@testing-library/dom@10.4.1)(prettier@3.7.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@storybook/csf-plugin': 10.2.8(esbuild@0.27.1)(rollup@4.53.3)(storybook@10.2.8(@testing-library/dom@10.4.1)(prettier@3.7.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2)) + storybook: 10.2.8(@testing-library/dom@10.4.1)(prettier@3.7.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) ts-dedent: 2.2.0 vite: 7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2) transitivePeerDependencies: - esbuild - - msw - rollup - webpack - '@storybook/csf-plugin@10.1.4(esbuild@0.27.1)(rollup@4.53.3)(storybook@10.1.4(@testing-library/dom@10.4.1)(prettier@3.7.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2))': + '@storybook/csf-plugin@10.2.8(esbuild@0.27.1)(rollup@4.53.3)(storybook@10.2.8(@testing-library/dom@10.4.1)(prettier@3.7.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2))': dependencies: - storybook: 10.1.4(@testing-library/dom@10.4.1)(prettier@3.7.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + storybook: 10.2.8(@testing-library/dom@10.4.1)(prettier@3.7.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) unplugin: 2.3.11 optionalDependencies: esbuild: 0.27.1 @@ -5745,43 +5866,42 @@ snapshots: react: 19.2.1 react-dom: 19.2.1(react@19.2.1) - '@storybook/react-dom-shim@10.1.4(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(storybook@10.1.4(@testing-library/dom@10.4.1)(prettier@3.7.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))': + '@storybook/react-dom-shim@10.2.8(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(storybook@10.2.8(@testing-library/dom@10.4.1)(prettier@3.7.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))': dependencies: react: 19.2.1 react-dom: 19.2.1(react@19.2.1) - storybook: 10.1.4(@testing-library/dom@10.4.1)(prettier@3.7.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + storybook: 10.2.8(@testing-library/dom@10.4.1)(prettier@3.7.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@storybook/react-vite@10.1.4(esbuild@0.27.1)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(rollup@4.53.3)(storybook@10.1.4(@testing-library/dom@10.4.1)(prettier@3.7.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(typescript@5.9.3)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2))': + '@storybook/react-vite@10.2.8(esbuild@0.27.1)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(rollup@4.53.3)(storybook@10.2.8(@testing-library/dom@10.4.1)(prettier@3.7.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(typescript@5.9.3)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2))': dependencies: - '@joshwooding/vite-plugin-react-docgen-typescript': 0.6.1(typescript@5.9.3)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2)) + '@joshwooding/vite-plugin-react-docgen-typescript': 0.6.4(typescript@5.9.3)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2)) '@rollup/pluginutils': 5.3.0(rollup@4.53.3) - '@storybook/builder-vite': 10.1.4(esbuild@0.27.1)(rollup@4.53.3)(storybook@10.1.4(@testing-library/dom@10.4.1)(prettier@3.7.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2)) - '@storybook/react': 10.1.4(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(storybook@10.1.4(@testing-library/dom@10.4.1)(prettier@3.7.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(typescript@5.9.3) + '@storybook/builder-vite': 10.2.8(esbuild@0.27.1)(rollup@4.53.3)(storybook@10.2.8(@testing-library/dom@10.4.1)(prettier@3.7.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2)) + '@storybook/react': 10.2.8(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(storybook@10.2.8(@testing-library/dom@10.4.1)(prettier@3.7.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(typescript@5.9.3) empathic: 2.0.0 magic-string: 0.30.21 react: 19.2.1 react-docgen: 8.0.2 react-dom: 19.2.1(react@19.2.1) resolve: 1.22.11 - storybook: 10.1.4(@testing-library/dom@10.4.1)(prettier@3.7.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + storybook: 10.2.8(@testing-library/dom@10.4.1)(prettier@3.7.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) tsconfig-paths: 4.2.0 vite: 7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2) transitivePeerDependencies: - esbuild - - msw - rollup - supports-color - typescript - webpack - '@storybook/react@10.1.4(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(storybook@10.1.4(@testing-library/dom@10.4.1)(prettier@3.7.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(typescript@5.9.3)': + '@storybook/react@10.2.8(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(storybook@10.2.8(@testing-library/dom@10.4.1)(prettier@3.7.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(typescript@5.9.3)': dependencies: '@storybook/global': 5.0.0 - '@storybook/react-dom-shim': 10.1.4(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(storybook@10.1.4(@testing-library/dom@10.4.1)(prettier@3.7.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)) + '@storybook/react-dom-shim': 10.2.8(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(storybook@10.2.8(@testing-library/dom@10.4.1)(prettier@3.7.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)) react: 19.2.1 react-docgen: 8.0.2 react-dom: 19.2.1(react@19.2.1) - storybook: 10.1.4(@testing-library/dom@10.4.1)(prettier@3.7.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + storybook: 10.2.8(@testing-library/dom@10.4.1)(prettier@3.7.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: @@ -6003,9 +6123,18 @@ snapshots: chai: 5.3.3 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.4(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2))': + '@vitest/expect@4.0.18': dependencies: - '@vitest/spy': 3.2.4 + '@standard-schema/spec': 1.0.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.0.18 + '@vitest/utils': 4.0.18 + chai: 6.2.2 + tinyrainbow: 3.0.3 + + '@vitest/mocker@4.0.18(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2))': + dependencies: + '@vitest/spy': 4.0.18 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: @@ -6015,16 +6144,38 @@ snapshots: dependencies: tinyrainbow: 2.0.0 + '@vitest/pretty-format@4.0.18': + dependencies: + tinyrainbow: 3.0.3 + + '@vitest/runner@4.0.18': + dependencies: + '@vitest/utils': 4.0.18 + pathe: 2.0.3 + + '@vitest/snapshot@4.0.18': + dependencies: + '@vitest/pretty-format': 4.0.18 + magic-string: 0.30.21 + pathe: 2.0.3 + '@vitest/spy@3.2.4': dependencies: tinyspy: 4.0.4 + '@vitest/spy@4.0.18': {} + '@vitest/utils@3.2.4': dependencies: '@vitest/pretty-format': 3.2.4 loupe: 3.2.1 tinyrainbow: 2.0.0 + '@vitest/utils@4.0.18': + dependencies: + '@vitest/pretty-format': 4.0.18 + tinyrainbow: 3.0.3 + acorn-jsx@5.3.2(acorn@8.15.0): dependencies: acorn: 8.15.0 @@ -6073,8 +6224,6 @@ snapshots: bail@2.0.2: {} - balanced-match@1.0.2: {} - baseline-browser-mapping@2.9.5: {} better-path-resolve@1.0.0: @@ -6083,10 +6232,6 @@ snapshots: boolbase@1.0.0: {} - brace-expansion@2.0.2: - dependencies: - balanced-match: 1.0.2 - braces@3.0.3: dependencies: fill-range: 7.1.1 @@ -6099,6 +6244,10 @@ snapshots: node-releases: 2.0.27 update-browserslist-db: 1.2.2(browserslist@4.28.1) + bundle-name@4.1.0: + dependencies: + run-applescript: 7.1.0 + caniuse-api@3.0.0: dependencies: browserslist: 4.28.1 @@ -6118,6 +6267,8 @@ snapshots: loupe: 3.2.1 pathval: 2.0.1 + chai@6.2.2: {} + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -6274,6 +6425,15 @@ snapshots: deepmerge@4.3.1: {} + default-browser-id@5.0.1: {} + + default-browser@5.5.0: + dependencies: + bundle-name: 4.1.0 + default-browser-id: 5.0.1 + + define-lazy-prop@3.0.0: {} + dequal@2.0.3: {} detect-indent@6.1.0: {} @@ -6316,16 +6476,10 @@ snapshots: domelementtype: 2.3.0 domhandler: 4.3.1 - eastasianwidth@0.2.0: {} - electron-to-chromium@1.5.266: {} emoji-regex@10.6.0: {} - emoji-regex@8.0.0: {} - - emoji-regex@9.2.2: {} - empathic@2.0.0: {} enhanced-resolve@5.18.3: @@ -6340,6 +6494,8 @@ snapshots: entities@2.2.0: {} + es-module-lexer@1.7.0: {} + esast-util-from-estree@2.0.0: dependencies: '@types/estree-jsx': 1.0.5 @@ -6411,6 +6567,7 @@ snapshots: '@esbuild/win32-arm64': 0.27.1 '@esbuild/win32-ia32': 0.27.1 '@esbuild/win32-x64': 0.27.1 + optional: true escalade@3.2.0: {} @@ -6463,6 +6620,8 @@ snapshots: eventemitter3@4.0.7: {} + expect-type@1.3.0: {} + extend@3.0.2: {} extendable-error@0.1.7: {} @@ -6494,11 +6653,6 @@ snapshots: locate-path: 5.0.0 path-exists: 4.0.0 - foreground-child@3.3.1: - dependencies: - cross-spawn: 7.0.6 - signal-exit: 4.1.0 - fs-extra@11.3.2: dependencies: graceful-fs: 4.2.11 @@ -6634,14 +6788,11 @@ snapshots: dependencies: is-glob: 4.0.3 - glob@10.5.0: + glob@13.0.2: dependencies: - foreground-child: 3.3.1 - jackspeak: 3.4.3 - minimatch: 9.0.5 + minimatch: 10.1.2 minipass: 7.1.2 - package-json-from-dist: 1.0.1 - path-scurry: 1.11.1 + path-scurry: 2.0.1 globals@16.5.0: {} @@ -6768,9 +6919,9 @@ snapshots: is-decimal@2.0.1: {} - is-extglob@2.1.1: {} + is-docker@3.0.0: {} - is-fullwidth-code-point@3.0.0: {} + is-extglob@2.1.1: {} is-glob@4.0.3: dependencies: @@ -6778,6 +6929,10 @@ snapshots: is-hexadecimal@2.0.1: {} + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + is-module@1.0.0: {} is-number@7.0.0: {} @@ -6794,13 +6949,11 @@ snapshots: is-windows@1.0.2: {} - isexe@2.0.0: {} - - jackspeak@3.4.3: + is-wsl@3.1.0: dependencies: - '@isaacs/cliui': 8.0.2 - optionalDependencies: - '@pkgjs/parseargs': 0.11.0 + is-inside-container: 1.0.0 + + isexe@2.0.0: {} jiti@2.6.1: {} @@ -6900,8 +7053,6 @@ snapshots: loupe@3.2.1: {} - lru-cache@10.4.3: {} - lru-cache@11.2.4: {} lru-cache@5.1.1: @@ -7356,9 +7507,9 @@ snapshots: min-indent@1.0.1: {} - minimatch@9.0.5: + minimatch@10.1.2: dependencies: - brace-expansion: 2.0.2 + '@isaacs/brace-expansion': 5.0.1 minimist@1.2.8: {} @@ -7410,6 +7561,8 @@ snapshots: dependencies: boolbase: 1.0.0 + obug@2.1.1: {} + oniguruma-parser@0.12.1: {} oniguruma-to-es@4.3.4: @@ -7418,6 +7571,13 @@ snapshots: regex: 6.0.1 regex-recursion: 6.0.2 + open@10.2.0: + dependencies: + default-browser: 5.5.0 + define-lazy-prop: 3.0.0 + is-inside-container: 1.0.0 + wsl-utils: 0.1.0 + outdent@0.5.0: {} p-filter@2.1.0: @@ -7447,8 +7607,6 @@ snapshots: p-try@2.2.0: {} - package-json-from-dist@1.0.1: {} - package-manager-detector@0.2.11: dependencies: quansync: 0.2.11 @@ -7469,15 +7627,17 @@ snapshots: path-parse@1.0.7: {} - path-scurry@1.11.1: + path-scurry@2.0.1: dependencies: - lru-cache: 10.4.3 + lru-cache: 11.2.4 minipass: 7.1.2 path-to-regexp@8.3.0: {} path-type@4.0.0: {} + pathe@2.0.3: {} + pathval@2.0.1: {} picocolors@1.1.1: {} @@ -7995,6 +8155,8 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.53.3 fsevents: 2.3.3 + run-applescript@7.1.0: {} + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -8062,6 +8224,8 @@ snapshots: '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 + siginfo@2.0.0: {} + signal-exit@4.1.0: {} slash@3.0.0: {} @@ -8088,7 +8252,11 @@ snapshots: stable@0.1.8: {} - storybook@10.1.4(@testing-library/dom@10.4.1)(prettier@3.7.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1): + stackback@0.0.2: {} + + std-env@3.10.0: {} + + storybook@10.2.8(@testing-library/dom@10.4.1)(prettier@3.7.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1): dependencies: '@storybook/global': 5.0.0 '@storybook/icons': 2.0.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1) @@ -8096,7 +8264,8 @@ snapshots: '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1) '@vitest/expect': 3.2.4 '@vitest/spy': 3.2.4 - esbuild: 0.27.1 + esbuild: 0.25.12 + open: 10.2.0 recast: 0.23.11 semver: 7.7.3 use-sync-external-store: 1.6.0(react@19.2.1) @@ -8112,18 +8281,6 @@ snapshots: string-hash@1.1.3: {} - string-width@4.2.3: - dependencies: - emoji-regex: 8.0.0 - is-fullwidth-code-point: 3.0.0 - strip-ansi: 6.0.1 - - string-width@5.1.2: - dependencies: - eastasianwidth: 0.2.0 - emoji-regex: 9.2.2 - strip-ansi: 7.1.2 - string-width@7.2.0: dependencies: emoji-regex: 10.6.0 @@ -8200,6 +8357,8 @@ snapshots: tiny-invariant@1.3.3: {} + tinybench@2.9.0: {} + tinyexec@1.0.2: {} tinyglobby@0.2.15: @@ -8209,6 +8368,8 @@ snapshots: tinyrainbow@2.0.0: {} + tinyrainbow@3.0.3: {} + tinyspy@4.0.4: {} to-regex-range@5.0.1: @@ -8367,6 +8528,43 @@ snapshots: lightningcss: 1.30.2 yaml: 2.8.2 + vitest@4.0.18(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2): + dependencies: + '@vitest/expect': 4.0.18 + '@vitest/mocker': 4.0.18(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2)) + '@vitest/pretty-format': 4.0.18 + '@vitest/runner': 4.0.18 + '@vitest/snapshot': 4.0.18 + '@vitest/spy': 4.0.18 + '@vitest/utils': 4.0.18 + es-module-lexer: 1.7.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 1.0.2 + tinyglobby: 0.2.15 + tinyrainbow: 3.0.3 + vite: 7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 24.10.1 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - yaml + web-vitals@4.2.4: {} webpack-virtual-modules@0.6.2: {} @@ -8375,17 +8573,10 @@ snapshots: dependencies: isexe: 2.0.0 - wrap-ansi@7.0.0: + why-is-node-running@2.3.0: dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - - wrap-ansi@8.1.0: - dependencies: - ansi-styles: 6.2.3 - string-width: 5.1.2 - strip-ansi: 7.1.2 + siginfo: 2.0.0 + stackback: 0.0.2 wrap-ansi@9.0.2: dependencies: @@ -8395,6 +8586,10 @@ snapshots: ws@8.18.3: {} + wsl-utils@0.1.0: + dependencies: + is-wsl: 3.1.0 + y18n@5.0.8: {} yallist@3.1.1: {}