Skip to content

Commit c4367f9

Browse files
committed
chore: minor updates
1 parent 92a4264 commit c4367f9

File tree

4 files changed

+305
-16
lines changed

4 files changed

+305
-16
lines changed

packages/headwind/src/config.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,42 @@ export const defaultConfig: HeadwindConfig = {
2323
800: '#1f2937',
2424
900: '#111827',
2525
},
26+
'blue-gray': {
27+
50: '#f8fafc',
28+
100: '#f1f5f9',
29+
200: '#e2e8f0',
30+
300: '#cbd5e1',
31+
400: '#94a3b8',
32+
500: '#64748b',
33+
600: '#475569',
34+
700: '#334155',
35+
800: '#1e293b',
36+
900: '#0f172a',
37+
},
38+
sky: {
39+
50: '#f0f9ff',
40+
100: '#e0f2fe',
41+
200: '#bae6fd',
42+
300: '#7dd3fc',
43+
400: '#38bdf8',
44+
500: '#0ea5e9',
45+
600: '#0284c7',
46+
700: '#0369a1',
47+
800: '#075985',
48+
900: '#0c4a6e',
49+
},
50+
cyan: {
51+
50: '#ecfeff',
52+
100: '#cffafe',
53+
200: '#a5f3fc',
54+
300: '#67e8f9',
55+
400: '#22d3ee',
56+
500: '#06b6d4',
57+
600: '#0891b2',
58+
700: '#0e7490',
59+
800: '#155e75',
60+
900: '#164e63',
61+
},
2662
red: {
2763
500: '#ef4444',
2864
700: '#b91c1c',

packages/headwind/src/rules-advanced.ts

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -88,30 +88,33 @@ export const ringRule: UtilityRule = (parsed, config) => {
8888
}
8989

9090
if (parsed.utility === 'ring-offset' && parsed.value) {
91+
// First check if it's a width value
9192
const widths: Record<string, string> = {
9293
0: '0px',
9394
1: '1px',
9495
2: '2px',
9596
4: '4px',
9697
8: '8px',
9798
}
98-
return { '--hw-ring-offset-width': widths[parsed.value] || parsed.value } as Record<string, string>
99-
}
99+
if (widths[parsed.value]) {
100+
return { '--hw-ring-offset-width': widths[parsed.value] } as Record<string, string>
101+
}
100102

101-
if (parsed.utility === 'ring-offset-color' || (parsed.utility === 'ring-offset' && parsed.value)) {
102-
// Handle ring-offset-{color}
103-
const value = parsed.value
104-
if (value) {
105-
const parts = value.split('-')
106-
if (parts.length >= 2) {
107-
const colorName = parts.slice(0, -1).join('-')
108-
const shade = parts[parts.length - 1]
109-
const colorValue = config.theme.colors[colorName]
110-
if (typeof colorValue === 'object' && colorValue[shade]) {
111-
return { '--hw-ring-offset-color': colorValue[shade] } as Record<string, string>
112-
}
103+
// Otherwise, treat as a color (e.g., ring-offset-ocean-blue)
104+
const parts = parsed.value.split('-')
105+
if (parts.length >= 2) {
106+
const colorName = parts.slice(0, -1).join('-')
107+
const shade = parts[parts.length - 1]
108+
const colorValue = config.theme.colors[colorName]
109+
if (typeof colorValue === 'object' && colorValue[shade]) {
110+
return { '--hw-ring-offset-color': colorValue[shade] } as Record<string, string>
113111
}
114112
}
113+
// Check for direct color (e.g., ring-offset-black)
114+
const directColor = config.theme.colors[parsed.value]
115+
if (typeof directColor === 'string') {
116+
return { '--hw-ring-offset-color': directColor } as Record<string, string>
117+
}
115118
}
116119
}
117120

@@ -260,6 +263,13 @@ export const divideRule: UtilityRule = (parsed, config) => {
260263
// Gradient color stops
261264
export const gradientStopsRule: UtilityRule = (parsed, config) => {
262265
const getColor = (value: string) => {
266+
// First check if it's a direct color value (e.g., ocean-blue, black, white)
267+
const directColor = config.theme.colors[value]
268+
if (typeof directColor === 'string') {
269+
return directColor
270+
}
271+
272+
// Then check if it's a color-shade combination (e.g., sky-500, blue-gray-200)
263273
const parts = value.split('-')
264274
if (parts.length >= 2) {
265275
const colorName = parts.slice(0, -1).join('-')
@@ -293,6 +303,7 @@ export const gradientStopsRule: UtilityRule = (parsed, config) => {
293303
const color = getColor(parsed.value)
294304
return {
295305
'--hw-gradient-to': color,
306+
'--hw-gradient-stops': 'var(--hw-gradient-from), var(--hw-gradient-to)',
296307
} as Record<string, string>
297308
}
298309
}

packages/headwind/src/rules-interactivity.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,35 @@ export const strokeWidthRule: UtilityRule = (parsed) => {
411411
}
412412

413413
// Accessibility
414+
export const screenReaderRule: UtilityRule = (parsed) => {
415+
if (parsed.raw === 'sr-only') {
416+
return {
417+
position: 'absolute',
418+
width: '1px',
419+
height: '1px',
420+
padding: '0',
421+
margin: '-1px',
422+
overflow: 'hidden',
423+
clip: 'rect(0, 0, 0, 0)',
424+
'white-space': 'nowrap',
425+
'border-width': '0',
426+
} as Record<string, string>
427+
}
428+
if (parsed.raw === 'not-sr-only') {
429+
return {
430+
position: 'static',
431+
width: 'auto',
432+
height: 'auto',
433+
padding: '0',
434+
margin: '0',
435+
overflow: 'visible',
436+
clip: 'auto',
437+
'white-space': 'normal',
438+
} as Record<string, string>
439+
}
440+
return undefined
441+
}
442+
414443
export const forcedColorAdjustRule: UtilityRule = (parsed) => {
415444
const values: Record<string, string> = {
416445
'forced-color-adjust-auto': 'auto',
@@ -444,5 +473,6 @@ export const interactivityRules: UtilityRule[] = [
444473
fillRule,
445474
strokeRule,
446475
strokeWidthRule,
476+
screenReaderRule,
447477
forcedColorAdjustRule,
448478
]

packages/headwind/test/advanced-features.test.ts

Lines changed: 214 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,7 @@ describe('Advanced Features', () => {
402402
gen.generate('space-x-8')
403403
gen.generate('space-x-reverse')
404404
const css = gen.toCSS(false)
405-
expect(css).toContain('--tw-space-x-reverse')
405+
expect(css).toContain('--hw-space-x-reverse')
406406
expect(css).toContain('margin-right')
407407
expect(css).toContain('margin-left')
408408
})
@@ -414,7 +414,7 @@ describe('Advanced Features', () => {
414414
gen.generate('space-y-8')
415415
gen.generate('space-y-reverse')
416416
const css = gen.toCSS(false)
417-
expect(css).toContain('--tw-space-y-reverse')
417+
expect(css).toContain('--hw-space-y-reverse')
418418
expect(css).toContain('margin-top')
419419
expect(css).toContain('margin-bottom')
420420
})
@@ -687,4 +687,216 @@ describe('Advanced Features', () => {
687687
expect(css).toContain('animation')
688688
})
689689
})
690+
691+
describe('Multi-segment color names', () => {
692+
it('should handle blue-gray color', () => {
693+
const gen = new CSSGenerator(defaultConfig)
694+
gen.generate('text-blue-gray-200')
695+
gen.generate('bg-blue-gray-500')
696+
gen.generate('border-blue-gray-700')
697+
const css = gen.toCSS(false)
698+
expect(css).toContain('#e2e8f0') // blue-gray-200
699+
expect(css).toContain('#64748b') // blue-gray-500
700+
expect(css).toContain('#334155') // blue-gray-700
701+
})
702+
703+
it('should handle sky color', () => {
704+
const gen = new CSSGenerator(defaultConfig)
705+
gen.generate('text-sky-400')
706+
gen.generate('bg-sky-500')
707+
gen.generate('border-sky-600')
708+
const css = gen.toCSS(false)
709+
expect(css).toContain('#38bdf8') // sky-400
710+
expect(css).toContain('#0ea5e9') // sky-500
711+
expect(css).toContain('#0284c7') // sky-600
712+
})
713+
714+
it('should handle cyan color', () => {
715+
const gen = new CSSGenerator(defaultConfig)
716+
gen.generate('text-cyan-400')
717+
gen.generate('bg-cyan-500')
718+
gen.generate('border-cyan-600')
719+
const css = gen.toCSS(false)
720+
expect(css).toContain('#22d3ee') // cyan-400
721+
expect(css).toContain('#06b6d4') // cyan-500
722+
expect(css).toContain('#0891b2') // cyan-600
723+
})
724+
})
725+
726+
describe('Ring offset colors', () => {
727+
it('should handle ring-offset width', () => {
728+
const gen = new CSSGenerator(defaultConfig)
729+
gen.generate('ring-offset-0')
730+
gen.generate('ring-offset-1')
731+
gen.generate('ring-offset-2')
732+
gen.generate('ring-offset-4')
733+
const css = gen.toCSS(false)
734+
expect(css).toContain('--hw-ring-offset-width')
735+
expect(css).toContain('0px')
736+
expect(css).toContain('1px')
737+
expect(css).toContain('2px')
738+
expect(css).toContain('4px')
739+
})
740+
741+
it('should handle ring-offset with single color', () => {
742+
const gen = new CSSGenerator(defaultConfig)
743+
gen.generate('ring-offset-black')
744+
gen.generate('ring-offset-white')
745+
const css = gen.toCSS(false)
746+
expect(css).toContain('--hw-ring-offset-color')
747+
expect(css).toContain('#000')
748+
expect(css).toContain('#fff')
749+
})
750+
751+
it('should handle ring-offset with two-segment color', () => {
752+
const gen = new CSSGenerator(defaultConfig)
753+
gen.generate('ring-offset-blue-500')
754+
gen.generate('ring-offset-red-500')
755+
gen.generate('ring-offset-gray-300')
756+
const css = gen.toCSS(false)
757+
expect(css).toContain('--hw-ring-offset-color')
758+
expect(css).toContain('#3b82f6') // blue-500
759+
expect(css).toContain('#ef4444') // red-500
760+
expect(css).toContain('#d1d5db') // gray-300
761+
})
762+
763+
it('should handle ring-offset with multi-segment color names', () => {
764+
const gen = new CSSGenerator(defaultConfig)
765+
gen.generate('ring-offset-blue-gray-500')
766+
gen.generate('ring-offset-sky-400')
767+
gen.generate('ring-offset-cyan-600')
768+
const css = gen.toCSS(false)
769+
expect(css).toContain('--hw-ring-offset-color')
770+
expect(css).toContain('#64748b') // blue-gray-500
771+
expect(css).toContain('#38bdf8') // sky-400
772+
expect(css).toContain('#0891b2') // cyan-600
773+
})
774+
})
775+
776+
describe('Gradient utilities', () => {
777+
it('should handle gradient direction utilities', () => {
778+
const gen = new CSSGenerator(defaultConfig)
779+
gen.generate('bg-gradient-to-r')
780+
gen.generate('bg-gradient-to-l')
781+
gen.generate('bg-gradient-to-t')
782+
gen.generate('bg-gradient-to-b')
783+
gen.generate('bg-gradient-to-tr')
784+
gen.generate('bg-gradient-to-br')
785+
gen.generate('bg-gradient-to-bl')
786+
gen.generate('bg-gradient-to-tl')
787+
const css = gen.toCSS(false)
788+
expect(css).toContain('linear-gradient')
789+
expect(css).toContain('to right')
790+
expect(css).toContain('to left')
791+
expect(css).toContain('to top')
792+
expect(css).toContain('to bottom')
793+
})
794+
795+
it('should handle gradient color stops', () => {
796+
const gen = new CSSGenerator(defaultConfig)
797+
gen.generate('from-blue-500')
798+
gen.generate('via-gray-300')
799+
gen.generate('to-red-500')
800+
const css = gen.toCSS(false)
801+
expect(css).toContain('--hw-gradient-from')
802+
expect(css).toContain('--hw-gradient-to')
803+
expect(css).toContain('--hw-gradient-stops')
804+
expect(css).toContain('#3b82f6') // blue-500
805+
expect(css).toContain('#ef4444') // red-500
806+
})
807+
808+
it('should handle gradient with multi-segment colors', () => {
809+
const gen = new CSSGenerator(defaultConfig)
810+
gen.generate('from-blue-gray-500')
811+
gen.generate('via-sky-400')
812+
gen.generate('to-cyan-600')
813+
const css = gen.toCSS(false)
814+
expect(css).toContain('--hw-gradient-from')
815+
expect(css).toContain('--hw-gradient-to')
816+
expect(css).toContain('--hw-gradient-stops')
817+
expect(css).toContain('#64748b') // blue-gray-500
818+
expect(css).toContain('#38bdf8') // sky-400
819+
expect(css).toContain('#0891b2') // cyan-600
820+
})
821+
822+
it('should handle gradient with hover variants', () => {
823+
const gen = new CSSGenerator(defaultConfig)
824+
gen.generate('hover:from-sky-500')
825+
gen.generate('hover:to-cyan-500')
826+
gen.generate('hover:via-blue-500')
827+
const css = gen.toCSS(false)
828+
expect(css).toContain(':hover')
829+
expect(css).toContain('--hw-gradient-from')
830+
expect(css).toContain('--hw-gradient-to')
831+
expect(css).toContain('#0ea5e9') // sky-500
832+
expect(css).toContain('#06b6d4') // cyan-500
833+
})
834+
835+
it('should handle complete gradient combination', () => {
836+
const gen = new CSSGenerator(defaultConfig)
837+
gen.generate('bg-gradient-to-r')
838+
gen.generate('from-sky-500')
839+
gen.generate('to-cyan-500')
840+
const css = gen.toCSS(false)
841+
expect(css).toContain('linear-gradient(to right, var(--hw-gradient-stops))')
842+
expect(css).toContain('--hw-gradient-from: #0ea5e9') // sky-500
843+
expect(css).toContain('--hw-gradient-to: #06b6d4') // cyan-500
844+
})
845+
846+
it('should properly update gradient stops when using to-{color}', () => {
847+
const gen = new CSSGenerator(defaultConfig)
848+
gen.generate('from-blue-500')
849+
gen.generate('to-red-500')
850+
const css = gen.toCSS(false)
851+
// Both from and to should set --hw-gradient-stops
852+
const fromMatch = css.match(/\.from-blue-500\s*\{[^}]*\}/)
853+
const toMatch = css.match(/\.to-red-500\s*\{[^}]*\}/)
854+
expect(fromMatch).toBeTruthy()
855+
expect(toMatch).toBeTruthy()
856+
expect(fromMatch![0]).toContain('--hw-gradient-stops')
857+
expect(toMatch![0]).toContain('--hw-gradient-stops')
858+
})
859+
})
860+
861+
describe('Accessibility utilities', () => {
862+
it('should handle sr-only utility', () => {
863+
const gen = new CSSGenerator(defaultConfig)
864+
gen.generate('sr-only')
865+
const css = gen.toCSS(false)
866+
expect(css).toContain('position: absolute')
867+
expect(css).toContain('width: 1px')
868+
expect(css).toContain('height: 1px')
869+
expect(css).toContain('padding: 0')
870+
expect(css).toContain('margin: -1px')
871+
expect(css).toContain('overflow: hidden')
872+
expect(css).toContain('clip: rect(0, 0, 0, 0)')
873+
expect(css).toContain('white-space: nowrap')
874+
expect(css).toContain('border-width: 0')
875+
})
876+
877+
it('should handle not-sr-only utility', () => {
878+
const gen = new CSSGenerator(defaultConfig)
879+
gen.generate('not-sr-only')
880+
const css = gen.toCSS(false)
881+
expect(css).toContain('position: static')
882+
expect(css).toContain('width: auto')
883+
expect(css).toContain('height: auto')
884+
expect(css).toContain('padding: 0')
885+
expect(css).toContain('margin: 0')
886+
expect(css).toContain('overflow: visible')
887+
expect(css).toContain('clip: auto')
888+
expect(css).toContain('white-space: normal')
889+
})
890+
891+
it('should handle accessibility in forms', () => {
892+
const gen = new CSSGenerator(defaultConfig)
893+
// sr-only is typically used for labels and screen reader text
894+
gen.generate('sr-only')
895+
const css = gen.toCSS(false)
896+
// Verify all required properties for accessibility
897+
expect(css).toContain('position')
898+
expect(css).toContain('width')
899+
expect(css).toContain('height')
900+
})
901+
})
690902
})

0 commit comments

Comments
 (0)