({
384 | duration: "150ms",
385 | easing: "ease-in-out",
386 | });
387 |
388 | export class AnimationComposer extends Composer {
389 | property(property: P, steps: PropertyAnimationSteps) {
390 | const currentProperties = this.getConfig(config).properties ?? {};
391 |
392 | return this.updateConfig(config, { properties: { ...currentProperties, [property]: steps } });
393 | }
394 |
395 | duration(duration: Length) {
396 | return this.updateConfig(config, { duration: addUnit(duration, "ms") });
397 | }
398 |
399 | delay(delay: Length) {
400 | return this.addStyle({ animationDelay: addUnit(delay, "ms") });
401 | }
402 |
403 | get fadeIn() {
404 | return this.property("opacity", [0, 1]);
405 | }
406 |
407 | get fadeOut() {
408 | return this.property("opacity", [1, 0]);
409 | }
410 |
411 | slideUpFromBottom(by: Length) {
412 | return this.property("transform-y", [resolveMaybeBaseValue(by), "0"]);
413 | }
414 |
415 | slideDownFromTop(by: Length) {
416 | return this.property("transform-y", [`-${resolveMaybeBaseValue(by)}`, "0"]);
417 | }
418 |
419 | zoomIn(scale: number) {
420 | return this.property("transform-scale", [scale, 1]);
421 | }
422 |
423 | zoomOut(scale: number) {
424 | return this.property("transform-scale", [1, scale]);
425 | }
426 |
427 | easing(easing: Property.AnimationTimingFunction) {
428 | return this.updateConfig(config, { easing });
429 | }
430 |
431 | slideLeftFromRight(by: Length) {
432 | return this.property("transform-x", [`-${resolveMaybeBaseValue(by)}`, "0"]);
433 | }
434 |
435 | slideRightFromLeft(by: Length) {
436 | return this.property("transform-x", [resolveMaybeBaseValue(by), "0"]);
437 | }
438 |
439 | fillMode(mode: Property.AnimationFillMode) {
440 | return this.addStyle({ animationFillMode: mode });
441 | }
442 |
443 | iterationCount(count: Property.AnimationIterationCount) {
444 | return this.addStyle({ animationIterationCount: count });
445 | }
446 |
447 | rotate(angles: Length[]) {
448 | return this.property("transform-rotate", angles);
449 | }
450 |
451 | x(steps: Length[]) {
452 | return this.property("transform-x", resolveMaybeBaseValues(steps));
453 | }
454 |
455 | y(steps: Length[]) {
456 | return this.property("transform-y", resolveMaybeBaseValues(steps));
457 | }
458 |
459 | scale(steps: number[]) {
460 | return this.property("transform-scale", steps);
461 | }
462 |
463 | blur(steps: Length[]) {
464 | return this.property(
465 | "filter-blur",
466 | steps.map((s) => addUnit(s, "px")),
467 | );
468 | }
469 |
470 | opacity(steps: Length[]) {
471 | return this.property("opacity", steps);
472 | }
473 |
474 | get infinite() {
475 | return this.addStyle({ animationIterationCount: "infinite" });
476 | }
477 |
478 | get spin() {
479 | return this.rotate(["0deg", "360deg"]).infinite.easing("linear").duration("2s");
480 | }
481 |
482 | get transformStyle() {
483 | return "";
484 | }
485 |
486 | compile() {
487 | if (getHasValue(this.compileCache)) return this.compileCache;
488 |
489 | const currentConfig = this.getConfig(config);
490 |
491 | if (!currentConfig.properties) return super.compile();
492 |
493 | const variables = getPropertiesAnimationVariables(currentConfig.properties);
494 | const keyframesString = getAnimationKeyframesString(currentConfig.properties);
495 |
496 | // prettier-ignore
497 | const animation = keyframes`${keyframesString}`;
498 |
499 | const willChangeProperties = getWillChangeProperties(currentConfig.properties);
500 |
501 | // prettier-ignore
502 | const animationName = css`animation-name: ${animation};`;
503 |
504 | // const rule = simplifyRule(css`
505 | // animation-name: ${animation};
506 |
507 | // ${{
508 | // animationDuration: addUnit(currentConfig.duration, "ms"),
509 | // animationTimingFunction: currentConfig.easing,
510 | // willChange: willChangeProperties?.join(", ") ?? undefined,
511 | // ...variables,
512 | // }}
513 | // `);
514 |
515 | return super.compile([
516 | animationName,
517 | `animation-duration: ${addUnit(currentConfig.duration, "ms")};`,
518 | `animation-timing-function: ${currentConfig.easing};`,
519 | willChangeProperties ? `will-change: ${willChangeProperties.join(", ")};` : undefined,
520 | ...Object.entries(variables).map(([key, value]) => `${key}: ${value};`),
521 | ]);
522 | }
523 | }
524 |
525 | export const $animation = composer(AnimationComposer);
526 |
--------------------------------------------------------------------------------
/stylings/src/ColorComposer.ts:
--------------------------------------------------------------------------------
1 | import { Composer, composer } from "./Composer";
2 | import { blendColors, getColorBorderColor, getHighlightedColor, isColorDark, setColorOpacity } from "./utils/color";
3 |
4 | import { ComposerConfig } from "./ComposerConfig";
5 | import { getHasValue } from "./utils/maybeValue";
6 | import { isDefined } from "./utils";
7 | import { memoizeFn } from "./utils/memoize";
8 |
9 | interface ColorsInput {
10 | color: string;
11 | foreground?: string;
12 | hoverMultiplier?: number;
13 | border?: string;
14 | hover?: string;
15 | active?: string;
16 | }
17 |
18 | export interface ColorConfig extends ColorsInput {
19 | outputType?: "inline" | "background" | "color" | "border" | "outline" | "fill";
20 | isBackgroundWithBorder?: boolean;
21 | interactive?: boolean;
22 | }
23 |
24 | const config = new ComposerConfig({
25 | color: "#000000",
26 | });
27 |
28 | const hovers = [":hover", ".hover"];
29 | const svgActives = [":active", ".active"];
30 | const actives = [...svgActives, ":not(button):not(div):focus"];
31 |
32 | const HOVER_SELECTOR = hovers.map((hover) => `&${hover}`).join(", ");
33 | const ACTIVE_SELECTOR = actives.map((active) => `&${active}`).join(", ");
34 |
35 | function getColorForeground(config: ColorConfig) {
36 | if (isColorDark(config.color)) {
37 | return "#ffffff";
38 | }
39 |
40 | return "#000000";
41 | }
42 |
43 | function getColorHoverColor(config: ColorConfig) {
44 | const hoverMultiplier = config.hoverMultiplier ?? 1;
45 | if (isDefined(config.hover)) {
46 | return config.hover;
47 | }
48 |
49 | return blendColors(config.color, config.foreground ?? getColorForeground(config), 0.125 * hoverMultiplier);
50 | }
51 |
52 | function getColorActiveColor(config: ColorConfig) {
53 | const hoverMultiplier = config.hoverMultiplier ?? 1;
54 |
55 | if (isDefined(config.active)) {
56 | return config.active;
57 | }
58 |
59 | return blendColors(config.color, config.foreground ?? getColorForeground(config), 0.175 * hoverMultiplier);
60 | }
61 |
62 | function getColorStyles(config: ColorConfig) {
63 | if (!isDefined(config.outputType) || config.outputType === "inline") {
64 | return config.color;
65 | }
66 |
67 | const styles: string[] = [];
68 |
69 | switch (config.outputType) {
70 | case "background":
71 | styles.push(`background-color: ${config.color}; --background-color: ${config.color};`);
72 | if (config.foreground) {
73 | styles.push(`color: ${config.foreground}; --color: ${config.foreground};`);
74 | }
75 | break;
76 | case "color":
77 | styles.push(`color: ${config.color}; --color: ${config.color};`);
78 | break;
79 | case "border":
80 | styles.push(`border-color: ${config.color}; --border-color: ${config.color};`);
81 | break;
82 | case "outline":
83 | styles.push(`outline-color: ${config.color}; --outline-color: ${config.color};`);
84 | break;
85 | case "fill":
86 | styles.push(`fill: ${config.color}; --fill-color: ${config.color};`);
87 | if (config.foreground) {
88 | styles.push(`color: ${config.foreground}; --color: ${config.foreground};`);
89 | }
90 | break;
91 | }
92 |
93 | if (config.isBackgroundWithBorder) {
94 | styles.push(
95 | `border: 1px solid ${getColorBorderColor(config.color)}; --border-color: ${getColorBorderColor(config.color)};`,
96 | );
97 | }
98 |
99 | if (!config.interactive) {
100 | return styles.join(";");
101 | }
102 |
103 | styles.push(
104 | `${HOVER_SELECTOR} { ${getColorStyles({
105 | ...config,
106 | color: getColorHoverColor(config),
107 | interactive: false,
108 | })} }`,
109 | );
110 |
111 | styles.push(
112 | `${ACTIVE_SELECTOR} { ${getColorStyles({
113 | ...config,
114 | color: getColorActiveColor(config),
115 | interactive: false,
116 | })} }`,
117 | );
118 |
119 | return styles;
120 | }
121 |
122 | export class ColorComposer extends Composer {
123 | define(value: ColorsInput) {
124 | return this.updateConfig(config, value);
125 | }
126 |
127 | color(value: string) {
128 | return this.updateConfig(config, { color: value });
129 | }
130 |
131 | opacity(value: number) {
132 | const currentConfig = this.getConfig(config);
133 |
134 | return this.updateConfig(config, { color: setColorOpacity(currentConfig.color, value) });
135 | }
136 |
137 | get secondary() {
138 | return this.opacity(0.55);
139 | }
140 |
141 | get tertiary() {
142 | return this.opacity(0.3);
143 | }
144 |
145 | get transparent() {
146 | return this.opacity(0);
147 | }
148 |
149 | outputType(value: ColorConfig["outputType"]) {
150 | return this.updateConfig(config, { outputType: value });
151 | }
152 |
153 | get asBg() {
154 | return this.outputType("background");
155 | }
156 |
157 | get asColor() {
158 | return this.outputType("color");
159 | }
160 |
161 | get asBorder() {
162 | return this.outputType("border");
163 | }
164 |
165 | get asOutline() {
166 | return this.outputType("outline");
167 | }
168 |
169 | get asFill() {
170 | return this.outputType("fill");
171 | }
172 |
173 | get withBorder() {
174 | return this.updateConfig(config, { isBackgroundWithBorder: true });
175 | }
176 |
177 | get interactive() {
178 | return this.updateConfig(config, { interactive: true });
179 | }
180 |
181 | private changeColor(value: string) {
182 | const currentConfig = this.getConfig(config);
183 |
184 | return this.updateConfig(config, {
185 | color: value,
186 | border: currentConfig.border,
187 | foreground: currentConfig.foreground,
188 | });
189 | }
190 |
191 | get hover() {
192 | const currentConfig = this.getConfig(config);
193 |
194 | return this.changeColor(getHighlightedColor(currentConfig.color));
195 | }
196 |
197 | get active() {
198 | const currentConfig = this.getConfig(config);
199 |
200 | return this.changeColor(getHighlightedColor(currentConfig.color, 2));
201 | }
202 |
203 | get muted() {
204 | const currentConfig = this.getConfig(config);
205 |
206 | return this.changeColor(getHighlightedColor(currentConfig.color, 0.33));
207 | }
208 |
209 | highlight(ratio: number = 1) {
210 | const currentConfig = this.getConfig(config);
211 |
212 | return this.changeColor(getHighlightedColor(currentConfig.color, ratio));
213 | }
214 |
215 | get foreground() {
216 | const currentConfig = this.getConfig(config);
217 |
218 | return this.updateConfig(config, { color: getColorForeground(currentConfig) });
219 | }
220 |
221 | compile() {
222 | if (getHasValue(this.compileCache)) return this.compileCache;
223 |
224 | const currentConfig = this.getConfig(config);
225 |
226 | return super.compile(getColorStyles(currentConfig));
227 | }
228 | }
229 |
230 | export const $color = memoizeFn(
231 | function color(color: ColorsInput) {
232 | return composer(ColorComposer).define(color);
233 | },
234 | { mode: "hash" },
235 | );
236 |
--------------------------------------------------------------------------------
/stylings/src/CommonComposer.ts:
--------------------------------------------------------------------------------
1 | import { Composer, composer } from "./Composer";
2 |
3 | import { Length } from "./utils";
4 | import type { Properties } from "csstype";
5 | import { resolveMaybeBaseValue } from "./SizeComposer";
6 |
7 | export class CommonComposer extends Composer {
8 | get disabled() {
9 | return this.addStyle([`opacity: 0.5;`, `pointer-events: none;`]);
10 | }
11 |
12 | get round() {
13 | return this.addStyle(`border-radius: 1000px;`);
14 | }
15 |
16 | get secondary() {
17 | return this.addStyle(`opacity: 0.5;`);
18 | }
19 |
20 | get tertiary() {
21 | return this.addStyle(`opacity: 0.25;`);
22 | }
23 |
24 | get quaternary() {
25 | return this.addStyle(`opacity: 0.125;`);
26 | }
27 |
28 | get notAllowed() {
29 | return this.addStyle([`cursor: not-allowed;`, `opacity: 0.5;`]);
30 | }
31 |
32 | get fullWidth() {
33 | return this.addStyle(`width: 100%;`);
34 | }
35 |
36 | get fullHeight() {
37 | return this.addStyle(`height: 100%;`);
38 | }
39 |
40 | width(width: Length) {
41 | return this.addStyle(`width: ${resolveMaybeBaseValue(width)};`);
42 | }
43 |
44 | height(height: Length) {
45 | return this.addStyle(`height: ${resolveMaybeBaseValue(height)};`);
46 | }
47 |
48 | get circle() {
49 | return this.addStyle(`border-radius: 1000px;`);
50 | }
51 |
52 | size(size: Length) {
53 | return this.width(size).height(size);
54 | }
55 |
56 | z(z: number) {
57 | return this.addStyle(`z-index: ${z};`);
58 | }
59 |
60 | get widthFull() {
61 | return this.width("100%");
62 | }
63 |
64 | get heightFull() {
65 | return this.height("100%");
66 | }
67 |
68 | get relative() {
69 | return this.addStyle(`position: relative;`);
70 | }
71 |
72 | get absolute() {
73 | return this.addStyle(`position: absolute;`);
74 | }
75 |
76 | get fixed() {
77 | return this.addStyle(`position: fixed;`);
78 | }
79 |
80 | get notSelectable() {
81 | return this.addStyle(`user-select: none;`);
82 | }
83 |
84 | cursor(cursor: Properties["cursor"]) {
85 | return this.addStyle(`cursor: ${cursor};`);
86 | }
87 |
88 | pt(py: Length) {
89 | return this.addStyle(`padding-top: ${resolveMaybeBaseValue(py)};`);
90 | }
91 |
92 | pb(py: Length) {
93 | return this.addStyle(`padding-bottom: ${resolveMaybeBaseValue(py)};`);
94 | }
95 |
96 | py(px: Length) {
97 | return this.pt(px).pb(px);
98 | }
99 |
100 | pl(px: Length) {
101 | return this.addStyle(`padding-left: ${resolveMaybeBaseValue(px)};`);
102 | }
103 |
104 | pr(px: Length) {
105 | return this.addStyle(`padding-right: ${resolveMaybeBaseValue(px)};`);
106 | }
107 |
108 | px(px: Length) {
109 | return this.pl(px).pr(px);
110 | }
111 |
112 | p(px: Length) {
113 | return this.py(px).px(px);
114 | }
115 |
116 | mt(mt: Length) {
117 | return this.addStyle(`margin-top: ${resolveMaybeBaseValue(mt)};`);
118 | }
119 |
120 | mb(mb: Length) {
121 | return this.addStyle(`margin-bottom: ${resolveMaybeBaseValue(mb)};`);
122 | }
123 |
124 | my(my: Length) {
125 | return this.mt(my).mb(my);
126 | }
127 |
128 | ml(ml: Length) {
129 | return this.addStyle(`margin-left: ${resolveMaybeBaseValue(ml)};`);
130 | }
131 |
132 | mr(mr: Length) {
133 | return this.addStyle(`margin-right: ${resolveMaybeBaseValue(mr)};`);
134 | }
135 |
136 | mx(mx: Length) {
137 | return this.ml(mx).mr(mx);
138 | }
139 |
140 | m(mx: Length) {
141 | return this.my(mx).mx(mx);
142 | }
143 |
144 | left(left: Length) {
145 | return this.addStyle(`left: ${resolveMaybeBaseValue(left)};`);
146 | }
147 |
148 | right(right: Length) {
149 | return this.addStyle(`right: ${resolveMaybeBaseValue(right)};`);
150 | }
151 |
152 | top(top: Length) {
153 | return this.addStyle(`top: ${resolveMaybeBaseValue(top)};`);
154 | }
155 |
156 | bottom(bottom: Length) {
157 | return this.addStyle(`bottom: ${resolveMaybeBaseValue(bottom)};`);
158 | }
159 |
160 | inset(inset: Length) {
161 | return this.top(inset).right(inset).bottom(inset).left(inset);
162 | }
163 |
164 | aspectRatio(ratio: number) {
165 | return this.addStyle(`aspect-ratio: ${ratio};`);
166 | }
167 |
168 | get square() {
169 | return this.aspectRatio(1);
170 | }
171 |
172 | maxWidth(maxWidth: Length) {
173 | return this.addStyle(`max-width: ${resolveMaybeBaseValue(maxWidth)};`);
174 | }
175 |
176 | maxHeight(maxHeight: Length) {
177 | return this.addStyle(`max-height: ${resolveMaybeBaseValue(maxHeight)};`);
178 | }
179 |
180 | minWidth(minWidth: Length) {
181 | return this.addStyle(`min-width: ${resolveMaybeBaseValue(minWidth)};`);
182 | }
183 |
184 | minHeight(minHeight: Length) {
185 | return this.addStyle(`min-height: ${resolveMaybeBaseValue(minHeight)};`);
186 | }
187 |
188 | x(x: Length) {
189 | return this.addStyle(`transform: translateX(${resolveMaybeBaseValue(x)});`);
190 | }
191 |
192 | y(y: Length) {
193 | return this.addStyle(`transform: translateY(${resolveMaybeBaseValue(y)});`);
194 | }
195 |
196 | get overflowHidden() {
197 | return this.addStyle(`overflow: hidden;`);
198 | }
199 |
200 | lineClamp(lines: number) {
201 | return this.addStyle({
202 | overflow: "hidden",
203 | display: "-webkit-box",
204 | WebkitBoxOrient: "vertical",
205 | WebkitLineClamp: lines,
206 | });
207 | }
208 | }
209 |
210 | export const $common = composer(CommonComposer);
211 |
--------------------------------------------------------------------------------
/stylings/src/Composer.ts:
--------------------------------------------------------------------------------
1 | import { CSSProperties, RuleSet } from "styled-components";
2 | import { getHasValue, maybeValue } from "./utils/maybeValue";
3 |
4 | import { ComposerConfig } from "./ComposerConfig";
5 | import { DeepMap } from "./utils/map/DeepMap";
6 | import { HashMap } from "./utils/map/HashMap";
7 | import { MaybeUndefined } from "./utils/nullish";
8 | import { compileComposerStyles } from "./compilation";
9 | import { getObjectId } from "./utils/objectId";
10 | import { isPrimitive } from "./utils/primitive";
11 | import { memoizeFn } from "./utils/memoize";
12 |
13 | const IS_COMPOSER = Symbol("isComposer");
14 |
15 | export interface GetStylesProps {
16 | theme?: ThemeOrThemeProps;
17 | }
18 | export type ThemeOrThemeProps = GetStylesProps | object;
19 |
20 | export type GetStyles = (propsOrTheme?: ThemeOrThemeProps) => CompileResult;
21 | export type ComposerStyle = CSSProperties | string | Composer | RuleSet | Array;
22 |
23 | export type StyledComposer = T extends GetStyles ? T : T & GetStyles;
24 | export type AnyStyledComposer = StyledComposer;
25 |
26 | export type PickComposer = T extends StyledComposer ? U : never;
27 |
28 | export type CompileResult = string | string[] | RuleSet | null | Array;
29 |
30 | function getIsStyledComposer(value: C): value is StyledComposer {
31 | return typeof value === "function";
32 | }
33 |
34 | type ConstructorOf = new (...args: any[]) => T;
35 |
36 | interface HolderFunction {
37 | (): void;
38 | rawComposer: Composer;
39 | }
40 |
41 | const composerHolderFunctionProxyHandler: ProxyHandler = {
42 | get(holderFunction, prop) {
43 | if (prop === "rawComposer") {
44 | return holderFunction.rawComposer;
45 | }
46 |
47 | return holderFunction.rawComposer[prop as keyof Composer];
48 | },
49 | set(holderFunction, prop, value) {
50 | // @ts-expect-error
51 | holderFunction.rawComposer[prop as keyof Composer] = value;
52 |
53 | return true;
54 | },
55 | apply(holderFunction) {
56 | return holderFunction.rawComposer.compile();
57 | },
58 | getPrototypeOf(target) {
59 | return Object.getPrototypeOf(target.rawComposer);
60 | },
61 | deleteProperty(target, prop) {
62 | return Reflect.deleteProperty(target.rawComposer, prop);
63 | },
64 | has(target, prop) {
65 | return Reflect.has(target.rawComposer, prop);
66 | },
67 | ownKeys(target) {
68 | return Reflect.ownKeys(target.rawComposer);
69 | },
70 | getOwnPropertyDescriptor(target, prop) {
71 | return Reflect.getOwnPropertyDescriptor(target.rawComposer, prop);
72 | },
73 | setPrototypeOf(target, proto) {
74 | return Reflect.setPrototypeOf(target.rawComposer, proto);
75 | },
76 | isExtensible(target) {
77 | return Reflect.isExtensible(target.rawComposer);
78 | },
79 | preventExtensions(target) {
80 | return Reflect.preventExtensions(target.rawComposer);
81 | },
82 | };
83 |
84 | export function getIsComposer(input: unknown): input is Composer {
85 | if (isPrimitive(input)) return false;
86 |
87 | return IS_COMPOSER in (input as object);
88 | }
89 |
90 | export function pickComposer(input: C): C {
91 | if (getIsStyledComposer(input)) {
92 | return input.rawComposer as C;
93 | }
94 |
95 | if (getIsComposer(input)) {
96 | return input;
97 | }
98 |
99 | throw new Error("Invalid composer");
100 | }
101 |
102 | let isCreatingWithComposerFunction = false;
103 |
104 | const warnAboutCreatingInstanceDirectly = memoizeFn((constructor: typeof Composer) => {
105 | const name = constructor.name;
106 | console.warn(
107 | `Seems you are creating ${name} composer using "const instance = new ${name}()". Use "const instance = composer(${name})" instead.`,
108 | );
109 | });
110 |
111 | const EMPTY_CONFIGS = new Map();
112 | const EMPTY_STYLES: ComposerStyle[] = [];
113 |
114 | function createCompilerCallProxy(composer: C) {
115 | if (typeof composer === "function") {
116 | return composer;
117 | }
118 | const compileStyles: HolderFunction = () => {};
119 |
120 | compileStyles.rawComposer = composer;
121 |
122 | return new Proxy(compileStyles, composerHolderFunctionProxyHandler) as StyledComposer;
123 | }
124 |
125 | export class Composer {
126 | readonly styles: ComposerStyle[] = EMPTY_STYLES;
127 | readonly configs: Map = EMPTY_CONFIGS;
128 |
129 | readonly [IS_COMPOSER] = true;
130 |
131 | constructor() {
132 | if (!isCreatingWithComposerFunction) {
133 | warnAboutCreatingInstanceDirectly(this.constructor as typeof Composer);
134 | }
135 | }
136 |
137 | get rawComposer() {
138 | return this;
139 | }
140 |
141 | clone(
142 | this: T,
143 | styles: ComposerStyle[],
144 | configs: Map,
145 | ): StyledComposer {
146 | try {
147 | isCreatingWithComposerFunction = true;
148 |
149 | const newComposer = new (this.constructor as ConstructorOf)() as StyledComposer;
150 |
151 | // @ts-expect-error
152 | newComposer.styles = styles;
153 | // @ts-expect-error
154 | newComposer.configs = configs;
155 |
156 | return newComposer;
157 | } finally {
158 | isCreatingWithComposerFunction = false;
159 | }
160 | }
161 |
162 | private updateConfigCache = new DeepMap(HashMap);
163 |
164 | updateConfig(this: T, config: ComposerConfig, changes: Partial): StyledComposer {
165 | const key = [config, changes];
166 |
167 | let clone = this.updateConfigCache.get(key) as StyledComposer | undefined;
168 |
169 | if (clone) {
170 | return clone;
171 | }
172 |
173 | const existingConfig = this.getConfig(config);
174 |
175 | if (!existingConfig) {
176 | clone = this.setConfig(config, { ...config.defaultConfig, ...changes });
177 | } else {
178 | clone = this.setConfig(config, { ...existingConfig, ...changes });
179 | }
180 |
181 | clone = createCompilerCallProxy(clone);
182 |
183 | this.updateConfigCache.set(key, clone);
184 |
185 | return clone;
186 | }
187 |
188 | private setConfig(this: T, config: ComposerConfig, value: C): StyledComposer {
189 | const configs = new Map(this.configs);
190 |
191 | configs.set(config, value);
192 |
193 | return this.clone(this.styles, configs);
194 | }
195 |
196 | getConfig(config: ComposerConfig): C {
197 | const existingConfig = this.configs?.get(config) as MaybeUndefined;
198 |
199 | return existingConfig ?? config.defaultConfig;
200 | }
201 |
202 | private reuseStyleMap = new HashMap>();
203 |
204 | addStyle(this: T, style: ComposerStyle): StyledComposer {
205 | let clone = this.reuseStyleMap.get(style) as StyledComposer | undefined;
206 |
207 | if (clone) {
208 | return clone;
209 | }
210 |
211 | clone = createCompilerCallProxy(this.clone([...this.styles, style], this.configs));
212 |
213 | this.reuseStyleMap.set(style, clone);
214 |
215 | return clone;
216 | }
217 |
218 | init() {
219 | return this;
220 | }
221 |
222 | protected compileCache = maybeValue();
223 |
224 | compile(addedStyles?: ComposerStyle): CompileResult {
225 | if (getHasValue(this.compileCache)) {
226 | return this.compileCache;
227 | }
228 |
229 | this.compileCache = compileComposerStyles(addedStyles ? [...this.styles, addedStyles] : this.styles);
230 |
231 | return this.compileCache;
232 | }
233 | }
234 |
235 | export function composer(Composer: ConstructorOf): StyledComposer {
236 | try {
237 | isCreatingWithComposerFunction = true;
238 | const composer = new Composer();
239 |
240 | return createCompilerCallProxy(composer.init());
241 | } finally {
242 | isCreatingWithComposerFunction = false;
243 | }
244 | }
245 |
--------------------------------------------------------------------------------
/stylings/src/ComposerConfig.ts:
--------------------------------------------------------------------------------
1 | import { JSONObject } from "./utils/json";
2 |
3 | interface ComposerConfigOptions {
4 | cache?: boolean;
5 | }
6 |
7 | export class ComposerConfig {
8 | constructor(
9 | readonly defaultConfig: T,
10 | readonly options: ComposerConfigOptions = { cache: true },
11 | ) {}
12 | }
13 |
14 | export function composerConfig(defaultConfig: T) {
15 | return new ComposerConfig(defaultConfig);
16 | }
17 |
--------------------------------------------------------------------------------
/stylings/src/FlexComposer.ts:
--------------------------------------------------------------------------------
1 | import { Composer, composer } from "./Composer";
2 |
3 | import { CSSProperties } from "styled-components";
4 | import { convertToRem } from "./utils/convertUnits";
5 |
6 | export class FlexComposer extends Composer {
7 | init() {
8 | return this.addStyle(`display: flex;`);
9 | }
10 |
11 | direction(value: CSSProperties["flexDirection"]) {
12 | return this.addStyle(`flex-direction: ${value};`);
13 | }
14 |
15 | get row() {
16 | return this.addStyle(`flex-direction: row;`);
17 | }
18 |
19 | get column() {
20 | return this.addStyle(`flex-direction: column;`);
21 | }
22 |
23 | /**
24 | * @alias row
25 | */
26 | get horizontal() {
27 | return this.row;
28 | }
29 |
30 | /**
31 | * @alias column
32 | */
33 | get vertical() {
34 | return this.column;
35 | }
36 |
37 | /**
38 | * @alias row
39 | */
40 | get x() {
41 | return this.row;
42 | }
43 |
44 | /**
45 | * @alias column
46 | */
47 | get y() {
48 | return this.column;
49 | }
50 |
51 | gap(value: number | string = 1) {
52 | if (typeof value === "string") {
53 | return this.addStyle(`gap: ${value};`);
54 | }
55 |
56 | return this.addStyle(`gap: ${convertToRem(value, "level")}rem;`);
57 | }
58 |
59 | align(value: CSSProperties["alignItems"]) {
60 | return this.addStyle(`align-items: ${value};`);
61 | }
62 |
63 | get alignCenter() {
64 | return this.addStyle(`align-items: center;`);
65 | }
66 |
67 | get alignStart() {
68 | return this.addStyle(`align-items: flex-start;`);
69 | }
70 |
71 | get alignEnd() {
72 | return this.addStyle(`align-items: flex-end;`);
73 | }
74 |
75 | get alignStretch() {
76 | return this.addStyle(`align-items: stretch;`);
77 | }
78 |
79 | get alignBaseline() {
80 | return this.addStyle(`align-items: baseline;`);
81 | }
82 |
83 | justify(value: CSSProperties["justifyContent"]) {
84 | return this.addStyle(`justify-content: ${value};`);
85 | }
86 |
87 | get justifyCenter() {
88 | return this.addStyle(`justify-content: center;`);
89 | }
90 |
91 | get justifyStart() {
92 | return this.addStyle(`justify-content: flex-start;`);
93 | }
94 |
95 | get justifyEnd() {
96 | return this.addStyle(`justify-content: flex-end;`);
97 | }
98 |
99 | get justifyBetween() {
100 | return this.addStyle(`justify-content: space-between;`);
101 | }
102 |
103 | get justifyAround() {
104 | return this.addStyle(`justify-content: space-around;`);
105 | }
106 |
107 | get justifyEvenly() {
108 | return this.addStyle(`justify-content: space-evenly;`);
109 | }
110 |
111 | get center() {
112 | return this.alignCenter.justifyCenter;
113 | }
114 |
115 | get reverse() {
116 | return this.addStyle(`flex-direction: row-reverse;`);
117 | }
118 |
119 | get wrap() {
120 | return this.addStyle(`flex-wrap: wrap;`);
121 | }
122 |
123 | get inline() {
124 | return this.addStyle(`display: inline-flex;`);
125 | }
126 | }
127 |
128 | export const $flex = composer(FlexComposer);
129 |
--------------------------------------------------------------------------------
/stylings/src/FontComposer.ts:
--------------------------------------------------------------------------------
1 | import { Composer, composer } from "./Composer";
2 | import { Length, addUnit } from "./utils";
3 |
4 | import { Properties } from "csstype";
5 |
6 | export class FontComposer extends Composer {
7 | family(value: Properties["fontFamily"]) {
8 | return this.addStyle(`font-family: ${value};`);
9 | }
10 |
11 | size(value: Length) {
12 | return this.addStyle(`font-size: ${addUnit(value, "em")};`);
13 | }
14 |
15 | weight(value: Properties["fontWeight"]) {
16 | return this.addStyle(`font-weight: ${value};`);
17 | }
18 |
19 | lineHeight(value: Length) {
20 | return this.addStyle(`line-height: ${addUnit(value, "em")};`);
21 | }
22 |
23 | get copyLineHeight() {
24 | return this.lineHeight(1.5);
25 | }
26 |
27 | get headingLineHeight() {
28 | return this.lineHeight(1.25);
29 | }
30 |
31 | get balance() {
32 | return this.addStyle(`text-wrap: balance;`);
33 | }
34 |
35 | get uppercase() {
36 | return this.addStyle(`text-transform: uppercase;`);
37 | }
38 |
39 | get lowercase() {
40 | return this.addStyle(`text-transform: lowercase;`);
41 | }
42 |
43 | get capitalize() {
44 | return this.addStyle(`text-transform: capitalize;`);
45 | }
46 |
47 | get underline() {
48 | return this.addStyle(`text-decoration: underline;`);
49 | }
50 |
51 | get left() {
52 | return this.addStyle(`text-align: left;`);
53 | }
54 |
55 | get center() {
56 | return this.addStyle(`text-align: center;`);
57 | }
58 |
59 | get right() {
60 | return this.addStyle(`text-align: right;`);
61 | }
62 |
63 | get ellipsis() {
64 | return this.addStyle(`text-overflow: ellipsis; white-space: nowrap; overflow: hidden;`);
65 | }
66 |
67 | get resetLineHeight() {
68 | return this.lineHeight(1);
69 | }
70 |
71 | maxLines(value: number) {
72 | return this.addStyle({
73 | WebkitLineClamp: value,
74 | WebkitBoxOrient: "vertical",
75 | overflow: "hidden",
76 | display: "-webkit-box",
77 | });
78 | }
79 |
80 | opacity(value: number) {
81 | return this.addStyle(`opacity: ${value};`);
82 | }
83 |
84 | get secondary() {
85 | return this.opacity(0.5);
86 | }
87 |
88 | get tertiary() {
89 | return this.opacity(0.3);
90 | }
91 |
92 | get w100() {
93 | return this.weight(100);
94 | }
95 |
96 | get w200() {
97 | return this.weight(200);
98 | }
99 |
100 | get w300() {
101 | return this.weight(300);
102 | }
103 |
104 | get w400() {
105 | return this.weight(400);
106 | }
107 |
108 | get w500() {
109 | return this.weight(500);
110 | }
111 |
112 | get normal() {
113 | return this.weight(400);
114 | }
115 |
116 | get w600() {
117 | return this.weight(600);
118 | }
119 |
120 | letterSpacing(value: Length) {
121 | return this.addStyle(`letter-spacing: ${addUnit(value, "em")};`);
122 | }
123 |
124 | get w700() {
125 | return this.weight(700);
126 | }
127 |
128 | get w800() {
129 | return this.weight(800);
130 | }
131 |
132 | get w900() {
133 | return this.weight(900);
134 | }
135 |
136 | get nowrap() {
137 | return this.addStyle(`white-space: nowrap;`);
138 | }
139 |
140 | get antialiased() {
141 | return this.addStyle(`-webkit-font-smoothing: antialiased;`);
142 | }
143 | }
144 |
145 | export const $font = composer(FontComposer);
146 |
--------------------------------------------------------------------------------
/stylings/src/GridComposer.ts:
--------------------------------------------------------------------------------
1 | import { Composer, composer } from "./Composer";
2 |
3 | import { CSSProperties } from "styled-components";
4 | import type { Property } from "csstype";
5 | import { convertToRem } from "./utils/convertUnits";
6 |
7 | export class GridComposer extends Composer {
8 | init() {
9 | return this.addStyle(`display: grid;`);
10 | }
11 |
12 | columns(value: CSSProperties["gridTemplateColumns"]) {
13 | return this.addStyle(`grid-template-columns: ${value};`);
14 | }
15 |
16 | rows(value: CSSProperties["gridTemplateRows"]) {
17 | return this.addStyle(`grid-template-rows: ${value};`);
18 | }
19 |
20 | gap(value: number = 1) {
21 | return this.addStyle(`gap: ${convertToRem(value, "level")}rem;`);
22 | }
23 |
24 | alignItems(value: Property.AlignItems) {
25 | return this.addStyle(`align-items: ${value};`);
26 | }
27 |
28 | get alignItemsCenter() {
29 | return this.alignItems("center");
30 | }
31 |
32 | get alignItemsStart() {
33 | return this.alignItems("start");
34 | }
35 |
36 | get alignItemsEnd() {
37 | return this.alignItems("end");
38 | }
39 |
40 | get alignItemsStretch() {
41 | return this.alignItems("stretch");
42 | }
43 |
44 | justifyItems(value: Property.JustifyItems) {
45 | return this.addStyle(`justify-items: ${value};`);
46 | }
47 |
48 | get justifyItemsCenter() {
49 | return this.justifyItems("center");
50 | }
51 |
52 | get justifyItemsStart() {
53 | return this.justifyItems("start");
54 | }
55 |
56 | get justifyItemsEnd() {
57 | return this.justifyItems("end");
58 | }
59 |
60 | get justifyItemsStretch() {
61 | return this.justifyItems("stretch");
62 | }
63 |
64 | alignContent(value: Property.AlignContent) {
65 | return this.addStyle(`align-content: ${value};`);
66 | }
67 |
68 | get alignContentCenter() {
69 | return this.alignContent("center");
70 | }
71 |
72 | get alignContentStart() {
73 | return this.alignContent("start");
74 | }
75 |
76 | get alignContentEnd() {
77 | return this.alignContent("end");
78 | }
79 |
80 | get alignContentStretch() {
81 | return this.alignContent("stretch");
82 | }
83 |
84 | get alignContentBetween() {
85 | return this.alignContent("space-between");
86 | }
87 |
88 | get alignContentAround() {
89 | return this.alignContent("space-around");
90 | }
91 |
92 | get alignContentEvenly() {
93 | return this.alignContent("space-evenly");
94 | }
95 |
96 | justifyContent(value: Property.JustifyContent) {
97 | return this.addStyle(`justify-content: ${value};`);
98 | }
99 |
100 | get justifyContentCenter() {
101 | return this.justifyContent("center");
102 | }
103 |
104 | get justifyContentStart() {
105 | return this.justifyContent("start");
106 | }
107 |
108 | get justifyContentEnd() {
109 | return this.justifyContent("end");
110 | }
111 |
112 | get justifyContentStretch() {
113 | return this.justifyContent("stretch");
114 | }
115 |
116 | get justifyContentBetween() {
117 | return this.justifyContent("space-between");
118 | }
119 |
120 | get justifyContentAround() {
121 | return this.justifyContent("space-around");
122 | }
123 |
124 | get justifyContentEvenly() {
125 | return this.justifyContent("space-evenly");
126 | }
127 |
128 | autoFlow(value: Property.GridAutoFlow) {
129 | return this.addStyle(`grid-auto-flow: ${value};`);
130 | }
131 |
132 | get flowRow() {
133 | return this.autoFlow("row");
134 | }
135 |
136 | get flowColumn() {
137 | return this.autoFlow("column");
138 | }
139 |
140 | get flowRowDense() {
141 | return this.autoFlow("row dense");
142 | }
143 |
144 | get flowColumnDense() {
145 | return this.autoFlow("column dense");
146 | }
147 |
148 | get center() {
149 | return this.alignItemsCenter.justifyItemsCenter;
150 | }
151 |
152 | get inline() {
153 | return this.addStyle(`display: inline-grid;`);
154 | }
155 | }
156 |
157 | export const $grid = composer(GridComposer);
158 |
--------------------------------------------------------------------------------
/stylings/src/ShadowComposer.ts:
--------------------------------------------------------------------------------
1 | import { Composer, composer } from "./Composer";
2 |
3 | import { ComposerConfig } from "./ComposerConfig";
4 | import { getHasValue } from "./utils/maybeValue";
5 | import { setColorOpacity } from "./utils/color";
6 |
7 | interface ShadowConfig {
8 | x?: number;
9 | y?: number;
10 | blur?: number;
11 | color?: string;
12 | inset?: boolean;
13 | spread?: number;
14 | }
15 |
16 | const shadowConfig = new ComposerConfig({});
17 |
18 | export class ShadowComposer extends Composer {
19 | x(value: number) {
20 | return this.updateConfig(shadowConfig, { x: value });
21 | }
22 |
23 | y(value: number) {
24 | return this.updateConfig(shadowConfig, { y: value });
25 | }
26 |
27 | blur(value: number) {
28 | return this.updateConfig(shadowConfig, { blur: value });
29 | }
30 |
31 | color(value: string) {
32 | return this.updateConfig(shadowConfig, { color: value });
33 | }
34 |
35 | inset(value: boolean) {
36 | return this.updateConfig(shadowConfig, { inset: value });
37 | }
38 |
39 | spread(value: number) {
40 | return this.updateConfig(shadowConfig, { spread: value });
41 | }
42 |
43 | opacity(value: number) {
44 | const color = this.getConfig(shadowConfig).color;
45 |
46 | if (!color) {
47 | console.warn("To set shadow opacity, you must first set a color");
48 | return this;
49 | }
50 |
51 | return this.updateConfig(shadowConfig, { color: setColorOpacity(color, value) });
52 | }
53 |
54 | compile() {
55 | if (getHasValue(this.compileCache)) return this.compileCache;
56 |
57 | const { x, y, blur, color, inset, spread } = this.getConfig(shadowConfig);
58 |
59 | const shadowStyle = `box-shadow: ${x}px ${y}px ${blur}px ${spread}px ${color} ${inset ? "inset" : ""};`;
60 |
61 | return super.compile(shadowStyle);
62 | }
63 | }
64 |
65 | export const $shadow = composer(ShadowComposer);
66 |
--------------------------------------------------------------------------------
/stylings/src/SizeComposer.ts:
--------------------------------------------------------------------------------
1 | import { Composer, composer } from "./Composer";
2 |
3 | import { ComposerConfig } from "./ComposerConfig";
4 | import { Length } from "./utils";
5 | import { convertToRem } from "./utils/convertUnits";
6 |
7 | type SizeOutputTarget =
8 | | "width"
9 | | "height"
10 | | "margin-x"
11 | | "margin-y"
12 | | "margin-top"
13 | | "margin-bottom"
14 | | "margin-left"
15 | | "margin-right"
16 | | "margin"
17 | | "padding-x"
18 | | "padding-y"
19 | | "padding-top"
20 | | "padding-bottom"
21 | | "padding-left"
22 | | "padding-right"
23 | | "padding"
24 | | "gap"
25 | | "min-width"
26 | | "min-height"
27 | | "max-width"
28 | | "max-height"
29 | | "transform-x"
30 | | "transform-y";
31 |
32 | interface StyledSizeConfig {
33 | targets: SizeOutputTarget[] | "inline";
34 | value: Length;
35 | }
36 |
37 | const config = new ComposerConfig({
38 | targets: "inline",
39 | value: 0,
40 | });
41 |
42 | export function resolveMaybeBaseValue(value: Length) {
43 | if (typeof value === "number") {
44 | return `${value / 4}rem`;
45 | }
46 |
47 | return value;
48 | }
49 |
50 | export function resolveMaybeBaseValues(values: Length[]) {
51 | return values.map(resolveMaybeBaseValue);
52 | }
53 |
54 | export function resolveSizeValue(value: Length) {
55 | if (typeof value === "number") {
56 | return `${value}rem`;
57 | }
58 |
59 | return value;
60 | }
61 |
62 | export function resolveSizeValues(values: Length[]) {
63 | return values.map(resolveSizeValue);
64 | }
65 |
66 | export class SizeComposer extends Composer {
67 | private get resolvedSize() {
68 | return resolveSizeValue(this.getConfig(config).value);
69 | }
70 |
71 | private setValue(value: Length) {
72 | return this.updateConfig(config, { value });
73 | }
74 |
75 | base(value: number) {
76 | return this.setValue(convertToRem(value, "base") + "rem");
77 | }
78 |
79 | rem(value: number) {
80 | return this.setValue(`${value}rem`);
81 | }
82 |
83 | level(level: number) {
84 | return this.setValue(convertToRem(level, "level") + "rem");
85 | }
86 |
87 | px(value: number) {
88 | return this.setValue(`${value}px`);
89 | }
90 |
91 | em(value: number) {
92 | return this.setValue(`${value}em`);
93 | }
94 |
95 | get width() {
96 | return this.addStyle(`width: ${this.resolvedSize};`);
97 | }
98 |
99 | get height() {
100 | return this.addStyle(`height: ${this.resolvedSize};`);
101 | }
102 |
103 | get marginX() {
104 | return this.addStyle(`margin-left: ${this.resolvedSize}; margin-right: ${this.resolvedSize};`);
105 | }
106 |
107 | get marginY() {
108 | return this.addStyle(`margin-top: ${this.resolvedSize}; margin-bottom: ${this.resolvedSize};`);
109 | }
110 |
111 | get marginTop() {
112 | return this.addStyle(`margin-top: ${this.resolvedSize};`);
113 | }
114 |
115 | get marginBottom() {
116 | return this.addStyle(`margin-bottom: ${this.resolvedSize};`);
117 | }
118 |
119 | get marginLeft() {
120 | return this.addStyle(`margin-left: ${this.resolvedSize};`);
121 | }
122 |
123 | get marginRight() {
124 | return this.addStyle(`margin-right: ${this.resolvedSize};`);
125 | }
126 |
127 | get margin() {
128 | return this.addStyle([
129 | `margin-top: ${this.resolvedSize};`,
130 | `margin-right: ${this.resolvedSize};`,
131 | `margin-bottom: ${this.resolvedSize};`,
132 | `margin-left: ${this.resolvedSize};`,
133 | ]);
134 | }
135 |
136 | get paddingX() {
137 | return this.addStyle(`padding-left: ${this.resolvedSize}; padding-right: ${this.resolvedSize};`);
138 | }
139 |
140 | get paddingY() {
141 | return this.addStyle(`padding-top: ${this.resolvedSize}; padding-bottom: ${this.resolvedSize};`);
142 | }
143 |
144 | get paddingTop() {
145 | return this.addStyle(`padding-top: ${this.resolvedSize};`);
146 | }
147 |
148 | get paddingBottom() {
149 | return this.addStyle(`padding-bottom: ${this.resolvedSize};`);
150 | }
151 |
152 | get paddingLeft() {
153 | return this.addStyle(`padding-left: ${this.resolvedSize};`);
154 | }
155 |
156 | get paddingRight() {
157 | return this.addStyle(`padding-right: ${this.resolvedSize};`);
158 | }
159 |
160 | get padding() {
161 | return this.addStyle([
162 | `padding-top: ${this.resolvedSize};`,
163 | `padding-right: ${this.resolvedSize};`,
164 | `padding-bottom: ${this.resolvedSize};`,
165 | `padding-left: ${this.resolvedSize};`,
166 | ]);
167 | }
168 |
169 | get gap() {
170 | return this.addStyle(`gap: ${this.resolvedSize};`);
171 | }
172 |
173 | get size() {
174 | return this.width.height;
175 | }
176 |
177 | get minSize() {
178 | return this.minWidth.minHeight;
179 | }
180 |
181 | get maxSize() {
182 | return this.maxWidth.maxHeight;
183 | }
184 |
185 | get minWidth() {
186 | return this.addStyle(`min-width: ${this.resolvedSize};`);
187 | }
188 |
189 | get maxWidth() {
190 | return this.addStyle(`max-width: ${this.resolvedSize};`);
191 | }
192 |
193 | get minHeight() {
194 | return this.addStyle(`min-height: ${this.resolvedSize};`);
195 | }
196 |
197 | get maxHeight() {
198 | return this.addStyle(`max-height: ${this.resolvedSize};`);
199 | }
200 |
201 | get transformX() {
202 | return this.addStyle(`transform: translateX(${this.resolvedSize});`);
203 | }
204 |
205 | get transformY() {
206 | return this.addStyle(`transform: translateY(${this.resolvedSize});`);
207 | }
208 |
209 | get transformXY() {
210 | const resolvedSize = this.resolvedSize;
211 |
212 | return this.addStyle(`transform: translate(${resolvedSize}, ${resolvedSize});`);
213 | }
214 | }
215 |
216 | export const $size = composer(SizeComposer);
217 |
--------------------------------------------------------------------------------
/stylings/src/SurfaceComposer.ts:
--------------------------------------------------------------------------------
1 | import { FlexComposer } from "./FlexComposer";
2 | import { composer } from "./Composer";
3 | import { composerConfig } from "./ComposerConfig";
4 | import { memoizeFn } from "./utils/memoize";
5 | import { resolveSizeValue } from "./SizeComposer";
6 |
7 | interface SizingBoxConfig {
8 | paddingX?: number;
9 | paddingY?: number;
10 | radius?: number;
11 | height?: number;
12 | width?: number;
13 | }
14 |
15 | const config = composerConfig({});
16 |
17 | export class FrameComposer extends FlexComposer {
18 | getStyles() {
19 | return null;
20 | }
21 |
22 | define(value: SizingBoxConfig) {
23 | return this.updateConfig(config, value as Partial);
24 | }
25 |
26 | get padding() {
27 | const { paddingX = 0, paddingY = 0 } = this.getConfig(config);
28 |
29 | return this.addStyle({
30 | paddingLeft: resolveSizeValue(paddingX),
31 | paddingRight: resolveSizeValue(paddingX),
32 | paddingTop: resolveSizeValue(paddingY),
33 | paddingBottom: resolveSizeValue(paddingY),
34 | });
35 | }
36 |
37 | get paddingX() {
38 | const { paddingX = 0 } = this.getConfig(config);
39 |
40 | return this.addStyle({
41 | paddingLeft: resolveSizeValue(paddingX),
42 | paddingRight: resolveSizeValue(paddingX),
43 | });
44 | }
45 |
46 | get paddingY() {
47 | const { paddingY = 0 } = this.getConfig(config);
48 |
49 | return this.addStyle({
50 | paddingTop: resolveSizeValue(paddingY),
51 | paddingBottom: resolveSizeValue(paddingY),
52 | });
53 | }
54 |
55 | get radius() {
56 | const { radius = 0 } = this.getConfig(config);
57 |
58 | return this.addStyle({ borderRadius: resolveSizeValue(radius) });
59 | }
60 |
61 | get height() {
62 | const { height = 0 } = this.getConfig(config);
63 |
64 | return this.addStyle({ height: resolveSizeValue(height) });
65 | }
66 |
67 | get width() {
68 | const { width = 0 } = this.getConfig(config);
69 |
70 | return this.addStyle({ width: resolveSizeValue(width) });
71 | }
72 |
73 | get circle() {
74 | return this.define({ radius: 1000 });
75 | }
76 |
77 | get size() {
78 | const { height = 0, width = 0 } = this.getConfig(config);
79 |
80 | return this.addStyle({ height: resolveSizeValue(height), width: resolveSizeValue(width) });
81 | }
82 |
83 | get noPT() {
84 | return this.addStyle({ paddingTop: 0 });
85 | }
86 |
87 | get noPB() {
88 | return this.addStyle({ paddingBottom: 0 });
89 | }
90 |
91 | get noPL() {
92 | return this.addStyle({ paddingLeft: 0 });
93 | }
94 |
95 | get noPR() {
96 | return this.addStyle({ paddingRight: 0 });
97 | }
98 | }
99 |
100 | const $frameBase = composer(FrameComposer);
101 |
102 | export const $frame = memoizeFn(
103 | function frame(config: SizingBoxConfig) {
104 | return $frameBase.define(config);
105 | },
106 | { mode: "hash" },
107 | );
108 |
--------------------------------------------------------------------------------
/stylings/src/ThemeProvider.ts:
--------------------------------------------------------------------------------
1 | import { MaybeFalsy, isNotFalsy } from "./utils/nullish";
2 | import { ReactNode, createElement, useMemo } from "react";
3 | import { Theme, ThemeInput, ThemeVariant, composeThemeVariants } from "./theme";
4 |
5 | import { ThemeProvider as StyledThemeProvider } from "styled-components";
6 | import { useSameArray } from "./utils/hooks";
7 |
8 | interface ThemeProviderProps {
9 | theme: Theme;
10 | activeVariants?: Array>>;
11 | children: ReactNode;
12 | }
13 |
14 | export function ThemeProvider(props: ThemeProviderProps) {
15 | const { theme, activeVariants } = props;
16 |
17 | let presentActiveVariants = activeVariants?.filter(isNotFalsy) ?? [];
18 |
19 | presentActiveVariants = useSameArray(presentActiveVariants);
20 |
21 | const resolvedTheme = useMemo(() => {
22 | return composeThemeVariants(theme, presentActiveVariants);
23 | }, [theme, presentActiveVariants]);
24 |
25 | return createElement(
26 | StyledThemeProvider,
27 | {
28 | theme: resolvedTheme,
29 | },
30 | props.children,
31 | );
32 | }
33 |
--------------------------------------------------------------------------------
/stylings/src/ThemedValue.ts:
--------------------------------------------------------------------------------
1 | import { CompileResult, Composer, ThemeOrThemeProps, getIsComposer } from "./Composer";
2 | import { Primitive, isPrimitive } from "./utils/primitive";
3 | import { ThemeInput, ThemeOrVariant, getIsThemeOrVariant, getThemeValueByPath } from "./theme";
4 |
5 | import { HashMap } from "./utils/map/HashMap";
6 |
7 | interface ThemedComposerHolder {
8 | (propsOrTheme?: unknown): CompileResult;
9 | repeater: ComposerRepeater;
10 | }
11 |
12 | type RepeatStep =
13 | | {
14 | type: "get";
15 | property: string;
16 | propertyType: "getter" | "method";
17 | }
18 | | {
19 | type: "apply";
20 | args: unknown[];
21 | };
22 |
23 | function repeatStepsOnComposer(composer: C, steps: RepeatStep[]): C {
24 | let currentResult: unknown = composer;
25 | let currentComposer = currentResult;
26 |
27 | for (const step of steps) {
28 | if (!currentComposer) {
29 | throw new Error("Composer is not defined");
30 | }
31 | if (step.type === "get") {
32 | currentResult = (currentResult as Composer)[step.property as keyof Composer];
33 |
34 | if (step.propertyType === "getter") {
35 | currentResult = currentComposer = (currentResult as Composer).rawComposer;
36 | }
37 | } else if (step.type === "apply") {
38 | currentResult = (currentResult as Function).apply(currentComposer, step.args);
39 | currentResult = currentComposer = (currentResult as Composer).rawComposer;
40 | }
41 | }
42 |
43 | return currentResult as C;
44 | }
45 |
46 | interface AnalyzedPrototype {
47 | getters: Set;
48 | methods: Set;
49 | }
50 |
51 | function getPrototypeInfo(prototype: object): AnalyzedPrototype {
52 | const result: AnalyzedPrototype = {
53 | getters: new Set(),
54 | methods: new Set(),
55 | };
56 |
57 | while (prototype) {
58 | if (!prototype || prototype === Object.prototype) {
59 | break;
60 | }
61 |
62 | const descriptors = Object.getOwnPropertyDescriptors(prototype);
63 |
64 | for (const key in descriptors) {
65 | if (key === "constructor") continue;
66 |
67 | const descriptor = descriptors[key];
68 |
69 | if (descriptor.get) {
70 | result.getters.add(key);
71 | } else if (typeof descriptor.value === "function") {
72 | result.methods.add(key);
73 | }
74 | }
75 |
76 | prototype = Object.getPrototypeOf(prototype);
77 | }
78 |
79 | return result;
80 | }
81 |
82 | const IS_THEMED_COMPOSER = Symbol("isThemedComposer");
83 |
84 | const themedComposerHolderProxyHandler: ProxyHandler> = {
85 | get(holder, prop) {
86 | const prototypeInfo = holder.repeater.info.prototypeInfo;
87 |
88 | if (prototypeInfo.methods.has(prop as string)) {
89 | return holder.repeater.addStep({ type: "get", property: prop as string, propertyType: "method" });
90 | }
91 |
92 | if (prototypeInfo.getters.has(prop as string)) {
93 | return holder.repeater.addStep({ type: "get", property: prop as string, propertyType: "getter" });
94 | }
95 |
96 | return holder.repeater.info.themeDefaultComposer[prop as keyof Composer];
97 | },
98 | apply(target, _thisArg, argArray) {
99 | if (!target.repeater.canCompile) {
100 | return target.repeater.addStep({ type: "apply", args: argArray });
101 | }
102 |
103 | return target.repeater.compileForProps(argArray[0]);
104 | },
105 | has(target, prop) {
106 | if (prop === IS_THEMED_COMPOSER) {
107 | return true;
108 | }
109 |
110 | return Reflect.has(target.repeater.info.themeDefaultComposer, prop);
111 | },
112 | set() {
113 | throw new Error("Cannot set a property on a themed composer");
114 | },
115 | };
116 |
117 | function getThemeFromCallArg(propsOrTheme?: ThemeOrThemeProps): ThemeOrVariant | null {
118 | if (!propsOrTheme) {
119 | return null;
120 | }
121 |
122 | if (getIsThemeOrVariant(propsOrTheme)) {
123 | return propsOrTheme as ThemeOrVariant;
124 | }
125 |
126 | if (!("theme" in propsOrTheme)) return null;
127 |
128 | const maybeTheme = propsOrTheme.theme;
129 |
130 | if (maybeTheme === undefined) {
131 | return null;
132 | }
133 |
134 | if (getIsThemeOrVariant(maybeTheme)) {
135 | return maybeTheme as ThemeOrVariant;
136 | }
137 |
138 | throw new Error("There is some value provided as theme in props, but it is has unknown type");
139 | }
140 |
141 | export function getIsThemedComposer(value: unknown): value is Composer {
142 | if (isPrimitive(value)) return false;
143 |
144 | return IS_THEMED_COMPOSER in (value as object);
145 | }
146 |
147 | function createRepeaterProxy(repeater: ComposerRepeater) {
148 | const getThemedValue: ThemedComposerHolder = () => null;
149 | getThemedValue.repeater = repeater;
150 |
151 | return new Proxy(getThemedValue, themedComposerHolderProxyHandler) as unknown as C;
152 | }
153 |
154 | function createRepeaterRoot(defaultComposer: C, path: string) {
155 | const repeater = new ComposerRepeater(
156 | {
157 | themeDefaultComposer: defaultComposer,
158 | path,
159 | prototypeInfo: getPrototypeInfo(defaultComposer),
160 | },
161 | [],
162 | );
163 |
164 | return createRepeaterProxy(repeater);
165 | }
166 |
167 | interface ThemeComposerInfo {
168 | themeDefaultComposer: C;
169 | path: string;
170 | prototypeInfo: AnalyzedPrototype;
171 | }
172 |
173 | class ComposerRepeater {
174 | private addStepCache = new HashMap();
175 | private compileForComposerCache = new WeakMap();
176 |
177 | constructor(
178 | readonly info: ThemeComposerInfo,
179 | readonly steps: RepeatStep[],
180 | ) {}
181 |
182 | get canCompile(): boolean {
183 | const lastStep = this.steps.at(-1);
184 |
185 | if (!lastStep) return true;
186 |
187 | if (lastStep.type === "apply") return true;
188 |
189 | return lastStep.propertyType === "getter" ? true : false;
190 | }
191 |
192 | addStep(step: RepeatStep): C {
193 | let childComposer = this.addStepCache.get(step);
194 |
195 | if (childComposer) {
196 | return childComposer;
197 | }
198 |
199 | childComposer = createRepeaterProxy(new ComposerRepeater(this.info, [...this.steps, step]));
200 |
201 | this.addStepCache.set(step, childComposer);
202 |
203 | return childComposer;
204 | }
205 |
206 | private getComposerFromCallArg(propsOrTheme?: ThemeOrThemeProps): C {
207 | const theme = getThemeFromCallArg(propsOrTheme);
208 |
209 | if (!theme) {
210 | return this.info.themeDefaultComposer;
211 | }
212 |
213 | if (!getIsThemeOrVariant(theme)) {
214 | throw new Error("Theme is not composable");
215 | }
216 |
217 | const maybeComposer = getThemeValueByPath(theme, this.info.path);
218 |
219 | if (!maybeComposer) {
220 | return this.info.themeDefaultComposer;
221 | }
222 |
223 | if (!getIsComposer(maybeComposer)) {
224 | throw new Error("Theme value is not a composer");
225 | }
226 |
227 | return maybeComposer as C;
228 | }
229 |
230 | compileForComposer(sourceComposer: C): CompileResult {
231 | let result = this.compileForComposerCache.get(sourceComposer);
232 |
233 | if (result !== undefined) {
234 | return result;
235 | }
236 |
237 | const finalComposer = repeatStepsOnComposer(sourceComposer, this.steps);
238 |
239 | if (!finalComposer) {
240 | throw new Error("Failed to get theme value.");
241 | }
242 |
243 | result = finalComposer.compile();
244 |
245 | this.compileForComposerCache.set(sourceComposer, result);
246 |
247 | return result;
248 | }
249 |
250 | compileForProps(props: ThemeOrThemeProps): CompileResult {
251 | let composer = this.getComposerFromCallArg(props);
252 |
253 | return this.compileForComposer(composer);
254 | }
255 | }
256 |
257 | export type ThemedValueGetter = (props?: ThemeOrThemeProps) => V;
258 |
259 | function createThemedValueGetter(path: string, defaultValue: T): ThemedValueGetter {
260 | return function getThemedValue(props?: ThemeOrThemeProps) {
261 | const theme = getThemeFromCallArg(props);
262 |
263 | if (!theme) {
264 | return defaultValue;
265 | }
266 |
267 | const themeValue = getThemeValueByPath(theme, path);
268 |
269 | if (themeValue === undefined) {
270 | return defaultValue;
271 | }
272 |
273 | return themeValue as T;
274 | };
275 | }
276 |
277 | export type ThemedValueInput = Primitive | Composer;
278 |
279 | export type ThemedValue = V extends Primitive ? ThemedValueGetter : V;
280 |
281 | export function createThemedValue(path: string, defaultValue: V): ThemedValue {
282 | if (getIsComposer(defaultValue)) {
283 | return createRepeaterRoot(defaultValue, path) as ThemedValue;
284 | }
285 |
286 | return createThemedValueGetter(path, defaultValue) as ThemedValue;
287 | }
288 |
--------------------------------------------------------------------------------
/stylings/src/TransitionComposer.ts:
--------------------------------------------------------------------------------
1 | import { Composer, composer } from "./Composer";
2 | import { DEFAULT_TRANSITION_DURATION_MS, DEFAULT_TRANSITION_EASING } from "./defaults";
3 | import { Length, addUnit, multiplyUnit } from "./utils";
4 |
5 | import type { CSSProperty } from "./types";
6 | import { ComposerConfig } from "./ComposerConfig";
7 | import { Property } from "csstype";
8 | import { getHasValue } from "./utils/maybeValue";
9 |
10 | interface StyledTransitionConfig {
11 | easing: string;
12 | duration: Length;
13 | properties: CSSProperty[] | "all";
14 | slowRelease: boolean;
15 | }
16 |
17 | const config = new ComposerConfig({
18 | easing: DEFAULT_TRANSITION_EASING,
19 | duration: DEFAULT_TRANSITION_DURATION_MS,
20 | properties: ["all"],
21 | slowRelease: false,
22 | });
23 |
24 | export class TransitionComposer extends Composer {
25 | easing(easing: Property.TransitionTimingFunction) {
26 | return this.updateConfig(config, { easing });
27 | }
28 |
29 | duration(duration: Length) {
30 | return this.updateConfig(config, { duration });
31 | }
32 |
33 | get slowRelease() {
34 | return this.updateConfig(config, { slowRelease: true });
35 | }
36 |
37 | get all() {
38 | return this.property("all");
39 | }
40 |
41 | get colors() {
42 | return this.property("color", "background-color", "border-color", "text-decoration-color", "fill", "stroke");
43 | }
44 |
45 | get common() {
46 | return this.property(
47 | "color",
48 | "background-color",
49 | "border-color",
50 | "text-decoration-color",
51 | "fill",
52 | "stroke",
53 | "opacity",
54 | "box-shadow",
55 | "transform",
56 | "filter",
57 | "backdrop-filter",
58 | );
59 | }
60 |
61 | property(...properties: CSSProperty[]) {
62 | return this.updateConfig(config, { properties });
63 | }
64 |
65 | compile() {
66 | if (getHasValue(this.compileCache)) return this.compileCache;
67 |
68 | const { easing, duration, properties, slowRelease } = this.getConfig(config);
69 | const propertiesString = properties === "all" ? "all" : properties.join(", ");
70 |
71 | const styles = [
72 | `transition-property: ${propertiesString};`,
73 | `transition-timing-function: ${easing};`,
74 | `transition-duration: ${multiplyUnit(duration, 3, "ms")};`,
75 | ];
76 |
77 | if (slowRelease) {
78 | styles.push(`
79 | &:hover {
80 | transition-duration: ${addUnit(duration, "ms")};
81 | }
82 | `);
83 | }
84 |
85 | return super.compile(styles);
86 | }
87 | }
88 |
89 | export const $transition = composer(TransitionComposer);
90 |
--------------------------------------------------------------------------------
/stylings/src/UI.tsx:
--------------------------------------------------------------------------------
1 | import { ComponentProps, createElement, FunctionComponent, HTMLAttributes, ReactNode, type JSX } from "react";
2 |
3 | import styled from "styled-components";
4 | import { resolveStylesInput, StylesInput } from "./input";
5 | import { type AnyStyledComposer } from "./Composer";
6 | import { useElementDebugUIId } from "./utils/debug";
7 | import { useInnerForwardRef, useSameArray } from "./utils/hooks";
8 | import { memoizeFn } from "./utils/memoize";
9 | import { registerStylesComponent } from "./utils/registry";
10 |
11 | type IntrinsicElementName = keyof JSX.IntrinsicElements;
12 |
13 | interface StylesExtraProps {
14 | as?: IntrinsicElementName;
15 | styles?: StylesInput;
16 | }
17 |
18 | type SWIntrinsicProps = ComponentProps & StylesExtraProps;
19 |
20 | type SWIntrinsicComponent = (props: SWIntrinsicProps) => ReactNode;
21 |
22 | type InferSWComponentFromKey = T extends `${string}_${infer T}`
23 | ? T extends IntrinsicElementName
24 | ? SWIntrinsicComponent
25 | : never
26 | : never;
27 |
28 | type CustomStylesComponents = {
29 | [P in `${string}_${IntrinsicElementName}`]: InferSWComponentFromKey;
30 | } & Record>;
31 |
32 | const createStylesComponent = memoizeFn(function createStylesComponent(
33 | intrinsicComponentType: T,
34 | customName?: string,
35 | ): SWIntrinsicComponent {
36 | function StylesComponent({ styles, as: asType = intrinsicComponentType, ref, ...props }: SWIntrinsicProps) {
37 | const innerRef = useInnerForwardRef(ref);
38 |
39 | const stylesList = useSameArray(resolveStylesInput(styles));
40 |
41 | useElementDebugUIId(innerRef, customName);
42 |
43 | return createElement(SW, {
44 | // Make it always first in the inspector
45 | "data-ui": customName,
46 | as: asType,
47 | ref: innerRef,
48 | $styles: stylesList,
49 | ...(props as HTMLAttributes),
50 | }) as ReactNode;
51 | }
52 |
53 | StylesComponent.displayName = `StylesComponent${intrinsicComponentType}`;
54 |
55 | registerStylesComponent(StylesComponent as FunctionComponent);
56 |
57 | return StylesComponent;
58 | });
59 |
60 | const createStylingsComponentFromCustomName = memoizeFn(function createStylingsComponentFromCustomName(
61 | customName: string,
62 | ) {
63 | if (!customName.includes("_")) return createStylesComponent("div", customName);
64 |
65 | const [componentName, intrinsicElement] = customName.split("_");
66 |
67 | if (!intrinsicElement) return createStylesComponent("div", customName);
68 |
69 | if (!getIsIntrinsicElementName(intrinsicElement)) return createStylesComponent("div", customName);
70 |
71 | return createStylesComponent(intrinsicElement, componentName);
72 | });
73 |
74 | const stylingsBuiltInComponents = {
75 | a: createStylesComponent("a"),
76 | abbr: createStylesComponent("abbr"),
77 | address: createStylesComponent("address"),
78 | area: createStylesComponent("area"),
79 | article: createStylesComponent("article"),
80 | aside: createStylesComponent("aside"),
81 | audio: createStylesComponent("audio"),
82 | b: createStylesComponent("b"),
83 | base: createStylesComponent("base"),
84 | bdi: createStylesComponent("bdi"),
85 | bdo: createStylesComponent("bdo"),
86 | blockquote: createStylesComponent("blockquote"),
87 | body: createStylesComponent("body"),
88 | br: createStylesComponent("br"),
89 | button: createStylesComponent("button"),
90 | canvas: createStylesComponent("canvas"),
91 | caption: createStylesComponent("caption"),
92 | cite: createStylesComponent("cite"),
93 | code: createStylesComponent("code"),
94 | col: createStylesComponent("col"),
95 | colgroup: createStylesComponent("colgroup"),
96 | data: createStylesComponent("data"),
97 | datalist: createStylesComponent("datalist"),
98 | dd: createStylesComponent("dd"),
99 | del: createStylesComponent("del"),
100 | details: createStylesComponent("details"),
101 | dfn: createStylesComponent("dfn"),
102 | dialog: createStylesComponent("dialog"),
103 | div: createStylesComponent("div"),
104 | dl: createStylesComponent("dl"),
105 | dt: createStylesComponent("dt"),
106 | em: createStylesComponent("em"),
107 | embed: createStylesComponent("embed"),
108 | fieldset: createStylesComponent("fieldset"),
109 | figcaption: createStylesComponent("figcaption"),
110 | figure: createStylesComponent("figure"),
111 | footer: createStylesComponent("footer"),
112 | form: createStylesComponent("form"),
113 | h1: createStylesComponent("h1"),
114 | h2: createStylesComponent("h2"),
115 | h3: createStylesComponent("h3"),
116 | h4: createStylesComponent("h4"),
117 | h5: createStylesComponent("h5"),
118 | h6: createStylesComponent("h6"),
119 | head: createStylesComponent("head"),
120 | header: createStylesComponent("header"),
121 | hr: createStylesComponent("hr"),
122 | html: createStylesComponent("html"),
123 | i: createStylesComponent("i"),
124 | iframe: createStylesComponent("iframe"),
125 | img: createStylesComponent("img"),
126 | input: createStylesComponent("input"),
127 | ins: createStylesComponent("ins"),
128 | kbd: createStylesComponent("kbd"),
129 | label: createStylesComponent("label"),
130 | legend: createStylesComponent("legend"),
131 | li: createStylesComponent("li"),
132 | link: createStylesComponent("link"),
133 | main: createStylesComponent("main"),
134 | map: createStylesComponent("map"),
135 | mark: createStylesComponent("mark"),
136 | menu: createStylesComponent("menu"),
137 | meta: createStylesComponent("meta"),
138 | meter: createStylesComponent("meter"),
139 | nav: createStylesComponent("nav"),
140 | noscript: createStylesComponent("noscript"),
141 | object: createStylesComponent("object"),
142 | ol: createStylesComponent("ol"),
143 | optgroup: createStylesComponent("optgroup"),
144 | option: createStylesComponent("option"),
145 | output: createStylesComponent("output"),
146 | p: createStylesComponent("p"),
147 | picture: createStylesComponent("picture"),
148 | pre: createStylesComponent("pre"),
149 | progress: createStylesComponent("progress"),
150 | q: createStylesComponent("q"),
151 | rp: createStylesComponent("rp"),
152 | rt: createStylesComponent("rt"),
153 | ruby: createStylesComponent("ruby"),
154 | s: createStylesComponent("s"),
155 | samp: createStylesComponent("samp"),
156 | script: createStylesComponent("script"),
157 | section: createStylesComponent("section"),
158 | select: createStylesComponent("select"),
159 | small: createStylesComponent("small"),
160 | source: createStylesComponent("source"),
161 | span: createStylesComponent("span"),
162 | strong: createStylesComponent("strong"),
163 | style: createStylesComponent("style"),
164 | sub: createStylesComponent("sub"),
165 | summary: createStylesComponent("summary"),
166 | sup: createStylesComponent("sup"),
167 | table: createStylesComponent("table"),
168 | tbody: createStylesComponent("tbody"),
169 | td: createStylesComponent("td"),
170 | template: createStylesComponent("template"),
171 | textarea: createStylesComponent("textarea"),
172 | tfoot: createStylesComponent("tfoot"),
173 | th: createStylesComponent("th"),
174 | thead: createStylesComponent("thead"),
175 | time: createStylesComponent("time"),
176 | title: createStylesComponent("title"),
177 | tr: createStylesComponent("tr"),
178 | track: createStylesComponent("track"),
179 | u: createStylesComponent("u"),
180 | ul: createStylesComponent("ul"),
181 | var: createStylesComponent("var"),
182 | video: createStylesComponent("video"),
183 | wbr: createStylesComponent("wbr"),
184 | // SVG elements
185 | circle: createStylesComponent("circle"),
186 | clipPath: createStylesComponent("clipPath"),
187 | defs: createStylesComponent("defs"),
188 | desc: createStylesComponent("desc"),
189 | ellipse: createStylesComponent("ellipse"),
190 | feBlend: createStylesComponent("feBlend"),
191 | feColorMatrix: createStylesComponent("feColorMatrix"),
192 | feComponentTransfer: createStylesComponent("feComponentTransfer"),
193 | feComposite: createStylesComponent("feComposite"),
194 | feConvolveMatrix: createStylesComponent("feConvolveMatrix"),
195 | feDiffuseLighting: createStylesComponent("feDiffuseLighting"),
196 | feDisplacementMap: createStylesComponent("feDisplacementMap"),
197 | feDistantLight: createStylesComponent("feDistantLight"),
198 | feDropShadow: createStylesComponent("feDropShadow"),
199 | feFlood: createStylesComponent("feFlood"),
200 | feFuncA: createStylesComponent("feFuncA"),
201 | feFuncB: createStylesComponent("feFuncB"),
202 | feFuncG: createStylesComponent("feFuncG"),
203 | feFuncR: createStylesComponent("feFuncR"),
204 | feGaussianBlur: createStylesComponent("feGaussianBlur"),
205 | feImage: createStylesComponent("feImage"),
206 | feMerge: createStylesComponent("feMerge"),
207 | feMergeNode: createStylesComponent("feMergeNode"),
208 | feMorphology: createStylesComponent("feMorphology"),
209 | feOffset: createStylesComponent("feOffset"),
210 | fePointLight: createStylesComponent("fePointLight"),
211 | feSpecularLighting: createStylesComponent("feSpecularLighting"),
212 | feSpotLight: createStylesComponent("feSpotLight"),
213 | feTile: createStylesComponent("feTile"),
214 | feTurbulence: createStylesComponent("feTurbulence"),
215 | filter: createStylesComponent("filter"),
216 | foreignObject: createStylesComponent("foreignObject"),
217 | g: createStylesComponent("g"),
218 | image: createStylesComponent("image"),
219 | line: createStylesComponent("line"),
220 | linearGradient: createStylesComponent("linearGradient"),
221 | marker: createStylesComponent("marker"),
222 | mask: createStylesComponent("mask"),
223 | metadata: createStylesComponent("metadata"),
224 | mpath: createStylesComponent("mpath"),
225 | path: createStylesComponent("path"),
226 | pattern: createStylesComponent("pattern"),
227 | polygon: createStylesComponent("polygon"),
228 | polyline: createStylesComponent("polyline"),
229 | radialGradient: createStylesComponent("radialGradient"),
230 | rect: createStylesComponent("rect"),
231 | set: createStylesComponent("set"),
232 | stop: createStylesComponent("stop"),
233 | switch: createStylesComponent("switch"),
234 | symbol: createStylesComponent("symbol"),
235 | text: createStylesComponent("text"),
236 | textPath: createStylesComponent("textPath"),
237 | tspan: createStylesComponent("tspan"),
238 | use: createStylesComponent("use"),
239 | view: createStylesComponent("view"),
240 | animate: createStylesComponent("animate"),
241 | animateMotion: createStylesComponent("animateMotion"),
242 | animateTransform: createStylesComponent("animateTransform"),
243 | big: createStylesComponent("big"),
244 | center: createStylesComponent("center"),
245 | hgroup: createStylesComponent("hgroup"),
246 | keygen: createStylesComponent("keygen"),
247 | menuitem: createStylesComponent("menuitem"),
248 | noindex: createStylesComponent("noindex"),
249 | param: createStylesComponent("param"),
250 | search: createStylesComponent("search"),
251 | slot: createStylesComponent("slot"),
252 | svg: createStylesComponent("svg"),
253 | webview: createStylesComponent("webview"),
254 | } satisfies Record>;
255 |
256 | function getIsIntrinsicElementName(element: string | symbol): element is IntrinsicElementName {
257 | return element in stylingsBuiltInComponents;
258 | }
259 |
260 | export type StylesComponentsLibrary = typeof stylingsBuiltInComponents & CustomStylesComponents;
261 |
262 | export const UI = new Proxy(stylingsBuiltInComponents, {
263 | get(builtInElements, prop) {
264 | if (getIsIntrinsicElementName(prop)) return builtInElements[prop];
265 |
266 | return createStylingsComponentFromCustomName(prop as string);
267 | },
268 | }) as StylesComponentsLibrary;
269 |
270 | const SW = styled.div<{ $styles?: AnyStyledComposer[] }>`
271 | ${(props) => props.$styles}
272 | `;
273 |
--------------------------------------------------------------------------------
/stylings/src/compilation.ts:
--------------------------------------------------------------------------------
1 | import { ComposerStyle, getIsComposer } from "./Composer";
2 | import { Interpolation, RuleSet, css } from "styled-components";
3 |
4 | import { isNotNullish } from "./utils/nullish";
5 | import { memoizeFn } from "./utils/memoize";
6 | import { mutateArray } from "./utils/array";
7 |
8 | export function simplifyRule(ruleSet: RuleSet) {
9 | mutateArray(ruleSet, (item, _index, controller) => {
10 | if (typeof item === "string") {
11 | const trimmed = item.trim().replace(/\s+/g, " ");
12 |
13 | if (trimmed.length === 0) return controller.remove;
14 |
15 | if (trimmed.length === item.length) return controller.noChange;
16 |
17 | return trimmed;
18 | }
19 |
20 | return controller.noChange;
21 | });
22 |
23 | return ruleSet;
24 | }
25 |
26 | export const compileComposerStyles = memoizeFn(
27 | (styles: ComposerStyle[]): RuleSet => {
28 | const precompiledStyles = styles
29 | .map((style) => {
30 | if (getIsComposer(style)) return style.compile();
31 |
32 | return style;
33 | })
34 | .filter(isNotNullish);
35 |
36 | // return precompiledStyles;
37 |
38 | // prettier-ignore
39 | const result = css`${precompiledStyles as Interpolation