Skip to content

Commit fe74ec9

Browse files
committed
feat(states): put states in theme (#206)
BREAKING CHANGE: states are now passed in object.
1 parent c3893f1 commit fe74ec9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+221
-3592
lines changed

packages/prop-types/src/index.test.ts

Lines changed: 0 additions & 420 deletions
Large diffs are not rendered by default.

packages/system/src/defaultStates.ts

Lines changed: 0 additions & 17 deletions
This file was deleted.

packages/system/src/defaultTheme.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,6 @@ export const defaultTheme = {
319319
colors: generateHexAlphaVariants(colors),
320320
space,
321321
screens: {
322-
_: 0,
323322
xs: 0,
324323
sm: 640,
325324
md: 768,
@@ -472,6 +471,24 @@ export const defaultTheme = {
472471
pulse: 'x-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite',
473472
bounce: 'x-bounce 1s infinite',
474473
},
474+
states: {
475+
_: null,
476+
motionSafe: '@media (prefers-reduced-motion: no-preference)',
477+
motionReduce: '@media (prefers-reduced-motion: reduce)',
478+
first: '&:first-child',
479+
last: '&:last-child',
480+
odd: '&:odd',
481+
even: '&:even',
482+
visited: '&:visited',
483+
checked: '&:checked',
484+
focusWithin: '&:focus-within',
485+
hover: '&:hover',
486+
focus: '&:focus',
487+
focusVisible: '&:focus-visible',
488+
active: '&:active',
489+
disabled: '&:disabled',
490+
placeholder: '&::placeholder',
491+
},
475492
}
476493

477494
export type DefaultTheme = typeof defaultTheme

packages/system/src/states.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { is, getThemeValue } from '@xstyled/util'
2+
import { IStates, IProps } from './types'
3+
4+
export function getThemeStates(props: IProps): IStates {
5+
const themeStates = getThemeValue<IProps>(props, 'states')
6+
if (is(themeStates)) return themeStates
7+
return {}
8+
}

packages/system/src/style.test.ts

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,23 +96,49 @@ describe('#style', () => {
9696
})
9797
})
9898

99-
it('works with states', () => {
100-
const theme = { screens: { _: 0, md: 400 } }
101-
expect(fontFamily({ hoverFontFamily: 'title' })).toEqual({
99+
it('works with variants', () => {
100+
const theme = {
101+
states: { hover: '&:hover', first: '&:first-child' },
102+
screens: { _: 0, md: 400 },
103+
}
104+
expect(fontFamily({ fontFamily: { hover: 'title' }, theme })).toEqual({
102105
'&:hover': {
103106
fontFamily: 'title',
104107
},
105108
})
106109
expect(
107110
fontFamily({
108-
motionReduceFontFamily: { md: 'title' },
111+
fontFamily: { hover: { md: 'title' } },
109112
theme,
110113
}),
111114
).toEqual({
112-
'@media (prefers-reduced-motion: reduce)': {
115+
'&:hover': {
113116
'@media (min-width: 400px)': { fontFamily: 'title' },
114117
},
115118
})
119+
expect(
120+
fontFamily({
121+
fontFamily: { hover: { first: { md: 'title' } } },
122+
theme,
123+
}),
124+
).toEqual({
125+
'&:hover': {
126+
'&:first-child': {
127+
'@media (min-width: 400px)': { fontFamily: 'title' },
128+
},
129+
},
130+
})
131+
expect(
132+
fontFamily({
133+
fontFamily: { _: 'title', hover: 'title2' },
134+
theme,
135+
}),
136+
).toEqual({
137+
fontFamily: 'title',
138+
'&:hover': {
139+
fontFamily: 'title2',
140+
},
141+
})
116142
})
117143

118144
it('returns empty object if style is not valid', () => {

packages/system/src/style.ts

Lines changed: 31 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,12 @@ import {
77
obj,
88
getThemeValue,
99
warn,
10-
identity,
1110
merge,
1211
assign,
1312
cascade,
1413
} from '@xstyled/util'
1514
import { getBreakpoints, getBreakpointMin, mediaMinWidth } from './media'
16-
import { defaultStates } from './defaultStates'
15+
import { getThemeStates } from './states'
1716
import {
1817
IProps,
1918
IStyles,
@@ -26,10 +25,6 @@ import {
2625
Mixin,
2726
} from './types'
2827

29-
const defaultStateKeys = Object.keys(
30-
defaultStates,
31-
) as (keyof typeof defaultStates)[]
32-
3328
const cacheSupported =
3429
typeof Map !== 'undefined' && typeof WeakMap !== 'undefined'
3530

@@ -143,42 +138,43 @@ export function createStyleGenerator(
143138
return generator
144139
}
145140

146-
function getMedias(props: IProps) {
141+
function getStates(props: IProps) {
147142
const breakpoints = getBreakpoints(props)
148143
const medias: { [key: string]: string | null } = {}
149144
for (const breakpoint in breakpoints) {
150145
medias[breakpoint] = mediaMinWidth(
151146
getBreakpointMin(breakpoints, breakpoint),
152147
)
153148
}
154-
return medias
149+
return { ...medias, ...getThemeStates(props) }
155150
}
156151

157-
function getCachedMedias(props: IProps, cache: ThemeCache) {
158-
if (cache.has('_medias')) {
159-
return cache.get('_medias')
152+
function getCachedStates(props: IProps, cache: ThemeCache) {
153+
if (cache.has('_states')) {
154+
return cache.get('_states')
160155
}
161-
const medias = getMedias(props)
162-
cache.set('_medias', medias)
163-
return medias
156+
const states = getStates(props)
157+
cache.set('_states', states)
158+
return states
164159
}
165160

166-
export function reduceBreakpoints(
161+
export function reduceStates(
167162
props: IProps,
168163
values: { [key: string]: any },
169-
getStyle: (value: any) => IStyles | null = identity,
164+
getStyle: (value: any) => IStyles | null,
170165
cache?: ThemeCache,
171-
) {
172-
const medias = cache ? getCachedMedias(props, cache) : getMedias(props)
166+
): IStyles {
167+
const states = cache ? getCachedStates(props, cache) : getStates(props)
173168
let styles: IStyles = {}
174-
for (const breakpoint in values) {
175-
const style = getStyle(values[breakpoint])
169+
for (const value in values) {
170+
const style = getStyle(values[value])
176171
if (style === null) continue
177-
const media = medias[breakpoint]
178-
if (media === null) {
172+
const state = states[value]
173+
if (state === undefined) continue
174+
if (state === null) {
179175
styles = merge(styles, style)
180176
} else {
181-
styles[media] = styles[media] ? assign(styles[media], style) : style
177+
styles[state] = styles[state] ? assign(styles[state], style) : style
182178
}
183179
}
184180
return styles
@@ -205,32 +201,18 @@ function getStyleFactory(
205201
themeGet: ThemeGetter,
206202
): StyleGetter {
207203
return function getStyle(props: IProps) {
208-
const value = props[prop]
209-
if (!is(value)) return null
210-
const cache = getCacheNamespace(props.theme, prop)
211-
212-
if (obj(value)) {
213-
return reduceBreakpoints(
214-
props,
215-
value,
216-
(breakpointValue) =>
217-
styleFromValue(mixin, breakpointValue, props, themeGet, cache),
218-
cache,
219-
)
220-
}
204+
const fromValue = (value: any) => {
205+
if (!is(value)) return null
206+
const cache = getCacheNamespace(props.theme, prop)
221207

222-
return styleFromValue(mixin, value, props, themeGet, cache)
223-
}
224-
}
208+
if (obj(value)) {
209+
return reduceStates(props, value, fromValue, cache)
210+
}
225211

226-
function scopeStyleGetter(
227-
selector: string,
228-
getStyle: StyleGetter,
229-
): StyleGetter {
230-
return (props: IProps) => {
231-
const result = getStyle(props)
232-
if (result === null) return result
233-
return { [selector]: result }
212+
return styleFromValue(mixin, value, props, themeGet, cache)
213+
}
214+
215+
return fromValue(props[prop])
234216
}
235217
}
236218

@@ -289,9 +271,9 @@ export function compose(...generators: StyleGenerator[]): StyleGenerator {
289271

290272
if (!sort) return styles
291273

292-
const medias = getCachedMedias(
274+
const medias = getCachedStates(
293275
props,
294-
getCacheNamespace(props.theme, '__medias'),
276+
getCacheNamespace(props.theme, '__states'),
295277
)
296278
return sortStyles(styles, medias)
297279
}
@@ -329,7 +311,6 @@ export function style({
329311
key,
330312
transform,
331313
themeGet,
332-
states = defaultStates,
333314
}: {
334315
prop: string | string[]
335316
cssProperty?: CSSProperty
@@ -355,20 +336,7 @@ export function style({
355336

356337
themeGet = themeGet || themeGetter({ key, transform })
357338

358-
const capitalizedProp = prop.charAt(0).toUpperCase() + prop.slice(1)
359339
const generators: StyleGenerator[] = []
360-
const stateNames =
361-
states === defaultStates ? defaultStateKeys : Object.keys(states)
362-
for (let i = 0; i < stateNames.length; i++) {
363-
const stateName = stateNames[i]
364-
const stateProp = `${stateName}${capitalizedProp}`
365-
const getStyle = scopeStyleGetter(
366-
states[stateName],
367-
getStyleFactory(stateProp, mixin, themeGet),
368-
)
369-
const generator = createStyleGenerator(getStyle, [stateProp])
370-
generators.push(generator)
371-
}
372340
const getStyle = getStyleFactory(prop, mixin, themeGet)
373341
const generator = createStyleGenerator(getStyle, [prop])
374342
generators.push(generator)

packages/system/src/styles/animations.ts

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,6 @@ type AnimationProp<T extends ITheme> = SystemProp<
1717
>
1818
export interface AnimationProps<T extends ITheme = Theme> {
1919
animation?: AnimationProp<T>
20-
motionSafeAnimation?: AnimationProp<T>
21-
motionReduceAnimation?: AnimationProp<T>
22-
firstAnimation?: AnimationProp<T>
23-
lastAnimation?: AnimationProp<T>
24-
oddAnimation?: AnimationProp<T>
25-
evenAnimation?: AnimationProp<T>
26-
visitedAnimation?: AnimationProp<T>
27-
checkedAnimation?: AnimationProp<T>
28-
focusWithinAnimation?: AnimationProp<T>
29-
hoverAnimation?: AnimationProp<T>
30-
focusAnimation?: AnimationProp<T>
31-
focusVisibleAnimation?: AnimationProp<T>
32-
activeAnimation?: AnimationProp<T>
33-
disabledAnimation?: AnimationProp<T>
34-
placeholderAnimation?: AnimationProp<T>
3520
}
3621

3722
export const animation = style({

0 commit comments

Comments
 (0)