@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/LICENSE +201 -0
- package/README.md +17 -0
- package/package.json +54 -0
- package/src/index.story.tsx +318 -0
- package/src/index.ts +720 -0
- package/src/lib.ts +178 -0
- package/src/theme.ts +107 -0
- package/src/types.ts +43 -0
- package/src/util.ts +78 -0
- package/theme.json +676 -0
package/src/lib.ts
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { Key } from './types'
|
|
2
|
+
import { unreachable } from './util'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 配列で指定したプロパティを動的に生やす
|
|
6
|
+
*
|
|
7
|
+
* @param source 拡張するオブジェクト
|
|
8
|
+
* @param member オブジェクトに生やすプロパティ一覧
|
|
9
|
+
* @param chain プロパティに格納される値を生成する関数
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
*
|
|
13
|
+
* const o = factory({}, ['red', 'blue'],
|
|
14
|
+
* color => hex(color)
|
|
15
|
+
* )
|
|
16
|
+
*
|
|
17
|
+
* console.log(o.red) //=> #ff0000
|
|
18
|
+
*/
|
|
19
|
+
export const factory = <TSource, TMember extends readonly Key[], TValue>(
|
|
20
|
+
source: TSource,
|
|
21
|
+
member: TMember,
|
|
22
|
+
chain: (key: TMember[number]) => TValue
|
|
23
|
+
) =>
|
|
24
|
+
Object.defineProperties(
|
|
25
|
+
source,
|
|
26
|
+
Object.fromEntries(
|
|
27
|
+
member.map((key) => [
|
|
28
|
+
key,
|
|
29
|
+
{ get: () => chain(key), enumerable: true, configurable: true },
|
|
30
|
+
])
|
|
31
|
+
)
|
|
32
|
+
) as TSource & { readonly [key in TMember[number]]: TValue }
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 配列で指定した名前のメソッドを動的に生やす
|
|
36
|
+
*
|
|
37
|
+
* @param source 拡張するオブジェクト
|
|
38
|
+
* @param member オブジェクトに生やすメソッド名一覧
|
|
39
|
+
* @param chain メソッドの戻り値になる値を生成する関数
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
*
|
|
43
|
+
* const o = argumentedFactory({}, ['red', 'blue'],
|
|
44
|
+
* (color, alpha: number) => hex(color, alpha)
|
|
45
|
+
* )
|
|
46
|
+
*
|
|
47
|
+
* console.log(o.red(0.5)) //=> #ff000077
|
|
48
|
+
*/
|
|
49
|
+
export const argumentedFactory = <
|
|
50
|
+
TSource,
|
|
51
|
+
TMember extends readonly string[],
|
|
52
|
+
TValue,
|
|
53
|
+
TArguments extends unknown[]
|
|
54
|
+
>(
|
|
55
|
+
source: TSource,
|
|
56
|
+
member: TMember,
|
|
57
|
+
chain: (key: TMember[number], ...args: TArguments) => TValue
|
|
58
|
+
) =>
|
|
59
|
+
Object.defineProperties(
|
|
60
|
+
source,
|
|
61
|
+
Object.fromEntries(
|
|
62
|
+
member.map((key) => [
|
|
63
|
+
key,
|
|
64
|
+
{
|
|
65
|
+
value: (...args: TArguments) => chain(key, ...args),
|
|
66
|
+
enumerable: true,
|
|
67
|
+
configurable: true,
|
|
68
|
+
},
|
|
69
|
+
])
|
|
70
|
+
)
|
|
71
|
+
) as TSource & {
|
|
72
|
+
readonly [key in TMember[number]]: (...args: TArguments) => TValue
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* オブジェクトで指定したプロパティ名と値を動的に生やす
|
|
77
|
+
*
|
|
78
|
+
* @param source 拡張するオブジェクト
|
|
79
|
+
* @param def オブジェクトに生やす定義(プロパティ名と値)
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
*
|
|
83
|
+
* const o = constFactory({}, {
|
|
84
|
+
* red: '#f00',
|
|
85
|
+
* blue: '#00f',
|
|
86
|
+
* })
|
|
87
|
+
*
|
|
88
|
+
* console.log(o.red) //=> #f00
|
|
89
|
+
*/
|
|
90
|
+
export const constFactory = <TSource, TDef extends { [key: string]: unknown }>(
|
|
91
|
+
source: TSource,
|
|
92
|
+
def: TDef
|
|
93
|
+
) =>
|
|
94
|
+
factory(source, Object.keys(def), (key) => def[key]) as TSource &
|
|
95
|
+
Readonly<TDef>
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* 配列で指定したモディファイア(プロパティ)をチェーン可能な再帰オブジェクトを動的に生やす
|
|
99
|
+
*
|
|
100
|
+
* @param modifiers オブジェクトに生やすモディファイヤ一覧
|
|
101
|
+
* @param source 指定されたモディファイヤの一覧から値を生成する関数
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
*
|
|
105
|
+
* const o = modifiedArgumentedFactory(['red', 'blue'],
|
|
106
|
+
* modifiers => modifiers.map(color => hex(color)).join(',')
|
|
107
|
+
* )
|
|
108
|
+
*
|
|
109
|
+
* console.log(o.red.blue) => #f00,#00f
|
|
110
|
+
*/
|
|
111
|
+
export const modifiedFactory = <TSource, T extends Key>(
|
|
112
|
+
modifiers: readonly T[],
|
|
113
|
+
source: (applied: readonly T[]) => TSource
|
|
114
|
+
) =>
|
|
115
|
+
(function recursiveModifiedFactory(
|
|
116
|
+
applied: readonly T[]
|
|
117
|
+
): Modified<TSource, T> {
|
|
118
|
+
const notApplied = modifiers.filter((v) => !applied.includes(v))
|
|
119
|
+
return factory(source(applied), notApplied, (modifier) =>
|
|
120
|
+
notApplied.length === 0
|
|
121
|
+
? unreachable()
|
|
122
|
+
: recursiveModifiedFactory([...applied, modifier])
|
|
123
|
+
)
|
|
124
|
+
})([])
|
|
125
|
+
|
|
126
|
+
export type Modified<TSource, TModifiers extends Key> = TSource & {
|
|
127
|
+
readonly [key in TModifiers]: Modified<TSource, Exclude<TModifiers, key>>
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* 配列で指定したモディファイア(メソッド)をチェーン可能な再帰オブジェクトを動的に生やす
|
|
132
|
+
*
|
|
133
|
+
* @param modifiers オブジェクトに生やすモディファイヤ一覧
|
|
134
|
+
* @param source 指定されたモディファイヤの一覧から値を生成する関数
|
|
135
|
+
* @param _inferPhantom 関数形式のモディファイヤの引数型を推論するためのメタタイプ(引数の個数に合わせてタプルで指定する)
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
*
|
|
139
|
+
* const o = modifiedArgumentedFactory(['red', 'blue'],
|
|
140
|
+
* modifiers => modifiers.map(([color, alpha]) => hex(color, alpha)).join(',')
|
|
141
|
+
* , {} as [number])
|
|
142
|
+
*
|
|
143
|
+
* console.log(o.red(0.5).blue(1)) => #ff000077,#0000ffff
|
|
144
|
+
*/
|
|
145
|
+
export const modifiedArgumentedFactory = <
|
|
146
|
+
TSource,
|
|
147
|
+
T extends string,
|
|
148
|
+
TArguments extends unknown[]
|
|
149
|
+
>(
|
|
150
|
+
modifiers: readonly T[],
|
|
151
|
+
source: (applied: readonly [T, ...TArguments][]) => TSource,
|
|
152
|
+
..._inferPhantom: TArguments
|
|
153
|
+
) =>
|
|
154
|
+
(function recursiveModifiedFactory(
|
|
155
|
+
applied: readonly [T, ...TArguments][]
|
|
156
|
+
): ModifiedArgumented<TSource, T, TArguments> {
|
|
157
|
+
const notApplied = modifiers.filter(
|
|
158
|
+
(v) => !applied.map(([w]) => w).includes(v)
|
|
159
|
+
)
|
|
160
|
+
return argumentedFactory(
|
|
161
|
+
source(applied),
|
|
162
|
+
notApplied,
|
|
163
|
+
(modifier, ...args: TArguments) =>
|
|
164
|
+
notApplied.length === 0
|
|
165
|
+
? unreachable()
|
|
166
|
+
: recursiveModifiedFactory([...applied, [modifier, ...args]])
|
|
167
|
+
)
|
|
168
|
+
})([])
|
|
169
|
+
|
|
170
|
+
export type ModifiedArgumented<
|
|
171
|
+
TSource,
|
|
172
|
+
TModifiers extends string,
|
|
173
|
+
TArguments extends unknown[]
|
|
174
|
+
> = TSource & {
|
|
175
|
+
readonly [key in TModifiers]: (
|
|
176
|
+
...args: TArguments
|
|
177
|
+
) => ModifiedArgumented<TSource, Exclude<TModifiers, key>, TArguments>
|
|
178
|
+
}
|
package/src/theme.ts
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { StyledTheme } from './types'
|
|
2
|
+
import {
|
|
3
|
+
BorderRadius,
|
|
4
|
+
Spacing,
|
|
5
|
+
Typography,
|
|
6
|
+
borderRadius,
|
|
7
|
+
spacing,
|
|
8
|
+
typography,
|
|
9
|
+
Breakpoint,
|
|
10
|
+
breakpoint,
|
|
11
|
+
} from '@charcoal-ui/foundation'
|
|
12
|
+
import { light as l, dark as d } from '@charcoal-ui/pixiv-theme'
|
|
13
|
+
import { Effect, Material, OpacityEffect, Theme } from '@charcoal-ui/theme'
|
|
14
|
+
import { applyEffect } from '@charcoal-ui/utils'
|
|
15
|
+
|
|
16
|
+
export interface ElementsTheme extends StyledTheme {
|
|
17
|
+
color: Theme['color']
|
|
18
|
+
gradientColor: Theme['gradientColor']
|
|
19
|
+
effect: {
|
|
20
|
+
hover: Effect
|
|
21
|
+
press: Effect
|
|
22
|
+
}
|
|
23
|
+
elementEffect: {
|
|
24
|
+
disabled: OpacityEffect
|
|
25
|
+
}
|
|
26
|
+
spacing: Spacing
|
|
27
|
+
typography: {
|
|
28
|
+
size: Typography
|
|
29
|
+
}
|
|
30
|
+
borderRadius: BorderRadius
|
|
31
|
+
border: {
|
|
32
|
+
default: {
|
|
33
|
+
color: Material
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
outline: {
|
|
37
|
+
default: {
|
|
38
|
+
color: string
|
|
39
|
+
weight: 4
|
|
40
|
+
}
|
|
41
|
+
assertive: {
|
|
42
|
+
color: string
|
|
43
|
+
weight: 4
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
breakpoint: Breakpoint
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const outlineEffect = {
|
|
50
|
+
type: 'opacity',
|
|
51
|
+
opacity: 0.32,
|
|
52
|
+
} as const
|
|
53
|
+
|
|
54
|
+
const common = {
|
|
55
|
+
borderRadius,
|
|
56
|
+
spacing,
|
|
57
|
+
typography: {
|
|
58
|
+
size: typography,
|
|
59
|
+
},
|
|
60
|
+
breakpoint,
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const light: ElementsTheme = {
|
|
64
|
+
color: l.color,
|
|
65
|
+
gradientColor: l.gradientColor,
|
|
66
|
+
effect: l.effect,
|
|
67
|
+
elementEffect: l.elementEffect,
|
|
68
|
+
border: {
|
|
69
|
+
default: {
|
|
70
|
+
color: l.color.border,
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
outline: {
|
|
74
|
+
default: {
|
|
75
|
+
color: applyEffect(l.color.brand, outlineEffect),
|
|
76
|
+
weight: 4,
|
|
77
|
+
},
|
|
78
|
+
assertive: {
|
|
79
|
+
color: applyEffect(l.color.assertive, outlineEffect),
|
|
80
|
+
weight: 4,
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
...common,
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export const dark: ElementsTheme = {
|
|
87
|
+
color: d.color,
|
|
88
|
+
gradientColor: d.gradientColor,
|
|
89
|
+
effect: d.effect,
|
|
90
|
+
elementEffect: d.elementEffect,
|
|
91
|
+
border: {
|
|
92
|
+
default: {
|
|
93
|
+
color: d.color.border,
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
outline: {
|
|
97
|
+
default: {
|
|
98
|
+
color: applyEffect(d.color.brand, outlineEffect),
|
|
99
|
+
weight: 4,
|
|
100
|
+
},
|
|
101
|
+
assertive: {
|
|
102
|
+
color: applyEffect(d.color.assertive, outlineEffect),
|
|
103
|
+
weight: 4,
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
...common,
|
|
107
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { TypographyDescriptor } from '@charcoal-ui/foundation'
|
|
2
|
+
import {
|
|
3
|
+
Effect,
|
|
4
|
+
GradientMaterial,
|
|
5
|
+
Material,
|
|
6
|
+
OpacityEffect,
|
|
7
|
+
} from '@charcoal-ui/theme'
|
|
8
|
+
|
|
9
|
+
export type EffectType = 'hover' | 'press' | 'disabled'
|
|
10
|
+
|
|
11
|
+
export type Key = string | number | symbol
|
|
12
|
+
|
|
13
|
+
export type ColorStyleTheme = {
|
|
14
|
+
[key in Key]: Material
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface StyledTheme {
|
|
18
|
+
color: ColorStyleTheme
|
|
19
|
+
gradientColor: { [key in Key]: GradientMaterial }
|
|
20
|
+
effect: { [key in EffectType]?: Effect }
|
|
21
|
+
elementEffect: { [key in EffectType]?: OpacityEffect }
|
|
22
|
+
spacing: { [key in Key]: number }
|
|
23
|
+
typography: {
|
|
24
|
+
size: { [key in Key]: TypographyDescriptor }
|
|
25
|
+
// TODO
|
|
26
|
+
// weight: { [key in Key]: string }
|
|
27
|
+
// variant: { [key in Key]: string }
|
|
28
|
+
}
|
|
29
|
+
borderRadius: { [key in Key]: number }
|
|
30
|
+
border: {
|
|
31
|
+
[key in Key]: {
|
|
32
|
+
color: Material
|
|
33
|
+
// TODO
|
|
34
|
+
// thickness: number
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
outline: {
|
|
38
|
+
[key in Key]: {
|
|
39
|
+
color: Material
|
|
40
|
+
weight: number
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
package/src/util.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Function used to assert a given code path is unreachable
|
|
3
|
+
*/
|
|
4
|
+
export function unreachable(): never
|
|
5
|
+
/**
|
|
6
|
+
* Function used to assert a given code path is unreachable.
|
|
7
|
+
* Very useful for ensuring switches are exhaustive:
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* switch (a.type) {
|
|
11
|
+
* case Types.A:
|
|
12
|
+
* case Types.B:
|
|
13
|
+
* break
|
|
14
|
+
* default:
|
|
15
|
+
* unreachable(a) // will cause a build error if there was
|
|
16
|
+
* // a Types.C that was not checked
|
|
17
|
+
* }
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* @param value Value to be asserted as unreachable
|
|
21
|
+
*/
|
|
22
|
+
// NOTE: Uses separate overloads, _not_ `value?: never`, to not allow `undefined` to be passed
|
|
23
|
+
// eslint-disable-next-line @typescript-eslint/unified-signatures
|
|
24
|
+
export function unreachable(value: never): never
|
|
25
|
+
export function unreachable(value?: never): never {
|
|
26
|
+
throw new Error(
|
|
27
|
+
arguments.length === 0
|
|
28
|
+
? 'unreachable'
|
|
29
|
+
: `unreachable (${JSON.stringify(value)})`
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Check whether a value is non-null and non-undefined
|
|
35
|
+
*
|
|
36
|
+
* @param value nullable
|
|
37
|
+
*/
|
|
38
|
+
export const isPresent = <T>(value: T): value is NonNullable<T> => value != null
|
|
39
|
+
|
|
40
|
+
type Head<U> = U extends [infer T, ...any[]] ? T : never
|
|
41
|
+
|
|
42
|
+
type Tail<U> = U extends [any, any, ...any[]]
|
|
43
|
+
? ((...args: U) => any) extends (head: any, ...args: infer T) => any
|
|
44
|
+
? T
|
|
45
|
+
: never
|
|
46
|
+
: never
|
|
47
|
+
// Buggy at ts@4.0.0-dev20200506
|
|
48
|
+
// type Tail<U> = U extends [any, ...infer T] ? T : never
|
|
49
|
+
|
|
50
|
+
type RecursiveObjectAssign<T, S extends any[]> = {
|
|
51
|
+
0: T & Head<S>
|
|
52
|
+
1: RecursiveObjectAssign<T & Head<S>, Tail<S>>
|
|
53
|
+
}[Tail<S> extends never ? 0 : 1]
|
|
54
|
+
|
|
55
|
+
type ObjectAssign<T extends any[]> = RecursiveObjectAssign<
|
|
56
|
+
Record<string, unknown>,
|
|
57
|
+
T
|
|
58
|
+
>
|
|
59
|
+
|
|
60
|
+
export function objectAssign<T extends any[]>(...sources: T) {
|
|
61
|
+
return Object.assign({}, ...sources) as ObjectAssign<T>
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function objectKeys<V, K extends keyof V>(obj: V) {
|
|
65
|
+
return Object.keys(obj) as K[]
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface ReadonlyArrayConstructor {
|
|
69
|
+
isArray(value: any): value is readonly any[]
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function extractNonNullKeys<V, K extends keyof V>(obj: {
|
|
73
|
+
[key in K]: V[key]
|
|
74
|
+
}) {
|
|
75
|
+
return Object.entries(obj)
|
|
76
|
+
.filter(([_, v]) => v !== null)
|
|
77
|
+
.map(([k]) => k) as { [key in K]: V[key] extends null ? never : key }[K][]
|
|
78
|
+
}
|