Skip to content

Commit

Permalink
Merge pull request #5 from facebook/better-theming-types
Browse files Browse the repository at this point in the history
Fix types for theming with tests
  • Loading branch information
nmn authored Nov 22, 2023
2 parents 0fb4fef + 7505285 commit 289cb7c
Show file tree
Hide file tree
Showing 3 changed files with 220 additions and 25 deletions.
112 changes: 112 additions & 0 deletions apps/nextjs-example/typetests/theming1.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
*/

/* eslint-disable no-unused-vars */

import stylex from '@stylexjs/stylex';
import type {
TokensFromVarGroup,
StyleXVar,
VarGroup,
Theme,
CompiledStyles,
} from '@stylexjs/stylex/lib/StyleXTypes';

const DARK = '@media (prefers-color-scheme: dark)';

const buttonTokens = stylex.defineVars({
bgColor: {
default: 'cyan',
[DARK]: 'navy',
},
textColor: {
default: 'black',
[DARK]: 'white',
},
cornerRadius: '4px',
paddingBlock: '4px',
paddingInline: '8px',
});

// DefineVars creates the right type.
buttonTokens satisfies VarGroup<
Readonly<{
bgColor: string;
textColor: string;
cornerRadius: string;
paddingBlock: string;
paddingInline: string;
}>,
symbol
>;
buttonTokens.bgColor satisfies StyleXVar<string>;

type TokensType = TokensFromVarGroup<typeof buttonTokens>;
({
bgColor: 'red',
textColor: 'white',
cornerRadius: '4px',
paddingBlock: '4px',
paddingInline: '8px',
}) satisfies TokensType;

({
bgColor: 'red',
textColor: 'white',
// @ts-expect-error - cornerRadius is a string.
cornerRadius: 4,
paddingBlock: '4px',
paddingInline: '8px',
}) satisfies TokensType;

({
bgColor: 'red',
textColor: 'white',
paddingBlock: '4px',
paddingInline: '8px',
// @ts-expect-error - cornerRadius is missing.
}) satisfies TokensType;

const correctTheme = stylex.createTheme(buttonTokens, {
bgColor: 'red',
textColor: 'white',
cornerRadius: '4px',
paddingBlock: '4px',
paddingInline: '8px',
});

correctTheme satisfies Theme<typeof buttonTokens, symbol>;

correctTheme satisfies CompiledStyles;

const result: string = stylex(correctTheme);
const result2: Readonly<{
className?: string;
style?: Readonly<{ [key: string]: string | number }>;
}> = stylex.props(correctTheme);

const wrongTheme1 = stylex.createTheme(buttonTokens, {
bgColor: 'red',
textColor: 'white',
// @ts-expect-error - cornerRadius is a string.
cornerRadius: 1,
paddingBlock: '4px',
paddingInline: '8px',
});

const wrongTheme2 = stylex.createTheme(
buttonTokens,
// @ts-expect-error - cornerRadius is missing.
{
bgColor: 'red',
textColor: 'white',
paddingBlock: '4px',
paddingInline: '8px',
},
);
55 changes: 30 additions & 25 deletions packages/stylex/src/StyleXTypes.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,13 @@ export type Stylex$Create = <
styles: S,
) => MapNamespaces<S>;

export type CompiledStyles = Readonly<{
[key: string]: StyleXClassName | null | void | never;
}>;
export type CompiledStyles =
| Readonly<{
[key: string]: StyleXClassName | null | void | never;
}>
| Readonly<{
theme: StyleXClassName;
}>;

declare const StyleXInlineStylesTag: unique symbol;

Expand Down Expand Up @@ -155,27 +159,29 @@ export type StyleXStylesWithout<CSS extends UserAuthoredStyles> = StyleXStyles<

declare const StyleXVarGroupTag: unique symbol;
export type VarGroup<
Tokens extends { [key: string]: unknown },
Tokens extends { [key: string]: any },
ID extends symbol = symbol,
> = Readonly<{
[Key in keyof Tokens]: Tokens[Key];
}> & {
$opaqueId: ID;
$tokens: Tokens;
} & typeof StyleXVarGroupTag;
[Key in keyof Tokens]: StyleXVar<Tokens[Key]>;
}> &
Readonly<{
$opaqueId: ID;
$tokens: Tokens;
}> &
typeof StyleXVarGroupTag;

export type TokensFromVarGroup<T extends VarGroup<TTokens>> = T['$tokens'];
export type TokensFromVarGroup<T extends VarGroup<unknown, unknown>> =
T['$tokens'];

export type IDFromVarGroup<T extends VarGroup<TTokens>> = T['$opaqueId'];
export type IDFromVarGroup<T extends VarGroup<unknown, unknown>> =
T['$opaqueId'];

type TTokens = {
[key: string]: string | { default: string; [key: AtRuleStr]: string };
};
type TTokens = Readonly<{
[key: string]: string | { [key: string]: string };
}>;

export type FlattenTokens<T extends TTokens> = Readonly<{
[Key in keyof T]: T[Key] extends { [key: string]: infer X }
? StyleXVar<X>
: StyleXVar<T[Key]>;
[Key in keyof T]: T[Key] extends { [key: string]: infer X } ? X : T[Key];
}>;

export type StyleX$DefineVars = <
Expand All @@ -186,24 +192,23 @@ export type StyleX$DefineVars = <
) => VarGroup<FlattenTokens<DefaultTokens>, ID>;

export type Theme<
T extends VarGroup<TTokens, symbol>,
// eslint-disable-next-line no-unused-vars
T extends VarGroup<unknown, symbol>,
Tag extends symbol = symbol,
> = Tag &
Readonly<{
theme: StyleXClassNameFor<string, IDFromVarGroup<T>>;
}>;

type OverridesForTokenType<Config extends { [key: string]: any }> = {
type OverridesForTokenType<Config extends { [key: string]: unknown }> = {
[Key in keyof Config]:
| Config[Key]
| { default: Config[Key]; [atRule: AtRuleStr]: Config[Key] };
};

export type StyleX$CreateTheme = <
BaseTokens extends VarGroup<any>,
ID extends symbol = symbol,
TVars extends VarGroup<unknown, unknown>,
ThemeID extends symbol = symbol,
>(
baseTokens: BaseTokens,
overrides: OverridesForTokenType<TokensFromVarGroup<BaseTokens>>,
) => Theme<BaseTokens, ID>;
baseTokens: TVars,
overrides: OverridesForTokenType<TokensFromVarGroup<TVars>>,
) => Theme<TVars, ThemeID>;
78 changes: 78 additions & 0 deletions packages/stylex/type-tests/theming1.js.flow
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
*/

/* eslint-disable no-unused-vars */

import stylex from '../src/stylex';
import type { StyleXVar, VarGroup, Theme } from '../src/StyleXTypes';

const DARK = '@media (prefers-color-scheme: dark)';

const buttonTokens = stylex.defineVars({
bgColor: {
default: 'cyan',
[DARK]: 'navy',
},
textColor: {
default: 'black',
[DARK]: 'white',
},
cornerRadius: '4px',
paddingBlock: '4px',
paddingInline: '8px',
});

// DefineVars creates the right type.
(buttonTokens: VarGroup<
$ReadOnly<{
bgColor: string,
textColor: string,
cornerRadius: string,
paddingBlock: string,
paddingInline: string,
}>,
string,
>);
(buttonTokens.bgColor: StyleXVar<string>);

const correctTheme = stylex.createTheme(buttonTokens, {
bgColor: 'red',
textColor: 'white',
cornerRadius: '4px',
paddingBlock: '4px',
paddingInline: '8px',
});

(correctTheme: Theme<typeof buttonTokens, string>);

const result: string = stylex(correctTheme);
const result2: $ReadOnly<{
className?: string,
style?: $ReadOnly<{ [string]: string | number }>,
}> = stylex.props(correctTheme);

const wrongTheme1 = stylex.createTheme(buttonTokens, {
bgColor: 'red',
textColor: 'white',
// $FlowExpectedError[incompatible-call] - cornerRadius must be a string
cornerRadius: 1,
paddingBlock: '4px',
paddingInline: '8px',
});

const wrongTheme2 = stylex.createTheme(
buttonTokens,
// $FlowExpectedError[prop-missing] - cornerRadius missing
{
bgColor: 'red',
textColor: 'white',
paddingBlock: '4px',
paddingInline: '8px',
},
);

0 comments on commit 289cb7c

Please sign in to comment.