+16
-29
@@ -7,25 +7,17 @@ //#region src/types.d.ts | ||
| type StyleInput = string | StyleObject | CSSStyleDeclaration | Iterable<[string, StylePrimitive]>; | ||
| type ConversionMode = 'pretty' | 'lossless' | 'strict'; | ||
| type SortMode = 'input' | 'tailwind' | 'grouped'; | ||
| type CompressionMode = 'none' | 'safe' | 'aggressive'; | ||
| type ColorMatchMode = 'exact' | 'nearest' | 'none'; | ||
| type NumericMultiplierMode = 'all' | 'integer' | 'never'; | ||
| type TwirlwindTheme = { | ||
| type Theme = { | ||
| spacing?: Record<string, string>; | ||
| colors?: Record<string, string>; | ||
| }; | ||
| type StyleToTailwindOptions = { | ||
| mode?: ConversionMode; | ||
| tailwindVersion?: '4'; | ||
| theme?: TwirlwindTheme; | ||
| type Options = { | ||
| theme?: Theme; | ||
| allowArbitraryValues?: boolean; | ||
| allowArbitraryProperties?: boolean; | ||
| preferThemeTokens?: boolean; | ||
| compression?: CompressionMode; | ||
| sort?: SortMode; | ||
| compression?: 'none' | 'safe' | 'aggressive'; | ||
| sort?: 'input' | 'tailwind' | 'grouped'; | ||
| important?: boolean; | ||
| colorMatch?: ColorMatchMode; | ||
| numericMultipliers?: NumericMultiplierMode; | ||
| colorMatch?: 'exact' | 'nearest' | 'none'; | ||
| numericMultipliers?: 'all' | 'integer' | 'never'; | ||
| }; | ||
| type CssDeclaration = { | ||
| type Declaration = { | ||
| property: string; | ||
@@ -36,11 +28,7 @@ value: string; | ||
| }; | ||
| type ConvertedDeclaration = CssDeclaration & { | ||
| type ConvertedDeclaration = Declaration & { | ||
| className: string; | ||
| kind: 'exact' | 'arbitrary'; | ||
| }; | ||
| type ConversionWarning = { | ||
| declaration: CssDeclaration; | ||
| message: string; | ||
| }; | ||
| type StyleToTailwindResult = { | ||
| type Result = { | ||
| className: string; | ||
@@ -50,12 +38,11 @@ classes: string[]; | ||
| arbitrary: ConvertedDeclaration[]; | ||
| unmatched: CssDeclaration[]; | ||
| warnings: ConversionWarning[]; | ||
| unmatched: Declaration[]; | ||
| }; | ||
| //#endregion | ||
| //#region src/index.d.ts | ||
| declare function styleToTailwind(input: StyleInput, options?: StyleToTailwindOptions): StyleToTailwindResult; | ||
| declare function styleToClassName(input: StyleInput, options?: StyleToTailwindOptions): string; | ||
| declare function styleToClasses(input: StyleInput, options?: StyleToTailwindOptions): string[]; | ||
| declare function cssTextToTailwind(cssText: string, options?: StyleToTailwindOptions): StyleToTailwindResult; | ||
| declare function twirl(input: StyleInput, options?: Options): string; | ||
| declare namespace twirl { | ||
| var convert: (input: StyleInput, options?: Options) => Result; | ||
| } | ||
| //#endregion | ||
| export { type ConvertedDeclaration, type CssDeclaration, type StyleInput, type StyleObject, type StylePrimitive, type StyleToTailwindOptions, type StyleToTailwindResult, type TwirlwindTheme, cssTextToTailwind, styleToClassName, styleToClasses, styleToTailwind }; | ||
| export { type ConvertedDeclaration, type Declaration, type Options, type Result, type StyleInput, type StyleObject, type StylePrimitive, type Theme, twirl }; |
+1
-1
| { | ||
| "name": "twirlwind", | ||
| "version": "0.1.0", | ||
| "version": "0.2.0", | ||
| "description": "Tailwind v4-first CSS style object to utility class serializer with lossless arbitrary-property fallback.", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
+49
-124
@@ -13,83 +13,43 @@ # Twirlwind | ||
| ## Quick start | ||
| ## Usage | ||
| ```ts | ||
| import { styleToClassName } from 'twirlwind' | ||
| import { twirl } from 'twirlwind' | ||
| styleToClassName({ | ||
| display: 'flex', | ||
| justifyContent: 'center', | ||
| padding: '16px 8px', | ||
| backgroundColor: 'oklch(62.3% 0.214 259.815)' | ||
| }) | ||
| // → "flex py-4 px-2 justify-center bg-blue-500" | ||
| twirl({ display: 'flex', padding: '16px 8px', color: '#ef4444' }) | ||
| // → "flex py-4 px-2 text-red-500" | ||
| ``` | ||
| ## API | ||
| ### Inputs | ||
| ### `styleToTailwind(input, options?)` | ||
| `twirl()` accepts any of these and returns a class string: | ||
| Full conversion result with metadata. | ||
| ```ts | ||
| import { styleToTailwind } from 'twirlwind' | ||
| // Style object | ||
| twirl({ backgroundColor: 'white', fontSize: '16px' }) | ||
| const result = styleToTailwind({ display: 'flex', width: '37px' }) | ||
| // CSS string | ||
| twirl('display: flex; padding: 16px') | ||
| result.className // "flex w-[37px]" | ||
| result.classes // ["flex", "w-[37px]"] | ||
| result.exact // ConvertedDeclaration[] — exact utility matches | ||
| result.arbitrary // ConvertedDeclaration[] — arbitrary value/property fallbacks | ||
| result.unmatched // CssDeclaration[] — declarations that couldn't be converted | ||
| result.warnings // ConversionWarning[] | ||
| ``` | ||
| // CSSStyleDeclaration (browser) | ||
| twirl(element.style) | ||
| ### `styleToClassName(input, options?)` | ||
| Returns just the class string. | ||
| ```ts | ||
| styleToClassName({ padding: '16px', color: 'white' }) | ||
| // → "p-4 text-white" | ||
| // Computed styles | ||
| twirl(getComputedStyle(element)) | ||
| ``` | ||
| ### `styleToClasses(input, options?)` | ||
| ### Detailed result | ||
| Returns an array of class strings. | ||
| Use `twirl.convert()` when you need metadata: | ||
| ```ts | ||
| styleToClasses({ margin: '8px 16px' }) | ||
| // → ["my-2", "mx-4"] | ||
| ``` | ||
| const result = twirl.convert({ display: 'flex', width: '37px' }) | ||
| ### `cssTextToTailwind(cssText, options?)` | ||
| Parses a CSS declaration string. | ||
| ```ts | ||
| cssTextToTailwind('display: flex; gap: 16px; padding: 8px') | ||
| // → { className: "flex p-2 gap-4", ... } | ||
| result.className // "flex w-[37px]" | ||
| result.classes // ["flex", "w-[37px]"] | ||
| result.exact // exact utility matches | ||
| result.arbitrary // arbitrary value/property fallbacks | ||
| result.unmatched // declarations that couldn't be converted | ||
| ``` | ||
| ## Input formats | ||
| ```ts | ||
| // Style object (camelCase or kebab-case) | ||
| styleToClassName({ backgroundColor: 'white', 'font-size': '16px' }) | ||
| // CSS declaration string | ||
| styleToClassName('display: flex; padding: 16px') | ||
| // CSSStyleDeclaration (browser) | ||
| styleToClassName(element.style) | ||
| // Iterable of [property, value] pairs | ||
| styleToClassName( | ||
| new Map([ | ||
| ['display', 'flex'], | ||
| ['padding', '16px'] | ||
| ]) | ||
| ) | ||
| ``` | ||
| ## Features | ||
@@ -99,11 +59,9 @@ | ||
| Matches colors across formats to Tailwind's palette — OKLCH (v4), hex (v3), `rgb()`, keywords, and opacity modifiers. | ||
| Matches across formats — OKLCH, hex, `rgb()`, keywords, opacity modifiers. | ||
| ```ts | ||
| styleToClassName({ color: '#ef4444' }) // "text-red-500" | ||
| styleToClassName({ color: 'rgb(59 130 246)' }) // "text-blue-500" | ||
| styleToClassName({ color: 'oklch(62.3% 0.214 259.815)' }) // "text-blue-500" | ||
| styleToClassName({ color: 'oklch(62.3% 0.214 259.815 / 50%)' }) // "text-blue-500/50" | ||
| styleToClassName({ color: 'inherit' }) // "text-inherit" | ||
| styleToClassName({ color: 'currentColor' }) // "text-current" | ||
| twirl({ color: '#ef4444' }) // "text-red-500" | ||
| twirl({ color: 'rgb(59 130 246)' }) // "text-blue-500" | ||
| twirl({ color: 'oklch(62.3% 0.214 259.815 / 50%)' }) // "text-blue-500/50" | ||
| twirl({ color: 'currentColor' }) // "text-current" | ||
| ``` | ||
@@ -116,25 +74,15 @@ | ||
| ```ts | ||
| styleToClassName({ border: '2px solid #ef4444' }) | ||
| // → "border-2 border-red-500 border-solid" | ||
| styleToClassName({ font: 'bold 16px/1.5 sans-serif' }) | ||
| // → "font-bold text-base leading-normal font-sans" | ||
| styleToClassName({ background: 'white center no-repeat' }) | ||
| // → "bg-white bg-center bg-no-repeat" | ||
| styleToClassName({ transition: 'all 200ms ease-in' }) | ||
| // → "transition-all duration-200 ease-in" | ||
| twirl({ border: '2px solid #ef4444' }) // "border-2 border-red-500 border-solid" | ||
| twirl({ font: 'bold 16px/1.5 sans-serif' }) // "font-bold text-base leading-normal font-sans" | ||
| twirl({ background: 'white center no-repeat' }) // "bg-white bg-center bg-no-repeat" | ||
| ``` | ||
| ### Multi-value transforms and filters | ||
| ### Multi-value parsing | ||
| Compound `transform` and `filter` declarations decompose into individual utility classes. | ||
| Compound `transform` and `filter` declarations decompose into individual classes. | ||
| ```ts | ||
| styleToClassName({ transform: 'translateX(8px) rotate(45deg)' }) | ||
| // → "rotate-45 translate-x-2" | ||
| styleToClassName({ filter: 'blur(8px) brightness(0.75)' }) | ||
| // → "blur brightness-75" | ||
| twirl({ transform: 'translateX(8px) rotate(45deg)' }) // "rotate-45 translate-x-2" | ||
| twirl({ filter: 'blur(8px) brightness(0.75)' }) // "blur brightness-75" | ||
| twirl({ scrollSnapType: 'x mandatory' }) // "snap-x snap-mandatory" | ||
| ``` | ||
@@ -144,10 +92,10 @@ | ||
| Expanded longhands compress to shorthand utilities when values match. | ||
| Expanded longhands compress to shorthand utilities. | ||
| ```ts | ||
| styleToClassName({ margin: '8px' }) // "m-2" (not "mt-2 mr-2 mb-2 ml-2") | ||
| styleToClassName({ inset: '0' }) // "inset-0" | ||
| styleToClassName({ padding: '8px 16px' }) // "py-2 px-4" | ||
| styleToClassName({ borderRadius: '8px' }) // "rounded-lg" | ||
| styleToClassName({ gap: '12px 12px' }) // "gap-3" | ||
| twirl({ margin: '8px' }) // "m-2" | ||
| twirl({ inset: '0' }) // "inset-0" | ||
| twirl({ padding: '8px 16px' }) // "py-2 px-4" | ||
| twirl({ borderRadius: '8px' }) // "rounded-lg" | ||
| twirl({ gap: '12px 12px' }) // "gap-3" | ||
| ``` | ||
@@ -157,6 +105,6 @@ | ||
| Nested objects map to Tailwind variants — pseudo-classes, media queries, and container queries. | ||
| Nested objects map to Tailwind variants. | ||
| ```ts | ||
| styleToClassName({ | ||
| twirl({ | ||
| color: 'white', | ||
@@ -171,19 +119,9 @@ ':hover': { color: '#3b82f6' }, | ||
| ### Scroll snap decomposition | ||
| ```ts | ||
| styleToClassName({ scrollSnapType: 'x mandatory' }) | ||
| // → "snap-x snap-mandatory" | ||
| ``` | ||
| ### Arbitrary fallback | ||
| Every CSS property produces valid output. Unknown properties use `[property:value]` syntax. | ||
| Every CSS property produces valid output. | ||
| ```ts | ||
| styleToClassName({ scrollTimelineName: '--main' }) | ||
| // → "[scroll-timeline-name:--main]" | ||
| styleToClassName({ width: '37px' }) | ||
| // → "w-[37px]" | ||
| twirl({ scrollTimelineName: '--main' }) // "[scroll-timeline-name:--main]" | ||
| twirl({ width: '37px' }) // "w-[37px]" | ||
| ``` | ||
@@ -194,22 +132,9 @@ | ||
| ```ts | ||
| styleToTailwind(input, { | ||
| // Allow arbitrary value utilities like w-[37px] | ||
| twirl(input, { | ||
| allowArbitraryValues: true, // default: true | ||
| // Allow arbitrary property fallbacks like [scroll-timeline-name:--x] | ||
| allowArbitraryProperties: true, // default: true | ||
| // Compress matching longhands to shorthands | ||
| compression: 'safe', // "none" | "safe" | "aggressive" | ||
| // Sort output classes | ||
| sort: 'grouped', // "input" | "tailwind" | "grouped" | ||
| // Color matching strategy | ||
| colorMatch: 'exact', // "exact" | "nearest" | "none" | ||
| // Spacing token multiplier mode | ||
| numericMultipliers: 'integer', // "all" | "integer" | "never" | ||
| // Custom theme colors/spacing | ||
| theme: { | ||
@@ -225,5 +150,5 @@ colors: { brand: '#ff6600' }, | ||
| 1. **Normalize** — camelCase → kebab-case, numeric → px, vendor prefixes, `!important` | ||
| 2. **Expand shorthands** — `margin`, `border`, `font`, `background`, `transition`, `overflow`, `gap`, etc. | ||
| 2. **Expand** — `margin`, `border`, `font`, `background`, `transition`, `overflow`, `gap`, etc. | ||
| 3. **Convert** — exact utility → value alias → spacing token → color match → arbitrary value → arbitrary property | ||
| 4. **Compress** — merge matching longhands back to axis/all shorthand utilities | ||
| 4. **Compress** — merge longhands back to shorthand utilities | ||
| 5. **Sort** — deterministic output ordering | ||
@@ -230,0 +155,0 @@ |
Sorry, the diff of this file is too big to display
112634
-2.97%3529
-0.45%151
-33.19%