@charcoal-ui/styled 1.0.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.ts ADDED
@@ -0,0 +1,720 @@
1
+ import { CSSObject, ThemedStyledInterface } from 'styled-components'
2
+ import warning from 'warning'
3
+ import {
4
+ factory,
5
+ modifiedFactory,
6
+ constFactory,
7
+ modifiedArgumentedFactory,
8
+ } from './lib'
9
+ import { EffectType, StyledTheme as Theme, Key } from './types'
10
+ import {
11
+ objectAssign,
12
+ unreachable,
13
+ ReadonlyArrayConstructor,
14
+ objectKeys,
15
+ isPresent,
16
+ } from './util'
17
+ import { columnPx, halfLeading } from '@charcoal-ui/foundation'
18
+ import {
19
+ applyEffect,
20
+ applyEffectToGradient,
21
+ dur,
22
+ gradient,
23
+ GradientDirection,
24
+ notDisabledSelector,
25
+ disabledSelector,
26
+ px,
27
+ } from '@charcoal-ui/utils'
28
+ export { type Modified, type ModifiedArgumented } from './lib'
29
+
30
+ import { light, dark } from './theme'
31
+ export { type ElementsTheme } from './theme'
32
+
33
+ const colorProperties = ['bg', 'font'] as const
34
+ type ColorProperty = typeof colorProperties[number]
35
+
36
+ const spacingProperties = ['margin', 'padding'] as const
37
+ const spacingDirections = [
38
+ 'top',
39
+ 'right',
40
+ 'bottom',
41
+ 'left',
42
+ 'vertical',
43
+ 'horizontal',
44
+ 'all',
45
+ ] as const
46
+ type SpacingProperty = typeof spacingProperties[number]
47
+ type SpacingDirection = typeof spacingDirections[number]
48
+
49
+ const fixedProperties = ['width', 'height'] as const
50
+ type FixedProperty = typeof fixedProperties[number]
51
+
52
+ const borderDirections = ['top', 'right', 'bottom', 'left'] as const
53
+ type BorderDirection = typeof borderDirections[number]
54
+
55
+ const outlineType = ['focus'] as const
56
+ type OutlineType = typeof outlineType[number]
57
+
58
+ /**
59
+ * `theme(o => [...])` の `o` の部分を構築する
60
+ *
61
+ * @param theme テーマオブジェクト
62
+ * @param isPhantom 型推論のためだけに使う場合にランタイムコストをゼロにするフラグ
63
+ */
64
+ function builder<T extends Theme>(
65
+ theme: {
66
+ // factoryの第二引数に入れ込むものだけ明示的に型変数を展開しておくことで型の具象化を遅延する
67
+ color: T['color']
68
+ gradientColor: T['gradientColor']
69
+ border: T['border']
70
+ outline: T['outline']
71
+ } & Omit<T, 'color' | 'gradientColor' | 'border' | 'outline'>,
72
+ isPhantom = false
73
+ ) {
74
+ if (isPhantom) {
75
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
76
+ return {} as never
77
+ }
78
+ const colors = objectKeys(theme.color)
79
+ const effects = objectKeys(theme.effect)
80
+
81
+ // 色
82
+ const gradientColors = objectKeys(theme.gradientColor)
83
+ const colorCss = createColorCss(theme)
84
+ const gradientColorCss = createGradientColorCss(theme)
85
+ const colorObject = constFactory(
86
+ {},
87
+ {
88
+ bg: objectAssign(
89
+ factory({}, colors, (color) =>
90
+ modifiedFactory(effects, (modifiers) =>
91
+ colorCss('bg', color, modifiers)
92
+ )
93
+ ),
94
+ factory(
95
+ {},
96
+ gradientColors,
97
+ (color) => (direction: GradientDirection) =>
98
+ modifiedFactory(effects, (modifiers) =>
99
+ gradientColorCss(color, modifiers, direction)
100
+ )
101
+ )
102
+ ),
103
+ font: factory({}, colors, (color) =>
104
+ modifiedFactory(effects, (modifiers) =>
105
+ colorCss('font', color, modifiers)
106
+ )
107
+ ),
108
+ }
109
+ )
110
+
111
+ // タイポグラフィ
112
+ const typographyModifiers = [
113
+ // TODO
114
+ 'monospace',
115
+ 'bold',
116
+ 'preserveHalfLeading',
117
+ ] as const
118
+ const typographyCss = createTypographyCss(theme)
119
+ const typographyObject = factory(
120
+ {},
121
+ ['typography'] as const,
122
+ (_) => (size: keyof T['typography']['size']) =>
123
+ modifiedFactory(typographyModifiers, (modifiers) =>
124
+ typographyCss(size, {
125
+ preserveHalfLeading: modifiers.includes('preserveHalfLeading'),
126
+ monospace: modifiers.includes('monospace'),
127
+ bold: modifiers.includes('bold'),
128
+ })
129
+ )
130
+ )
131
+
132
+ // スペーシング
133
+ const spacingCss = createSpacingCss(theme)
134
+ const spacingObject = factory({}, spacingProperties, (spacingProperty) =>
135
+ modifiedArgumentedFactory(
136
+ spacingDirections,
137
+ (modifiers) => spacingCss(spacingProperty, modifiers),
138
+ {} as keyof T['spacing'] | 'auto' // 推論のためのメタタイプ
139
+ )
140
+ )
141
+
142
+ // 大きさ
143
+ const fixedPxCss = createFixedPxCss(theme)
144
+ const fixedColumnCss = createFixedColumnCss(theme)
145
+ const fixedRelativeCss = createFixedRelativeCss(theme)
146
+ const fixedObject = factory({}, fixedProperties, (property) =>
147
+ constFactory(
148
+ {},
149
+ {
150
+ px: (size: keyof T['spacing'] | 'auto') => fixedPxCss(property, size),
151
+ column: (span: number) => fixedColumnCss(property, span),
152
+ auto: fixedRelativeCss(property, 'auto'),
153
+ full: fixedRelativeCss(property, '100%'),
154
+ }
155
+ )
156
+ )
157
+
158
+ // 要素へのエフェクト (etc: 透過)
159
+ const elementEffectCss = createElementEffectCss(theme)
160
+ const elementEffectObject = modifiedFactory(
161
+ objectKeys(theme.elementEffect),
162
+ (modifiers) => elementEffectCss(modifiers)
163
+ )
164
+
165
+ // ボーダー
166
+ const borderCss = createBorderCss(theme)
167
+ const borderObject = constFactory(
168
+ {},
169
+ {
170
+ border: factory({}, objectKeys(theme.border), (variant) =>
171
+ modifiedFactory(borderDirections, (modifiers) =>
172
+ borderCss(variant, modifiers)
173
+ )
174
+ ),
175
+ }
176
+ )
177
+
178
+ // 角丸
179
+ const borderRadiusCss = createBorderRadiusCss(theme)
180
+ const borderRadiusObject = constFactory(
181
+ {},
182
+ {
183
+ borderRadius: (radius: keyof T['borderRadius']) =>
184
+ borderRadiusCss(radius),
185
+ }
186
+ )
187
+
188
+ // アウトライン
189
+ const outlineCss = createOutlineColorCss(theme)
190
+ const outlineObject = constFactory(
191
+ {},
192
+ {
193
+ outline: factory({}, objectKeys(theme.outline), (variant) =>
194
+ modifiedFactory(outlineType, (modifiers) =>
195
+ outlineCss(variant, modifiers)
196
+ )
197
+ ),
198
+ }
199
+ )
200
+
201
+ return objectAssign(
202
+ colorObject,
203
+ typographyObject,
204
+ spacingObject,
205
+ fixedObject,
206
+ elementEffectObject,
207
+ borderObject,
208
+ borderRadiusObject,
209
+ outlineObject
210
+ )
211
+ }
212
+
213
+ function targetProperty(target: ColorProperty) {
214
+ return target === 'bg' ? 'background-color' : 'color'
215
+ }
216
+
217
+ function isSupportedEffect(effect: Key): effect is EffectType {
218
+ return ['hover', 'press', 'disabled'].includes(effect as string)
219
+ }
220
+
221
+ function onEffectPseudo(effect: EffectType, css: CSSObject) {
222
+ return effect === 'hover'
223
+ ? { '&:hover': { [notDisabledSelector]: css } }
224
+ : effect === 'press'
225
+ ? { '&:active': { [notDisabledSelector]: css } }
226
+ : // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
227
+ effect === 'disabled'
228
+ ? { [disabledSelector]: css }
229
+ : unreachable(effect)
230
+ }
231
+
232
+ const createColorCss =
233
+ <T extends Theme>(theme: T) =>
234
+ (
235
+ target: ColorProperty,
236
+ color: keyof T['color'],
237
+ effects: readonly (keyof T['effect'])[] = []
238
+ ): Internal =>
239
+ internal(
240
+ () => ({
241
+ [targetProperty(target)]: theme.color[color],
242
+ ...effects.filter(isSupportedEffect).reduce<CSSObject>(
243
+ (acc, effect) => ({
244
+ ...acc,
245
+ ...onEffectPseudo(effect, {
246
+ [targetProperty(target)]: applyEffect(
247
+ theme.color[color],
248
+ theme.effect[effect] ?? []
249
+ ),
250
+ }),
251
+ }),
252
+ {}
253
+ ),
254
+ }),
255
+ effects.length > 0
256
+ ? target === 'font'
257
+ ? {
258
+ colorTransition: true,
259
+ }
260
+ : {
261
+ backgroundColorTransition: true,
262
+ }
263
+ : {}
264
+ )
265
+
266
+ // TODO: deprecate
267
+ const TRANSITION_DURATION = 0.2
268
+
269
+ const createGradientColorCss =
270
+ <T extends Theme>(theme: T) =>
271
+ (
272
+ color: keyof T['gradientColor'],
273
+ effects: readonly (keyof T['effect'])[] = [],
274
+ direction: GradientDirection
275
+ ): Internal => {
276
+ const toLinearGradient = gradient(direction)
277
+ return internal((context) => {
278
+ const optimized = !useHalfLeadingCanceller(context)
279
+ const duration = dur(TRANSITION_DURATION)
280
+ if (optimized && effects.length > 0) {
281
+ return {
282
+ position: 'relative',
283
+ zIndex: 0,
284
+ overflow: 'hidden',
285
+ ...effects.filter(isSupportedEffect).reduce<CSSObject>(
286
+ (acc, effect) => ({
287
+ ...acc,
288
+ '&::before': {
289
+ zIndex: -1,
290
+ ...overlayElement,
291
+ transition: `${duration} background-color`,
292
+ },
293
+ '&::after': {
294
+ zIndex: -2,
295
+ ...overlayElement,
296
+ ...toLinearGradient(theme.gradientColor[color]),
297
+ },
298
+ ...onEffectPseudo(effect, {
299
+ '&::before': {
300
+ backgroundColor: applyEffect(
301
+ null,
302
+ theme.effect[effect] ?? []
303
+ ),
304
+ },
305
+ }),
306
+ }),
307
+ {}
308
+ ),
309
+ }
310
+ } else {
311
+ warning(
312
+ effects.length === 0,
313
+ // eslint-disable-next-line max-len
314
+ `'Transition' will not be applied. You can get around this by specifying 'preserveHalfLeading' or both 'padding' and 'typograpy'.`
315
+ )
316
+ return {
317
+ ...toLinearGradient(theme.gradientColor[color]),
318
+ ...effects.filter(isSupportedEffect).reduce<CSSObject>(
319
+ (acc, effect) => ({
320
+ ...acc,
321
+ ...onEffectPseudo(effect, {
322
+ ...toLinearGradient(
323
+ applyEffectToGradient(theme.effect[effect] ?? [])(
324
+ theme.gradientColor[color]
325
+ )
326
+ ),
327
+ }),
328
+ }),
329
+ {}
330
+ ),
331
+ }
332
+ }
333
+ })
334
+ }
335
+
336
+ /**
337
+ * @see https://developer.mozilla.org/ja/docs/Web/CSS/:focus-visible#selectively_showing_the_focus_indicator
338
+ */
339
+ const onFocus = (css: CSSObject) => ({
340
+ [notDisabledSelector]: {
341
+ '&:focus, &:active': {
342
+ outline: 'none',
343
+ ...css,
344
+ },
345
+
346
+ '&:focus:not(:focus-visible), &:active:not(:focus-visible)': {
347
+ outline: 'none',
348
+ },
349
+
350
+ '&:focus-visible': {
351
+ outline: 'none',
352
+ ...css,
353
+ },
354
+ },
355
+ })
356
+
357
+ const outlineCss = (weight: number, color: string) => ({
358
+ boxShadow: `0 0 0 ${px(weight)} ${color}`,
359
+ })
360
+
361
+ const createOutlineColorCss =
362
+ <T extends Theme>(theme: T) =>
363
+ (
364
+ variant: keyof T['outline'],
365
+ modifiers: readonly OutlineType[]
366
+ ): Internal => {
367
+ const weight = theme.outline[variant].weight
368
+ const color = theme.outline[variant].color
369
+ return internal(
370
+ () =>
371
+ modifiers.includes('focus')
372
+ ? onFocus(outlineCss(weight, color))
373
+ : { '&&': { [notDisabledSelector]: outlineCss(weight, color) } },
374
+ {
375
+ boxShadowTransition: true,
376
+ }
377
+ )
378
+ }
379
+
380
+ const overlayElement: CSSObject = {
381
+ content: "''",
382
+ display: 'block',
383
+ position: 'absolute',
384
+ width: '100%',
385
+ height: '100%',
386
+ top: 0,
387
+ left: 0,
388
+ }
389
+
390
+ // half-leadingをキャンセルするとき && 垂直方向のpaddingが無い時
391
+ // -> before/afterを入れる
392
+ const useHalfLeadingCanceller = ({
393
+ cancelHalfLeadingPx,
394
+ hasVerticalPadding = false,
395
+ }: Context) => cancelHalfLeadingPx !== undefined && !hasVerticalPadding
396
+
397
+ const createTypographyCss =
398
+ <T extends Theme>(theme: T) =>
399
+ (
400
+ size: keyof T['typography']['size'],
401
+ options: {
402
+ preserveHalfLeading?: boolean
403
+ monospace?: boolean
404
+ bold?: boolean
405
+ } = {}
406
+ ): Internal => {
407
+ const {
408
+ preserveHalfLeading = false,
409
+ monospace = false,
410
+ bold = false,
411
+ } = options
412
+ const descriptor = theme.typography.size[size]
413
+ const margin = -halfLeading(descriptor)
414
+
415
+ return internal(
416
+ (context) => ({
417
+ fontSize: px(descriptor.fontSize),
418
+ lineHeight: px(descriptor.lineHeight),
419
+ ...(monospace && {
420
+ fontFamily: 'monospace',
421
+ }),
422
+ ...(bold && {
423
+ fontWeight: 'bold',
424
+ }),
425
+ ...(useHalfLeadingCanceller(context) && {
426
+ // prevent margin collapsing
427
+ display: 'flow-root',
428
+ // cancel half-leading with negative margin
429
+ '&::before': {
430
+ ...leadingCancel,
431
+ marginTop: px(margin),
432
+ },
433
+ '&::after': {
434
+ ...leadingCancel,
435
+ marginBottom: px(margin),
436
+ },
437
+ }),
438
+ }),
439
+ !preserveHalfLeading
440
+ ? {
441
+ cancelHalfLeadingPx: margin,
442
+ }
443
+ : {}
444
+ )
445
+ }
446
+
447
+ const leadingCancel: CSSObject = {
448
+ display: 'block',
449
+ width: 0,
450
+ height: 0,
451
+ content: `''`,
452
+ }
453
+
454
+ function spacingProperty(
455
+ property: SpacingProperty,
456
+ direction: 'top' | 'right' | 'bottom' | 'left'
457
+ ) {
458
+ return `${property}-${direction}`
459
+ }
460
+
461
+ const createSpacingCss =
462
+ <T extends Theme>(theme: { spacing: T['spacing'] }) =>
463
+ (
464
+ property: SpacingProperty,
465
+ modifiers: readonly [SpacingDirection, keyof T['spacing'] | 'auto'][]
466
+ ): Internal => {
467
+ const { top, right, bottom, left } = modifiers.reduce(
468
+ (acc, [direction, size]) => {
469
+ if (direction === 'all') {
470
+ acc.top = size
471
+ acc.right = size
472
+ acc.bottom = size
473
+ acc.left = size
474
+ } else if (direction === 'vertical') {
475
+ acc.top = size
476
+ acc.bottom = size
477
+ } else if (direction === 'horizontal') {
478
+ acc.right = size
479
+ acc.left = size
480
+ } else {
481
+ acc[direction] = size
482
+ }
483
+ return acc
484
+ },
485
+ {} as Partial<
486
+ Record<'top' | 'right' | 'bottom' | 'left', keyof T['spacing'] | 'auto'>
487
+ >
488
+ )
489
+
490
+ const hasVerticalPadding =
491
+ property === 'padding' &&
492
+ top !== undefined &&
493
+ bottom !== undefined &&
494
+ top !== 'auto' &&
495
+ bottom !== 'auto'
496
+
497
+ return internal(
498
+ ({ cancelHalfLeadingPx = 0 }) => ({
499
+ ...(top !== undefined && {
500
+ [spacingProperty(property, 'top')]:
501
+ top === 'auto'
502
+ ? 'auto'
503
+ : px(
504
+ theme.spacing[top] +
505
+ (hasVerticalPadding ? cancelHalfLeadingPx : 0)
506
+ ),
507
+ }),
508
+ ...(bottom !== undefined && {
509
+ [spacingProperty(property, 'bottom')]:
510
+ bottom === 'auto'
511
+ ? 'auto'
512
+ : px(
513
+ theme.spacing[bottom] +
514
+ (hasVerticalPadding ? cancelHalfLeadingPx : 0)
515
+ ),
516
+ }),
517
+ ...(right !== undefined && {
518
+ [spacingProperty(property, 'right')]:
519
+ right === 'auto' ? 'auto' : px(theme.spacing[right]),
520
+ }),
521
+ ...(left !== undefined && {
522
+ [spacingProperty(property, 'left')]:
523
+ left === 'auto' ? 'auto' : px(theme.spacing[left]),
524
+ }),
525
+ }),
526
+ hasVerticalPadding ? { hasVerticalPadding: true } : {}
527
+ )
528
+ }
529
+
530
+ const createFixedPxCss =
531
+ <T extends Theme>(theme: T) =>
532
+ (property: FixedProperty, size: keyof T['spacing'] | 'auto'): Internal =>
533
+ internal(() => ({
534
+ [property]: size === 'auto' ? 'auto' : px(theme.spacing[size]),
535
+ }))
536
+
537
+ const createFixedRelativeCss =
538
+ <T extends Theme>(_theme: T) =>
539
+ (property: FixedProperty, amount: '100%' | 'auto'): Internal =>
540
+ internal(() => ({
541
+ [property]: amount,
542
+ }))
543
+
544
+ const createFixedColumnCss =
545
+ <T extends Theme>(_theme: T) =>
546
+ (property: FixedProperty, span: number): Internal =>
547
+ internal(() => ({
548
+ [property]: px(columnPx(span)),
549
+ }))
550
+
551
+ const createElementEffectCss =
552
+ <T extends Theme, TElementEffect extends T['elementEffect']>(theme: {
553
+ elementEffect: TElementEffect
554
+ }) =>
555
+ (effects: readonly (keyof TElementEffect)[] = []): Internal =>
556
+ internal(() =>
557
+ effects.filter(isSupportedEffect).reduce<CSSObject>(
558
+ (acc, effect) => ({
559
+ ...acc,
560
+ ...onEffectPseudo(effect, {
561
+ opacity:
562
+ !(Array as ReadonlyArrayConstructor).isArray(
563
+ theme.elementEffect[effect]
564
+ ) && theme.elementEffect[effect]?.type === 'opacity'
565
+ ? theme.elementEffect[effect]?.opacity
566
+ : unreachable(),
567
+ }),
568
+ }),
569
+ {}
570
+ )
571
+ )
572
+
573
+ function borderProperty(direction: BorderDirection) {
574
+ return `border-${direction}`
575
+ }
576
+
577
+ function borderShorthand(color: string) {
578
+ return `solid 1px ${color}`
579
+ }
580
+
581
+ const createBorderCss =
582
+ <T extends Theme>(theme: T) =>
583
+ (
584
+ variant: keyof T['border'],
585
+ directions: readonly BorderDirection[]
586
+ ): Internal => {
587
+ const all = directions.length === 0
588
+ const value = borderShorthand(theme.border[variant].color)
589
+ return internal(() => ({
590
+ ...(all
591
+ ? { border: value }
592
+ : directions.reduce<CSSObject>(
593
+ (acc, direction) => ({
594
+ ...acc,
595
+ [borderProperty(direction)]: value,
596
+ }),
597
+ {}
598
+ )),
599
+ }))
600
+ }
601
+
602
+ const createBorderRadiusCss =
603
+ <T extends Theme>(theme: T) =>
604
+ (size: keyof T['borderRadius']): Internal =>
605
+ internal(() => ({
606
+ borderRadius: px(theme.borderRadius[size]),
607
+ }))
608
+
609
+ interface Context {
610
+ cancelHalfLeadingPx?: number
611
+ hasVerticalPadding?: boolean
612
+ boxShadowTransition?: boolean
613
+ colorTransition?: boolean
614
+ backgroundColorTransition?: boolean
615
+ }
616
+
617
+ const commonSpec = (_theme: unknown): Internal => {
618
+ const duration = dur(TRANSITION_DURATION)
619
+ const transition = (property: string[]) => ({
620
+ transition: property.map((v) => `${duration} ${v}`).join(', '),
621
+ })
622
+ return internal(
623
+ ({
624
+ colorTransition = false,
625
+ backgroundColorTransition = false,
626
+ boxShadowTransition = false,
627
+ }) =>
628
+ transition(
629
+ [
630
+ colorTransition ? 'color' : null,
631
+ backgroundColorTransition ? 'background-color' : null,
632
+ boxShadowTransition ? 'box-shadow' : null,
633
+ ].filter(isPresent)
634
+ )
635
+ )
636
+ }
637
+
638
+ const internalSym: unique symbol = Symbol('internal')
639
+
640
+ function internal(
641
+ operation: (context: Context) => CSSObject,
642
+ context: Context = {}
643
+ ): Internal {
644
+ return {
645
+ [internalSym]: {
646
+ operation,
647
+ context,
648
+ },
649
+ }
650
+ }
651
+
652
+ export interface Internal {
653
+ [internalSym]: {
654
+ operation: (context: Context) => CSSObject
655
+ context: Context
656
+ }
657
+ }
658
+
659
+ type Blank = null | undefined | false
660
+
661
+ const nonBlank = <T>(value: T): value is T extends Blank ? never : T =>
662
+ isPresent(value) && (value as unknown) !== false
663
+
664
+ /**
665
+ * `theme(o => [...])` の `theme` ユーティリティを構築する
666
+ *
667
+ * @param _styled styled-componnets の `styled` そのもの (型推論のために用いられる)
668
+ *
669
+ * @example
670
+ *
671
+ * import styled from 'styled-components'
672
+ * const theme = createTheme(styled)
673
+ *
674
+ * @example
675
+ *
676
+ * const theme = createTheme<DefaultTheme>()
677
+ */
678
+ function createTheme<T extends Theme>(_styled?: ThemedStyledInterface<T>) {
679
+ // `theme(o => [...])` の `o` の部分の型推論のためだけに使う意味のない変数
680
+ // Tを型変数のまま渡してcreateThemeが呼ばれるまで型の具象化が行われないようにする
681
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any
682
+ const _phantomBuilder = builder<T>({} as any, true)
683
+ // ランタイムの `theme(o => [...])` のインターフェースを構築する
684
+ return (
685
+ // ユーザー定義
686
+ spec: (
687
+ o: typeof _phantomBuilder
688
+ ) => Blank | Internal | (Blank | Internal)[]
689
+ ): ThemeProp<T> =>
690
+ ({ theme }) => {
691
+ // styled-componentsのランタイムから受け取ったthemeオブジェクトをbuilderに食わせて`o`をつくる
692
+ // さらに、ユーザー定義にbuilderが構築した`o`を食わせる
693
+ // (`o`を一時変数に入れてしまうと型Tの具象化が行われるので関数合成を優先する)
694
+ const rawSpecDescriptor = spec(builder(theme))
695
+ // ユーザー定義の配列を整形
696
+ const specDescriptor = [
697
+ ...(Array.isArray(rawSpecDescriptor)
698
+ ? rawSpecDescriptor
699
+ : [rawSpecDescriptor]),
700
+ commonSpec(theme),
701
+ ].filter(nonBlank)
702
+
703
+ // 1パス目
704
+ // 全ユーザー定義を舐めて相互に影響し合う定義をチェックし、その結果(コンテキスト)を取得
705
+ const context = specDescriptor.reduce<Context>(
706
+ (acc, v) => ({ ...acc, ...v[internalSym].context }),
707
+ {}
708
+ )
709
+ // 2パス目
710
+ // コンテキストを見ながら最適化されたCSSを構築
711
+ return specDescriptor.map((v) => v[internalSym].operation(context))
712
+ }
713
+ }
714
+
715
+ // default export を疑似esmにしないようにする hack
716
+ createTheme.light = light
717
+ createTheme.dark = dark
718
+ export default createTheme
719
+
720
+ export type ThemeProp<T> = ({ theme }: { theme: T }) => CSSObject | CSSObject[]