/**
* Devup configuration file structure (devup.json)
diff --git a/packages/react/src/compat/stylex.d.ts b/packages/react/src/compat/stylex.d.ts
index 955c3c59f..f6e585b71 100644
--- a/packages/react/src/compat/stylex.d.ts
+++ b/packages/react/src/compat/stylex.d.ts
@@ -1,21 +1,39 @@
declare module '@stylexjs/stylex' {
import { stylex } from '@devup-ui/react'
- export {
- attrs,
- create,
- createTheme,
- createThemeContract,
- defineConsts,
- defineVars,
- firstThatWorks,
- include,
- keyframes,
- positionTry,
- props,
- types,
- viewTransitionClass,
- } from '@devup-ui/react/stylex'
+ export const attrs: typeof stylex.attrs
+ export const create: typeof stylex.create
+ export const createTheme: typeof stylex.createTheme
+ export const createThemeContract: typeof stylex.createThemeContract
+ export const defineConsts: typeof stylex.defineConsts
+ export const defineVars: typeof stylex.defineVars
+ export const firstThatWorks: typeof stylex.firstThatWorks
+ export const include: typeof stylex.include
+ export const keyframes: typeof stylex.keyframes
+ export const positionTry: typeof stylex.positionTry
+ export const props: typeof stylex.props
+ export const types: typeof stylex.types
+ export const viewTransitionClass: typeof stylex.viewTransitionClass
+
+ export type StyleXAttrs = stylex.StyleXAttrs
+ export type StyleXCompiledStyle = stylex.StyleXCompiledStyle
+ export type StyleXDeclarations = stylex.StyleXDeclarations
+ export type StyleXDynamicDeclarations = stylex.StyleXDynamicDeclarations
+ export type StyleXFlatDeclarations = stylex.StyleXFlatDeclarations
+ export type StyleXInclude
= stylex.StyleXInclude
+ export type StyleXInlineStyle = stylex.StyleXInlineStyle
+ export type StyleXProps = stylex.StyleXProps
+ export type StyleXRawStyles = stylex.StyleXRawStyles
+ export type StyleXStyleInput
= stylex.StyleXStyleInput
+ export type StyleXStyles
= stylex.StyleXStyles
+ export type StyleXThemeOverrides =
+ stylex.StyleXThemeOverrides
+ export type StyleXTypes = stylex.StyleXTypes
+ export type StyleXValue = stylex.StyleXValue
+ export type StyleXVariableValue =
+ stylex.StyleXVariableValue
+ export type StyleXVarGroup =
+ stylex.StyleXVarGroup
export default stylex
}
diff --git a/packages/react/src/types/props/css-properties.ts b/packages/react/src/types/props/css-properties.ts
index 3796749ba..9499ef0d4 100644
--- a/packages/react/src/types/props/css-properties.ts
+++ b/packages/react/src/types/props/css-properties.ts
@@ -4,6 +4,28 @@ import type { ResponsiveValue } from '../responsive-value'
type Responsive = { [K in keyof T]?: ResponsiveValue }
+type UnsupportedProperties =
+ | 'boxAlign'
+ | 'boxPack'
+ | 'boxFlex'
+ | 'boxFlexGroup'
+ | 'boxOrient'
+ | 'boxOrdinalGroup'
+ | 'boxDirection'
+ | 'boxLines'
+ | 'flexOrder'
+ | 'flexPositive'
+ | 'flexNegative'
+ | 'flexPreferredSize'
+ | 'scrollSnapCoordinate'
+ | 'scrollSnapDestination'
+ | 'scrollSnapPointsX'
+ | 'scrollSnapPointsY'
+ | 'scrollSnapTypeX'
+ | 'scrollSnapTypeY'
+
+export type SupportedCssProperties = Omit
+
// Keep an interface boundary so consumers can reuse cached type relationships.
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
-export interface DevupCssProperties extends Responsive {}
+export interface DevupCssProperties extends Responsive {}
diff --git a/packages/react/src/types/stylex-values.ts b/packages/react/src/types/stylex-values.ts
new file mode 100644
index 000000000..e260a8da3
--- /dev/null
+++ b/packages/react/src/types/stylex-values.ts
@@ -0,0 +1,95 @@
+import type { SupportedCssProperties } from './props/css-properties'
+export type StyleXScalar = string | number
+export type StyleXAtRule =
+ `@media ${string}` | `@supports ${string}` | `@container ${string}`
+
+/** Variable conditions may nest at-rules, but cannot depend on an element. */
+export type StyleXVariableValue =
+ | T
+ | null
+ | {
+ readonly default?: StyleXVariableValue
+ readonly [condition: StyleXAtRule]: StyleXVariableValue
+ }
+
+export type StyleXValue =
+ | T
+ | null
+ | {
+ readonly default?: StyleXValue
+ readonly [condition: StyleXAtRule | `:${string}`]: StyleXValue
+ }
+
+export type StyleXCheckedValue = V extends object
+ ? {
+ readonly [K in keyof V]: K extends C ? StyleXCheckedValue : never
+ }
+ : V
+
+export type StyleXCheckedVariableValue = StyleXCheckedValue<
+ V,
+ 'default' | StyleXAtRule
+>
+
+type CssProperties = SupportedCssProperties
+
+export type StyleXDeclarations = {
+ readonly [K in keyof CssProperties]?: StyleXValue
+} & {
+ readonly [property: `--${string}`]: StyleXValue
+}
+
+/** Dynamic namespace bodies and named declaration blocks are flat. */
+export type StyleXFlatDeclarations = {
+ readonly [K in keyof CssProperties]?: CssProperties[K]
+} & {
+ readonly [property: `--${string}`]: StyleXScalar
+}
+
+export type StyleXDynamicDeclarations = {
+ readonly [K in keyof CssProperties]?: CssProperties[K] | null
+} & {
+ readonly [property: `--${string}`]: StyleXScalar | null
+}
+
+export interface StyleXTypes {
+ angle>(
+ value: T & StyleXCheckedVariableValue,
+ ): T
+ color>(
+ value: T & StyleXCheckedVariableValue,
+ ): T
+ image>(
+ value: T & StyleXCheckedVariableValue,
+ ): T
+ integer>(
+ value: T & StyleXCheckedVariableValue,
+ ): T
+ length>(
+ value: T & StyleXCheckedVariableValue,
+ ): T
+ lengthPercentage>(
+ value: T & StyleXCheckedVariableValue,
+ ): T
+ number>(
+ value: T & StyleXCheckedVariableValue,
+ ): T
+ percentage>(
+ value: T & StyleXCheckedVariableValue,
+ ): T
+ resolution>(
+ value: T & StyleXCheckedVariableValue,
+ ): T
+ time>(
+ value: T & StyleXCheckedVariableValue,
+ ): T
+ transformFunction>(
+ value: T & StyleXCheckedVariableValue,
+ ): T
+ transformList>(
+ value: T & StyleXCheckedVariableValue,
+ ): T
+ url>(
+ value: T & StyleXCheckedVariableValue,
+ ): T
+}
diff --git a/packages/react/src/types/stylex.ts b/packages/react/src/types/stylex.ts
new file mode 100644
index 000000000..dc3e590f0
--- /dev/null
+++ b/packages/react/src/types/stylex.ts
@@ -0,0 +1,112 @@
+import type {
+ StyleXAtRule,
+ StyleXCheckedValue,
+ StyleXCheckedVariableValue,
+ StyleXDeclarations,
+ StyleXDynamicDeclarations,
+ StyleXVariableValue,
+} from './stylex-values'
+
+declare const compiledStyle: unique symbol
+declare const variableGroup: unique symbol
+declare const includedStyle: unique symbol
+
+/** Opaque build output, not a raw CSS object or a user-provided class string. */
+export type StyleXCompiledStyle = {
+ readonly [compiledStyle]: P
+}
+
+export type StyleXStyles
=
+ StyleXCompiledStyle
| false | null | undefined | readonly StyleXStyles
[]
+
+export type StyleXStyleInput
=
+ StyleXStyles
| string | readonly StyleXStyleInput
[]
+
+export type StyleXInclude
= {
+ readonly [includedStyle]: P
+}
+
+export type StyleXRawStyles = StyleXDeclarations & {
+ readonly [pseudo: `:${string}`]: StyleXDeclarations
+} & Partial
+
+export type StyleXNamespace =
+ StyleXRawStyles | null | ((...args: never[]) => StyleXDynamicDeclarations)
+
+type StyleLeaves = T extends object ? StyleLeaves : T
+type DeclaredStyleShape = {
+ readonly [
+ K in keyof S as K extends keyof StyleXDeclarations ? K : never
+ ]: StyleLeaves
+}
+
+type StyleShape =
+ S extends StyleXInclude
+ ? Omit> & DeclaredStyleShape
+ : DeclaredStyleShape
+
+type CheckedDeclarations = {
+ readonly [K in keyof S]: K extends keyof StyleXDeclarations
+ ? StyleXCheckedValue
+ : never
+}
+
+type CheckedRawStyles = {
+ readonly [K in keyof S]: K extends keyof StyleXDeclarations
+ ? StyleXCheckedValue
+ : K extends `:${string}`
+ ? CheckedDeclarations
+ : K extends typeof includedStyle
+ ? S[K]
+ : never
+}
+
+export type StyleXCompiledNamespaces = {
+ readonly [K in keyof S]: S[K] extends (...args: infer A) => infer R
+ ? (...args: A) => StyleXCompiledStyle>
+ : StyleXCompiledStyle>
+}
+
+export type StyleXCheckedNamespaces = {
+ readonly [K in keyof S]: S[K] extends null
+ ? null
+ : S[K] extends (...args: infer A) => infer R
+ ? (
+ ...args: A
+ ) => R &
+ Record, never>
+ : S[K] & CheckedRawStyles
+}
+
+export type StyleXVarGroup = {
+ readonly [variableGroup]: K
+} & Readonly>
+
+export type StyleXThemeOverrides = Readonly<
+ Partial>
+>
+
+export type StyleXCheckedVariables = {
+ readonly [K in keyof V]: StyleXCheckedVariableValue
+}
+
+export type StyleXConstants = {
+ readonly [K in keyof C]: C[K] extends string | number | boolean
+ ? `${C[K]}`
+ : never
+}
+
+export type StyleXInlineStyle = Readonly<
+ Record
+>
+
+export type StyleXProps = {
+ readonly className: string
+ readonly style?: StyleXInlineStyle
+}
+
+/** HTML attributes use style text; React props keep an inline style object. */
+export type StyleXAttrs = {
+ readonly class: string
+ readonly style?: string
+}
diff --git a/packages/react/src/utils/__tests__/stylex.test.ts b/packages/react/src/utils/__tests__/stylex.test.ts
index 6d5b4a953..4814939c5 100644
--- a/packages/react/src/utils/__tests__/stylex.test.ts
+++ b/packages/react/src/utils/__tests__/stylex.test.ts
@@ -67,9 +67,9 @@ describe('stylex', () => {
})
it('include should throw at runtime', () => {
- expect(() => include({ color: 'red' })).toThrowError(
- 'Cannot run on the runtime',
- )
+ expect(() =>
+ Reflect.apply(include, undefined, [{ color: 'red' }]),
+ ).toThrowError('Cannot run on the runtime')
})
it('defineVars should throw at runtime', () => {
@@ -80,7 +80,10 @@ describe('stylex', () => {
it('createTheme should throw at runtime', () => {
expect(() =>
- createTheme({ primary: '--x' }, { primary: 'blue' }),
+ Reflect.apply(createTheme, undefined, [
+ { primary: '--x' },
+ { primary: 'blue' },
+ ]),
).toThrowError('Cannot run on the runtime')
})
})
diff --git a/packages/react/src/utils/stylex.ts b/packages/react/src/utils/stylex.ts
index e0f051acd..3e8fedbd8 100644
--- a/packages/react/src/utils/stylex.ts
+++ b/packages/react/src/utils/stylex.ts
@@ -1,84 +1,118 @@
-type StyleValue = string | number | null | undefined
-
-type StyleProperties = Record>
-
-interface StyleXTypes {
- angle(value: T): T
- color(value: T): T
- image(value: T): T
- integer(value: T): T
- length(value: T): T
- lengthPercentage(value: T): T
- number(value: T): T
- percentage(value: T): T
- resolution(value: T): T
- time(value: T): T
- transformFunction(value: T): T
- transformList(value: T): T
- url(value: T): T
-}
-
-export function create>(
- _styles: S,
-): { readonly [K in keyof S]: S[K] } {
+import type { DevupCommonProps } from '../types/props'
+import type {
+ StyleXAttrs,
+ StyleXCheckedNamespaces,
+ StyleXCheckedVariables,
+ StyleXCompiledNamespaces,
+ StyleXCompiledStyle,
+ StyleXConstants,
+ StyleXInclude,
+ StyleXNamespace,
+ StyleXProps,
+ StyleXStyleInput,
+ StyleXThemeOverrides,
+ StyleXVarGroup,
+} from '../types/stylex'
+import type {
+ StyleXFlatDeclarations,
+ StyleXScalar,
+ StyleXTypes,
+ StyleXVariableValue,
+} from '../types/stylex-values'
+
+export type {
+ StyleXAttrs,
+ StyleXCompiledStyle,
+ StyleXInclude,
+ StyleXInlineStyle,
+ StyleXProps,
+ StyleXRawStyles,
+ StyleXStyleInput,
+ StyleXStyles,
+ StyleXThemeOverrides,
+ StyleXVarGroup,
+} from '../types/stylex'
+export type {
+ StyleXDeclarations,
+ StyleXDynamicDeclarations,
+ StyleXFlatDeclarations,
+ StyleXTypes,
+ StyleXValue,
+ StyleXVariableValue,
+} from '../types/stylex-values'
+
+export function create<
+ const S extends Readonly>,
+>(_styles: S & StyleXCheckedNamespaces): StyleXCompiledNamespaces {
throw new Error('Cannot run on the runtime')
}
-export function props(
- ..._styles: ReadonlyArray
-): { className?: string; style?: Record } {
+export function props(..._styles: readonly StyleXStyleInput[]): StyleXProps {
throw new Error('Cannot run on the runtime')
}
-export function attrs(
- ..._styles: ReadonlyArray
-): { class?: string; style?: string } {
+export function attrs(..._styles: readonly StyleXStyleInput[]): StyleXAttrs {
throw new Error('Cannot run on the runtime')
}
-export function keyframes(_frames: Record): string {
+export function keyframes(
+ _frames: Readonly>,
+): string {
throw new Error('Cannot run on the runtime')
}
-export function firstThatWorks(..._values: T[]): T {
+export function firstThatWorks(
+ ..._values: V
+): V[number] {
throw new Error('Cannot run on the runtime')
}
-export function include(_style: S): S {
+export function include(_style: StyleXCompiledStyle
): StyleXInclude
{
throw new Error('Cannot run on the runtime')
}
-export function defineVars>(
- _vars: V,
-): { readonly [K in keyof V]: string } {
+export function defineVars<
+ const V extends Readonly>,
+>(
+ _vars: V & StyleXCheckedVariables,
+): StyleXVarGroup> {
throw new Error('Cannot run on the runtime')
}
-export function createTheme>(
- _vars: V,
-
- _overrides: { readonly [K in keyof V]: StyleValue },
-): Record {
+export function createTheme<
+ K extends string,
+ const O extends Readonly>,
+>(
+ _vars: StyleXVarGroup,
+ _overrides: O &
+ StyleXCheckedVariables &
+ StyleXThemeOverrides> &
+ Record>, never>,
+): StyleXCompiledStyle {
throw new Error('Cannot run on the runtime')
}
-export function createThemeContract>(
- _vars: V,
-): { readonly [K in keyof V]: string } {
+export function createThemeContract<
+ const V extends Readonly>,
+>(_vars: V): StyleXVarGroup> {
throw new Error('Cannot run on the runtime')
}
-export function defineConsts>(
- _consts: V,
-): { readonly [K in keyof V]: V[K] } {
+export function defineConsts<
+ const V extends Readonly>,
+>(_consts: V): StyleXConstants {
throw new Error('Cannot run on the runtime')
}
-export function positionTry(_fallback: StyleProperties): string {
+export function positionTry(
+ _fallback: D & Record, never>,
+): string {
throw new Error('Cannot run on the runtime')
}
-export function viewTransitionClass(_styles: StyleProperties): string {
+export function viewTransitionClass(
+ _styles: D & Record, never>,
+): string {
throw new Error('Cannot run on the runtime')
}
diff --git a/packages/react/type-tests/dead-css-properties.ts b/packages/react/type-tests/dead-css-properties.ts
new file mode 100644
index 000000000..d6f0e817c
--- /dev/null
+++ b/packages/react/type-tests/dead-css-properties.ts
@@ -0,0 +1,41 @@
+import type { DevupProps } from '../src'
+import type { StyleXRawStyles } from '../src/utils/stylex'
+
+type Assert = T
+type DeadCssProperty =
+ | 'boxAlign'
+ | 'boxPack'
+ | 'boxFlex'
+ | 'boxFlexGroup'
+ | 'boxOrient'
+ | 'boxOrdinalGroup'
+ | 'boxDirection'
+ | 'boxLines'
+ | 'flexOrder'
+ | 'flexPositive'
+ | 'flexNegative'
+ | 'flexPreferredSize'
+ | 'scrollSnapCoordinate'
+ | 'scrollSnapDestination'
+ | 'scrollSnapPointsX'
+ | 'scrollSnapPointsY'
+ | 'scrollSnapTypeX'
+ | 'scrollSnapTypeY'
+
+export type DeadPropertiesAreRejected = Assert<
+ Extract extends never ? true : false
+>
+
+export type DeadStyleXPropertiesAreRejected = Assert<
+ Extract extends never ? true : false
+>
+
+export const compatibilityProperties = {
+ WebkitBoxAlign: 'center',
+ WebkitBoxFlex: 2,
+ WebkitBoxOrient: 'vertical',
+ strokeColor: 'red',
+ imeMode: 'active',
+ scrollSnapType: 'x mandatory',
+ _hover: { WebkitBoxFlex: 3 },
+} satisfies DevupProps
diff --git a/packages/react/type-tests/stylex.ts b/packages/react/type-tests/stylex.ts
new file mode 100644
index 000000000..48c751df6
--- /dev/null
+++ b/packages/react/type-tests/stylex.ts
@@ -0,0 +1,219 @@
+import stylex, {
+ create as namedCreate,
+ defineVars as namedDefineVars,
+ props as namedProps,
+ type StyleXCompiledStyle,
+ type StyleXDynamicDeclarations,
+ type StyleXFlatDeclarations,
+ type StyleXRawStyles,
+ type StyleXStyles,
+ type StyleXVarGroup,
+} from '@stylexjs/stylex'
+
+import * as direct from '../src/utils/stylex'
+
+type Assert = T
+type Equal =
+ (() => T extends A ? 1 : 2) extends () => T extends B ? 1 : 2
+ ? true
+ : false
+type Accepts = [A] extends [B] ? true : false
+type Rejects = Accepts extends false ? true : false
+type InvalidVariableCondition = {
+ readonly default: 'red'
+ readonly ':hover': 'blue'
+}
+type InvalidVariableGroup = { readonly group: { readonly color: 'red' } }
+type OfficialTransitionOptions = { readonly new: { readonly opacity: 0 } }
+type ContractInput = Parameters[0]
+type ConstantsInput = Parameters[0]
+type ExtraThemeInput = Parameters<
+ typeof direct.createTheme<'text', { missing: 'red' }>
+>[1]
+type InvalidConditionInput = Parameters<
+ typeof direct.defineVars<{ text: InvalidVariableCondition }>
+>[0]
+type InvalidGroupInput = Parameters<
+ typeof direct.defineVars
+>[0]
+type OfficialTransitionInput = Parameters<
+ typeof direct.viewTransitionClass
+>[0]
+
+const colors = namedDefineVars({
+ text: { default: 'black', '@media print': 'gray' },
+ accent: stylex.types.color({
+ default: 'blue',
+ '@supports (color: oklch(0 0 0))': {
+ default: 'oklch(0.6 0.2 250)',
+ '@container sidebar (min-width: 400px)': 'red',
+ },
+ }),
+ blank: null,
+})
+const contract = stylex.createThemeContract({
+ primary: null,
+ size: 'placeholder',
+})
+const partialTheme = stylex.createTheme(colors, { text: 'navy' })
+const conditionTheme = stylex.createTheme(contract, {
+ primary: { default: 'red', '@media print': null },
+})
+const emptyTheme = stylex.createTheme(colors, {})
+const _consts = stylex.defineConsts({
+ gap: 8,
+ flag: true,
+ color: 'red',
+ zero: -0,
+})
+const animation = stylex.keyframes({ from: { opacity: 0 }, to: { opacity: 1 } })
+const fallback = stylex.firstThatWorks(4, 'auto')
+const _emptyFallback = stylex.firstThatWorks()
+const base = namedCreate({
+ root: {
+ color: colors.text,
+ width: { default: fallback, ':hover': { '@media print': '50%' } },
+ ':focus': { outlineColor: 'red' },
+ animationName: animation,
+ '--local': 1,
+ },
+ empty: null,
+ dynamic: (height: number, color: string | null) => ({ height, color }),
+})
+const included = stylex.include(base.root)
+const styles = stylex.create({ root: { ...included, color: 'blue' } })
+const _inherited = stylex.create({ root: { ...included, opacity: 1 } })
+declare const enabled: boolean
+declare const external: StyleXStyles
+declare const externalClass: string
+const composition = [
+ base.root,
+ [enabled && styles.root, null, undefined, false],
+] as const
+export const reactProps = namedProps(
+ partialTheme,
+ conditionTheme,
+ emptyTheme,
+ base.empty,
+ base.dynamic(20, null),
+ composition,
+ external,
+ 'external',
+ [externalClass, ['nested-external']],
+)
+export const domAttrs = stylex.attrs(composition, base.dynamic(10, 'red'))
+export const tryName = stylex.positionTry({ top: 0, insetBlockEnd: 'auto' })
+export const transitionName = stylex.viewTransitionClass({
+ animationDuration: '1s',
+})
+export const scalarWrappers = [
+ stylex.types.angle('1deg'),
+ stylex.types.color('red'),
+ stylex.types.image('url(image.png)'),
+ stylex.types.integer(1),
+ stylex.types.length(2),
+ stylex.types.lengthPercentage('50%'),
+ stylex.types.number(0.5),
+ stylex.types.percentage('20%'),
+ stylex.types.resolution('2dppx'),
+ stylex.types.time('1s'),
+ stylex.types.transformFunction('scale(2)'),
+ stylex.types.transformList('scale(2) translateX(1px)'),
+ stylex.types.url('url(image.png)'),
+] as const
+export const conditionalWrappers = [
+ stylex.types.angle({ default: 1, '@media print': '0deg' }),
+ stylex.types.color({ default: 'red', '@media print': null }),
+ stylex.types.image({ default: 'none', '@media print': 'url(print.png)' }),
+ stylex.types.integer({ default: 1, '@media print': 2 }),
+ stylex.types.length({ default: 2, '@media print': '1px' }),
+ stylex.types.lengthPercentage({ default: 0, '@media print': '50%' }),
+ stylex.types.number({ default: 1, '@media print': 0 }),
+ stylex.types.percentage({ default: 0, '@media print': '20%' }),
+ stylex.types.resolution({ default: '1dppx', '@media print': '2dppx' }),
+ stylex.types.time({ default: '1s', '@media print': '0s' }),
+ stylex.types.transformFunction({ default: 'scale(2)', '@media print': null }),
+ stylex.types.transformList({ default: 'none', '@media print': null }),
+ stylex.types.url({ default: 'url(image.png)', '@media print': null }),
+] as const
+
+export type StyleXContractChecks = [
+ Assert>,
+ Assert>,
+ Assert>,
+ Assert>,
+ Assert>,
+ Assert>,
+ Assert<
+ Equal<
+ Parameters,
+ [height: number, color: string | null]
+ >
+ >,
+ Assert, StyleXCompiledStyle>>,
+ Assert>>,
+ Assert>>,
+ Assert>>,
+ Assert>,
+ Assert>>,
+ Assert>>,
+ Assert[number]>>,
+ Assert>,
+ Assert>,
+ Assert>,
+ Assert<
+ Accepts
+ >,
+ Assert[0]>>,
+ Assert>,
+ Assert>>,
+ Assert>>,
+ Assert>>,
+ Assert>,
+ Assert>,
+ Assert>,
+ Assert>,
+ Assert>,
+ Assert>,
+ Assert>,
+ Assert>,
+ Assert>,
+ Assert>,
+ Assert>,
+ Assert>,
+ Assert>,
+ Assert>,
+ Assert>,
+ Assert[0]>>,
+ Assert[0]>>,
+ Assert>,
+ Assert