Skip to content

Commit b8d8d0d

Browse files
committed
chore: wip
1 parent a365623 commit b8d8d0d

5 files changed

Lines changed: 243 additions & 3 deletions

File tree

bunpress.config.ts

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import type { BunPressOptions } from '@stacksjs/bunpress'
2+
3+
export default {
4+
verbose: false,
5+
docsDir: './docs',
6+
outDir: './dist',
7+
8+
nav: [
9+
{ text: 'Home', link: '/' },
10+
{ text: 'Guide', link: '/intro' },
11+
{ text: 'Usage', link: '/usage' },
12+
{ text: 'Configuration', link: '/config' },
13+
{ text: 'Features', link: '/features/cli' },
14+
{
15+
text: 'Ecosystem',
16+
items: [
17+
{ text: 'STX Templating', link: 'https://stx.sh' },
18+
{ text: 'BunPress Docs', link: 'https://bunpress.sh' },
19+
{ text: 'Clarity Logging', link: 'https://clarity.sh' },
20+
{ text: 'Pantry', link: 'https://pantry.sh' },
21+
{ text: 'Stacks Framework', link: 'https://stacksjs.org' },
22+
],
23+
},
24+
{ text: 'GitHub', link: 'https://github.com/stacksjs/headwind' },
25+
],
26+
27+
markdown: {
28+
title: 'Headwind - Blazingly Fast Utility-First CSS',
29+
meta: {
30+
description: 'A utility-first CSS framework built with Bun for exceptional performance. Generate only the CSS you need.',
31+
author: 'Stacks.js',
32+
keywords: 'css, utility-first, tailwind, bun, performance, framework',
33+
},
34+
35+
sidebar: {
36+
'/': [
37+
{
38+
text: 'Getting Started',
39+
items: [
40+
{ text: 'Introduction', link: '/intro' },
41+
{ text: 'Installation', link: '/install' },
42+
{ text: 'Configuration', link: '/config' },
43+
],
44+
},
45+
{
46+
text: 'Usage',
47+
items: [
48+
{ text: 'Basic Usage', link: '/usage' },
49+
{ text: 'API Reference', link: '/api-reference' },
50+
],
51+
},
52+
{
53+
text: 'Features',
54+
items: [
55+
{ text: 'CLI', link: '/features/cli' },
56+
{ text: 'Watch Mode', link: '/features/watch-mode' },
57+
{ text: 'Class Compilation', link: '/features/compile-class' },
58+
{ text: 'Shortcuts', link: '/features/shortcuts' },
59+
{ text: 'TypeScript', link: '/features/typescript' },
60+
],
61+
},
62+
{
63+
text: 'Advanced',
64+
items: [
65+
{ text: 'Custom Rules', link: '/advanced/custom-rules' },
66+
{ text: 'Presets', link: '/advanced/presets' },
67+
{ text: 'Theme Customization', link: '/advanced/theme-customization' },
68+
{ text: 'Framework Integration', link: '/advanced/frameworks' },
69+
],
70+
},
71+
{
72+
text: 'Resources',
73+
items: [
74+
{ text: 'Benchmarks', link: '/benchmarks' },
75+
{ text: 'Showcase', link: '/showcase' },
76+
{ text: 'Team', link: '/team' },
77+
{ text: 'Sponsors', link: '/sponsors' },
78+
],
79+
},
80+
],
81+
},
82+
83+
toc: {
84+
enabled: true,
85+
position: 'sidebar',
86+
title: 'On this page',
87+
minDepth: 2,
88+
maxDepth: 4,
89+
smoothScroll: true,
90+
activeHighlight: true,
91+
},
92+
93+
syntaxHighlightTheme: 'github-dark',
94+
95+
features: {
96+
containers: true,
97+
githubAlerts: true,
98+
codeBlocks: {
99+
lineNumbers: true,
100+
lineHighlighting: true,
101+
focus: true,
102+
diffs: true,
103+
errorWarningMarkers: true,
104+
},
105+
codeGroups: true,
106+
emoji: true,
107+
badges: true,
108+
},
109+
},
110+
111+
sitemap: {
112+
enabled: true,
113+
baseUrl: 'https://headwind.sh',
114+
priorityMap: {
115+
'/': 1.0,
116+
'/intro': 0.9,
117+
'/install': 0.9,
118+
'/usage': 0.8,
119+
'/config': 0.8,
120+
'/features/*': 0.7,
121+
'/advanced/*': 0.6,
122+
},
123+
},
124+
125+
robots: {
126+
enabled: true,
127+
rules: [
128+
{
129+
userAgent: '*',
130+
allow: ['/'],
131+
disallow: ['/draft/'],
132+
},
133+
],
134+
},
135+
136+
fathom: {
137+
enabled: true,
138+
siteId: 'HEADWIND',
139+
honorDNT: true,
140+
},
141+
} satisfies BunPressOptions

packages/headwind/src/parser.ts

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -989,17 +989,31 @@ function parseClassImpl(className: string): ParsedClass {
989989

990990
// Check for arbitrary values with brackets BEFORE splitting on colons
991991
// This handles cases like bg-[url(https://...)] where the URL contains colons
992+
// Also handles type hints like text-[color:var(--muted)]
992993
const preArbitraryMatch = cleanClassName.match(/^((?:[a-z-]+:)*)([a-z-]+?)-\[(.+)\]$/)
993994
if (preArbitraryMatch) {
994995
const variantPart = preArbitraryMatch[1]
995996
const variants = variantPart ? variantPart.split(':').filter(Boolean) : []
997+
let value = preArbitraryMatch[3]
998+
let typeHint: string | undefined
999+
1000+
// Check for type hint in arbitrary value: text-[color:var(--muted)]
1001+
// Type hints are: color, length, url, number, percentage, position, etc.
1002+
// Don't match if it looks like a CSS variable var(--...) or CSS function
1003+
const typeHintMatch = value.match(/^(color|length|url|number|percentage|position|line-width|absolute-size|relative-size|image|angle|time|flex|string|family-name):(.*)/i)
1004+
if (typeHintMatch) {
1005+
typeHint = typeHintMatch[1].toLowerCase()
1006+
value = typeHintMatch[2]
1007+
}
1008+
9961009
return {
9971010
raw: className,
9981011
variants,
9991012
utility: preArbitraryMatch[2],
1000-
value: preArbitraryMatch[3],
1013+
value,
10011014
important,
10021015
arbitrary: true,
1016+
typeHint,
10031017
}
10041018
}
10051019

@@ -1039,16 +1053,28 @@ function parseClassImpl(className: string): ParsedClass {
10391053
}
10401054
}
10411055

1042-
// Check for arbitrary values: w-[100px] or bg-[#ff0000]
1056+
// Check for arbitrary values: w-[100px] or bg-[#ff0000] or text-[color:var(--muted)]
10431057
const arbitraryMatch = utility.match(/^([a-z-]+?)-\[(.+?)\]$/)
10441058
if (arbitraryMatch) {
1059+
let value = arbitraryMatch[2]
1060+
let typeHint: string | undefined
1061+
1062+
// Check for type hint in arbitrary value: text-[color:var(--muted)]
1063+
// Type hints are: color, length, url, number, percentage, position, etc.
1064+
const typeHintMatch = value.match(/^(color|length|url|number|percentage|position|line-width|absolute-size|relative-size|image|angle|time|flex|string|family-name):(.*)/i)
1065+
if (typeHintMatch) {
1066+
typeHint = typeHintMatch[1].toLowerCase()
1067+
value = typeHintMatch[2]
1068+
}
1069+
10451070
return {
10461071
raw: className,
10471072
variants,
10481073
utility: arbitraryMatch[1],
1049-
value: arbitraryMatch[2],
1074+
value,
10501075
important,
10511076
arbitrary: true,
1077+
typeHint,
10521078
}
10531079
}
10541080

packages/headwind/src/rules.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,11 @@ export const colorRule: UtilityRule = (parsed, config) => {
348348

349349
const value = parsed.value
350350

351+
// Handle type hint for color: text-[color:var(--muted)] -> color: var(--muted)
352+
if (parsed.arbitrary && parsed.typeHint === 'color') {
353+
return { [prop]: value }
354+
}
355+
351356
// Build/update flat color cache if needed
352357
if (flatColorCache === null || flatColorCacheConfig !== config.theme.colors) {
353358
flatColorCache = buildFlatColorCache(config.theme.colors)
@@ -454,6 +459,16 @@ export const fontSizeRule: UtilityRule = (parsed, config) => {
454459
if (parsed.utility === 'text' && parsed.value) {
455460
// Handle arbitrary values first
456461
if (parsed.arbitrary) {
462+
// If there's a type hint, only handle font-size if it's a length-related type
463+
// For 'color' type hint, let colorRule handle it
464+
if (parsed.typeHint) {
465+
if (parsed.typeHint === 'color') {
466+
return undefined // Let colorRule handle it
467+
}
468+
// 'length' type hint or other size-related types -> font-size
469+
return { 'font-size': parsed.value } as Record<string, string>
470+
}
471+
// No type hint - default to font-size (backwards compatible)
457472
return { 'font-size': parsed.value } as Record<string, string>
458473
}
459474
const fontSize = config.theme.fontSize[parsed.value]

packages/headwind/src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ export interface ParsedClass {
157157
value?: string
158158
important: boolean
159159
arbitrary: boolean
160+
typeHint?: string // Type hint for arbitrary values, e.g., 'color' in text-[color:var(--muted)]
160161
}
161162

162163
export interface CSSRule {

packages/headwind/test/arbitrary.test.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,63 @@ describe('Arbitrary Values and Properties', () => {
126126
})
127127
})
128128

129+
describe('Type hints in arbitrary values', () => {
130+
it('should handle color type hint with CSS variable', () => {
131+
const gen = new CSSGenerator(defaultConfig)
132+
gen.generate('text-[color:var(--muted)]')
133+
expect(gen.toCSS(false)).toContain('color: var(--muted);')
134+
})
135+
136+
it('should handle length type hint', () => {
137+
const gen = new CSSGenerator(defaultConfig)
138+
gen.generate('text-[length:1.5rem]')
139+
expect(gen.toCSS(false)).toContain('font-size: 1.5rem;')
140+
})
141+
142+
it('should handle border color type hint', () => {
143+
const gen = new CSSGenerator(defaultConfig)
144+
gen.generate('border-[color:var(--border)]')
145+
expect(gen.toCSS(false)).toContain('border-color: var(--border);')
146+
})
147+
148+
it('should handle bg color type hint', () => {
149+
const gen = new CSSGenerator(defaultConfig)
150+
gen.generate('bg-[color:var(--background)]')
151+
expect(gen.toCSS(false)).toContain('background-color: var(--background);')
152+
})
153+
154+
it('should parse type hint correctly', () => {
155+
const result = parseClass('text-[color:var(--muted)]')
156+
expect(result.utility).toBe('text')
157+
expect(result.value).toBe('var(--muted)')
158+
expect(result.typeHint).toBe('color')
159+
expect(result.arbitrary).toBe(true)
160+
})
161+
162+
it('should parse length type hint correctly', () => {
163+
const result = parseClass('w-[length:100px]')
164+
expect(result.utility).toBe('w')
165+
expect(result.value).toBe('100px')
166+
expect(result.typeHint).toBe('length')
167+
expect(result.arbitrary).toBe(true)
168+
})
169+
170+
it('should not confuse CSS var() with type hint', () => {
171+
const result = parseClass('text-[var(--size)]')
172+
expect(result.utility).toBe('text')
173+
expect(result.value).toBe('var(--size)')
174+
expect(result.typeHint).toBeUndefined()
175+
})
176+
177+
it('should handle type hint with variant', () => {
178+
const gen = new CSSGenerator(defaultConfig)
179+
gen.generate('hover:text-[color:var(--muted)]')
180+
const css = gen.toCSS(false)
181+
expect(css).toContain(':hover')
182+
expect(css).toContain('color: var(--muted);')
183+
})
184+
})
185+
129186
describe('Modern CSS units', () => {
130187
it('should handle dvh units', () => {
131188
const gen = new CSSGenerator(defaultConfig)

0 commit comments

Comments
 (0)