@shapesos/clay 0.3.0 → 0.4.0
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/README.md +198 -73
- package/dist/index.cjs +28 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -1
- package/dist/index.d.ts +10 -1
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -1,45 +1,72 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
# Clay
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**Tokens, theming, and composable React components.**
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/@shapesos/clay)
|
|
8
|
+
[](LICENSE)
|
|
9
|
+
[](https://bundlephobia.com/package/@shapesos/clay)
|
|
10
|
+
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
6
16
|
|
|
7
17
|
```bash
|
|
8
|
-
|
|
18
|
+
npm install @shapesos/clay
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
import { colors, typographyTypes } from "@shapesos/clay/tokens";
|
|
23
|
+
import { Chat } from "@shapesos/clay/chat";
|
|
24
|
+
import { Lottie } from "@shapesos/clay/lottie";
|
|
9
25
|
```
|
|
10
26
|
|
|
11
27
|
## Entry Points
|
|
12
28
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
|
16
|
-
|
|
|
17
|
-
| `@
|
|
18
|
-
| `@
|
|
29
|
+
Clay is tree-shakeable with multiple entry points — import only what you need.
|
|
30
|
+
|
|
31
|
+
| Import | Contents | Peer Dependencies |
|
|
32
|
+
| ------------------------- | ------------------------------------- | ------------------------ |
|
|
33
|
+
| `@shapesos/clay/tokens` | Colors, typography, font families | None (pure JS) |
|
|
34
|
+
| `@shapesos/clay/chat` | Chat compound components + types | React, styled-components |
|
|
35
|
+
| `@shapesos/clay/icon` | Icon, IconButton components + types | React, styled-components |
|
|
36
|
+
| `@shapesos/clay/lottie` | Lottie animation component + types | React, styled-components |
|
|
37
|
+
| `@shapesos/clay` | Everything (convenience re-export) | React, styled-components |
|
|
38
|
+
|
|
39
|
+
---
|
|
19
40
|
|
|
20
|
-
##
|
|
41
|
+
## Design Tokens
|
|
21
42
|
|
|
22
|
-
|
|
43
|
+
Pure JavaScript color and typography tokens — no React required.
|
|
23
44
|
|
|
24
45
|
```typescript
|
|
25
|
-
import { colors, typographyStyles, typographyTypes, typographyMixin } from "@
|
|
46
|
+
import { colors, typographyStyles, typographyTypes, typographyMixin } from "@shapesos/clay/tokens";
|
|
26
47
|
|
|
27
|
-
|
|
48
|
+
// Color palette
|
|
49
|
+
colors["brown-100"]; // "#171716"
|
|
50
|
+
colors["brown-900"]; // "#F7F5F3"
|
|
28
51
|
|
|
52
|
+
// Typography definitions
|
|
29
53
|
typographyStyles[typographyTypes.GEIST_BODY_S_REGULAR];
|
|
30
|
-
// { fontFamily: "Geist", fontSize: 16, fontWeight: 400, lineHeight: 24, letterSpacing: -0.08 }
|
|
54
|
+
// => { fontFamily: "Geist", fontSize: 16, fontWeight: 400, lineHeight: 24, letterSpacing: -0.08 }
|
|
31
55
|
|
|
56
|
+
// CSS mixin for styled-components
|
|
32
57
|
typographyMixin(typographyTypes.GEIST_BODY_XS_MEDIUM);
|
|
33
|
-
// CSS string
|
|
58
|
+
// => CSS string ready to use in template literals
|
|
34
59
|
```
|
|
35
60
|
|
|
36
|
-
|
|
61
|
+
---
|
|
37
62
|
|
|
38
|
-
|
|
63
|
+
## Chat
|
|
64
|
+
|
|
65
|
+
Composable compound components for building chat interfaces. `Chat.Root` provides context — children consume it, giving you full control over layout.
|
|
39
66
|
|
|
40
67
|
```tsx
|
|
41
|
-
import { Chat } from "@
|
|
42
|
-
import type { ChatMessage } from "@
|
|
68
|
+
import { Chat } from "@shapesos/clay/chat";
|
|
69
|
+
import type { ChatMessage } from "@shapesos/clay/chat";
|
|
43
70
|
|
|
44
71
|
function MyChat() {
|
|
45
72
|
const [messages, setMessages] = useState<ChatMessage[]>([]);
|
|
@@ -62,29 +89,30 @@ function MyChat() {
|
|
|
62
89
|
}
|
|
63
90
|
```
|
|
64
91
|
|
|
65
|
-
|
|
92
|
+
<details>
|
|
93
|
+
<summary><strong>Props Reference</strong></summary>
|
|
66
94
|
|
|
67
|
-
####
|
|
95
|
+
#### Chat.Root
|
|
68
96
|
|
|
69
|
-
|
|
97
|
+
| Prop | Type | Required | Description |
|
|
98
|
+
| ------------------ | ---------------------------------------------------------- | -------- | ---------------------------------- |
|
|
99
|
+
| `messages` | `ChatMessage[]` | Yes | Array of messages to display |
|
|
100
|
+
| `onSendMessage` | `(content: string) => void` | Yes | Called when the user sends |
|
|
101
|
+
| `isLoading` | `boolean` | No | Show stop button while loading |
|
|
102
|
+
| `onStop` | `() => void` | No | Called when the user clicks stop |
|
|
103
|
+
| `onCopyMessage` | `(messageId: string) => void` | No | Called when a message is copied |
|
|
104
|
+
| `onThumbUpClick` | `(messageId: string, isHelpful: boolean \| null) => void` | No | Thumbs up feedback |
|
|
105
|
+
| `onThumbDownClick` | `(messageId: string, isHelpful: boolean \| null) => void` | No | Thumbs down feedback |
|
|
70
106
|
|
|
71
|
-
|
|
72
|
-
| ------------------ | ---------------------------------------------------------- | -------- | ----------------------------------------------- |
|
|
73
|
-
| `messages` | `ChatMessage[]` | Yes | Array of messages to display |
|
|
74
|
-
| `onSendMessage` | `(content: string) => void` | Yes | Called when the user sends a message |
|
|
75
|
-
| `isLoading` | `boolean` | No | Show stop button while loading |
|
|
76
|
-
| `onStop` | `() => void` | No | Called when the user clicks stop |
|
|
77
|
-
| `onCopyMessage` | `(messageId: string) => void` | No | Called when a message is copied |
|
|
78
|
-
| `onThumbUpClick` | `(messageId: string, isHelpful: boolean \| null) => void` | No | Called when thumbs up is clicked |
|
|
79
|
-
| `onThumbDownClick` | `(messageId: string, isHelpful: boolean \| null) => void` | No | Called when thumbs down is clicked |
|
|
107
|
+
#### Chat.Composer
|
|
80
108
|
|
|
81
|
-
|
|
109
|
+
| Prop | Type | Required | Default | Description |
|
|
110
|
+
| ------------- | -------- | -------- | ---------------------- | ----------------- |
|
|
111
|
+
| `placeholder` | `string` | No | `"Type a message..."` | Input placeholder |
|
|
82
112
|
|
|
83
|
-
|
|
84
|
-
| ------------- | -------- | -------- | -------------------- | ------------------ |
|
|
85
|
-
| `placeholder` | `string` | No | `"Type a message..."` | Input placeholder |
|
|
113
|
+
#### Chat.MessageList
|
|
86
114
|
|
|
87
|
-
|
|
115
|
+
No props — reads messages from context.
|
|
88
116
|
|
|
89
117
|
#### Data Model
|
|
90
118
|
|
|
@@ -94,15 +122,21 @@ type MessageRole = "user" | "assistant";
|
|
|
94
122
|
interface ChatMessage {
|
|
95
123
|
id: string;
|
|
96
124
|
role: MessageRole;
|
|
97
|
-
content: string;
|
|
125
|
+
content: string; // Markdown supported
|
|
98
126
|
isHelpful?: boolean | null;
|
|
99
127
|
}
|
|
100
128
|
```
|
|
101
129
|
|
|
102
|
-
|
|
130
|
+
</details>
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Icon
|
|
135
|
+
|
|
136
|
+
Render SVG icons with consistent sizing, and icon buttons with selection states.
|
|
103
137
|
|
|
104
138
|
```tsx
|
|
105
|
-
import { Icon, IconButton } from "@
|
|
139
|
+
import { Icon, IconButton } from "@shapesos/clay/icon";
|
|
106
140
|
import { IconSearch, IconCopy } from "@tabler/icons-react";
|
|
107
141
|
|
|
108
142
|
<Icon icon={IconSearch} size={20} color="#333" aria-label="Search" />
|
|
@@ -110,38 +144,121 @@ import { IconSearch, IconCopy } from "@tabler/icons-react";
|
|
|
110
144
|
<IconButton icon={IconCopy} size="small" onClick={handleCopy} aria-label="Copy" />
|
|
111
145
|
```
|
|
112
146
|
|
|
113
|
-
|
|
147
|
+
<details>
|
|
148
|
+
<summary><strong>Props Reference</strong></summary>
|
|
114
149
|
|
|
115
|
-
|
|
150
|
+
#### Icon
|
|
116
151
|
|
|
117
|
-
| Prop | Type | Required | Default | Description
|
|
118
|
-
| ------------ | ---------------------------------------- | -------- | ------- |
|
|
152
|
+
| Prop | Type | Required | Default | Description |
|
|
153
|
+
| ------------ | ---------------------------------------- | -------- | ------- | ------------------------ |
|
|
119
154
|
| `icon` | `ComponentType<SVGProps<SVGSVGElement>>` | Yes | | Icon component to render |
|
|
120
|
-
| `size` | `number` | No | `16` |
|
|
155
|
+
| `size` | `number` | No | `16` | Size in pixels |
|
|
121
156
|
| `color` | `string` | No | | Icon color |
|
|
122
157
|
| `className` | `string` | No | | CSS class |
|
|
123
158
|
| `aria-label` | `string` | No | | Accessible label |
|
|
124
159
|
|
|
125
|
-
|
|
160
|
+
#### IconButton
|
|
161
|
+
|
|
162
|
+
| Prop | Type | Required | Default | Description |
|
|
163
|
+
| ------------ | ---------------------------------------- | -------- | --------- | ------------------------ |
|
|
164
|
+
| `icon` | `ComponentType<SVGProps<SVGSVGElement>>` | Yes | | Icon component to render |
|
|
165
|
+
| `size` | `"small" \| "medium"` | No | `"small"` | Button size |
|
|
166
|
+
| `isSelected` | `boolean` | No | `false` | Selected state |
|
|
167
|
+
| `disabled` | `boolean` | No | `false` | Disabled state |
|
|
168
|
+
| `onClick` | `() => void` | No | | Click handler |
|
|
169
|
+
| `className` | `string` | No | | CSS class |
|
|
170
|
+
| `aria-label` | `string` | No | | Accessible label |
|
|
171
|
+
|
|
172
|
+
</details>
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Lottie
|
|
126
177
|
|
|
127
|
-
|
|
128
|
-
| ------------ | ---------------------------------------- | -------- | --------- | -------------------------- |
|
|
129
|
-
| `icon` | `ComponentType<SVGProps<SVGSVGElement>>` | Yes | | Icon component to render |
|
|
130
|
-
| `size` | `"small" \| "medium"` | No | `"small"` | Button size |
|
|
131
|
-
| `isSelected` | `boolean` | No | `false` | Selected/active state |
|
|
132
|
-
| `disabled` | `boolean` | No | `false` | Disabled state |
|
|
133
|
-
| `onClick` | `() => void` | No | | Click handler |
|
|
134
|
-
| `className` | `string` | No | | CSS class |
|
|
135
|
-
| `aria-label` | `string` | No | | Accessible label |
|
|
178
|
+
Render [Lottie](https://airbnb.io/lottie/) animations with declarative props, hover interactions, and imperative playback control.
|
|
136
179
|
|
|
137
|
-
|
|
180
|
+
```tsx
|
|
181
|
+
import { Lottie } from "@shapesos/clay/lottie";
|
|
182
|
+
import animationData from "./my-animation.json";
|
|
183
|
+
|
|
184
|
+
// Basic — loops forever
|
|
185
|
+
<Lottie animationData={animationData} />
|
|
186
|
+
|
|
187
|
+
// Interactive — plays once, then replays on hover
|
|
188
|
+
<Lottie
|
|
189
|
+
animationData={animationData}
|
|
190
|
+
loop={false}
|
|
191
|
+
playOnHover
|
|
192
|
+
loopOnHover
|
|
193
|
+
width={80}
|
|
194
|
+
height={80}
|
|
195
|
+
/>
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
**Imperative control** via ref:
|
|
199
|
+
|
|
200
|
+
```tsx
|
|
201
|
+
import type { LottieRef } from "@shapesos/clay/lottie";
|
|
202
|
+
|
|
203
|
+
const ref = useRef<LottieRef>(null);
|
|
204
|
+
|
|
205
|
+
<Lottie ref={ref} animationData={animationData} autoplay={false} />
|
|
206
|
+
<button onClick={() => ref.current?.play()}>Play</button>
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
<details>
|
|
210
|
+
<summary><strong>Props Reference</strong></summary>
|
|
211
|
+
|
|
212
|
+
#### Lottie
|
|
213
|
+
|
|
214
|
+
| Prop | Type | Required | Default | Description |
|
|
215
|
+
| ------------------- | ---------------------------------------------------------------- | -------- | ------- | ------------------------------ |
|
|
216
|
+
| `animationData` | `LottieAnimationData` | Yes | | Lottie JSON data |
|
|
217
|
+
| `autoplay` | `boolean` | No | `true` | Play on mount |
|
|
218
|
+
| `loop` | `boolean \| number` | No | `true` | Loop forever, or N times |
|
|
219
|
+
| `speed` | `number` | No | `1` | Playback speed |
|
|
220
|
+
| `direction` | `1 \| -1` | No | `1` | Forward or reverse |
|
|
221
|
+
| `width` | `string \| number` | No | | Container width |
|
|
222
|
+
| `height` | `string \| number` | No | | Container height |
|
|
223
|
+
| `playOnHover` | `boolean` | No | `false` | Replay on mouse enter |
|
|
224
|
+
| `loopOnHover` | `boolean` | No | `false` | Loop while hovering |
|
|
225
|
+
| `className` | `string` | No | | CSS class |
|
|
226
|
+
| `aria-label` | `string` | No | | Accessible label |
|
|
227
|
+
| `onAnimationLoaded` | `(animation: AnimationItem) => void` | No | | Fired when animation loads |
|
|
228
|
+
| `onComplete` | `() => void` | No | | Fired on completion |
|
|
229
|
+
| `onLoopComplete` | `() => void` | No | | Fired on each loop |
|
|
230
|
+
| `onEnterFrame` | `(frame: { currentTime: number; totalTime: number }) => void` | No | | Fired on each frame |
|
|
231
|
+
|
|
232
|
+
#### LottieRef (imperative handle)
|
|
233
|
+
|
|
234
|
+
| Method | Description |
|
|
235
|
+
| ---------------------------------- | ---------------------------------- |
|
|
236
|
+
| `play()` | Start playback |
|
|
237
|
+
| `pause()` | Pause playback |
|
|
238
|
+
| `stop()` | Stop and reset to frame 0 |
|
|
239
|
+
| `goToAndPlay(value, isFrame?)` | Jump to value and play |
|
|
240
|
+
| `goToAndStop(value, isFrame?)` | Jump to value and stop |
|
|
241
|
+
| `setDirection(direction)` | Set direction (1 or -1) |
|
|
242
|
+
| `setSpeed(speed)` | Set playback speed |
|
|
243
|
+
| `getAnimationItem()` | Access underlying lottie-web instance |
|
|
244
|
+
|
|
245
|
+
</details>
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## TypeScript
|
|
250
|
+
|
|
251
|
+
All components export their prop types:
|
|
138
252
|
|
|
139
253
|
```typescript
|
|
140
|
-
import type { ColorToken, TypographyStyle, TypographyType } from "@
|
|
141
|
-
import type { ChatMessage, MessageRole, ChatContextValue } from "@
|
|
142
|
-
import type { IconProps, IconButtonProps, IconButtonSize } from "@
|
|
254
|
+
import type { ColorToken, TypographyStyle, TypographyType } from "@shapesos/clay/tokens";
|
|
255
|
+
import type { ChatMessage, MessageRole, ChatContextValue } from "@shapesos/clay/chat";
|
|
256
|
+
import type { IconProps, IconButtonProps, IconButtonSize } from "@shapesos/clay/icon";
|
|
257
|
+
import type { LottieProps, LottieRef, LottieAnimationData } from "@shapesos/clay/lottie";
|
|
143
258
|
```
|
|
144
259
|
|
|
260
|
+
---
|
|
261
|
+
|
|
145
262
|
## Development
|
|
146
263
|
|
|
147
264
|
```bash
|
|
@@ -150,20 +267,28 @@ cd shapes-clay
|
|
|
150
267
|
bun install
|
|
151
268
|
```
|
|
152
269
|
|
|
153
|
-
| Command
|
|
154
|
-
|
|
|
155
|
-
| `bun run storybook`
|
|
156
|
-
| `bun run build`
|
|
157
|
-
| `bun run dev`
|
|
158
|
-
| `bun run test`
|
|
159
|
-
| `bun run test:coverage`
|
|
160
|
-
| `bun run test:e2e`
|
|
161
|
-
| `bun run
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
270
|
+
| Command | Description |
|
|
271
|
+
| ----------------------- | ------------------------------------------- |
|
|
272
|
+
| `bun run storybook` | Component playground (localhost:6006) |
|
|
273
|
+
| `bun run build` | Build package (tsup) |
|
|
274
|
+
| `bun run dev` | Build in watch mode |
|
|
275
|
+
| `bun run test` | Unit tests (Vitest) |
|
|
276
|
+
| `bun run test:coverage` | Unit tests with coverage |
|
|
277
|
+
| `bun run test:e2e` | E2E tests (Playwright + Storybook) |
|
|
278
|
+
| `bun run check` | All quality gates (lint + typecheck + test) |
|
|
279
|
+
|
|
280
|
+
### Releasing
|
|
281
|
+
|
|
282
|
+
Clay uses [changesets](https://github.com/changesets/changesets) for versioning. When making changes:
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
bunx changeset # Describe your change and its semver impact
|
|
286
|
+
git add .changeset/ # Commit the changeset with your PR
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
On merge to `master`, a "Version Packages" PR is created automatically. Merging that PR publishes to npm.
|
|
290
|
+
|
|
291
|
+
---
|
|
167
292
|
|
|
168
293
|
## License
|
|
169
294
|
|
package/dist/index.cjs
CHANGED
|
@@ -16345,6 +16345,7 @@ __export(src_exports, {
|
|
|
16345
16345
|
Chat: () => Chat,
|
|
16346
16346
|
Icon: () => Icon,
|
|
16347
16347
|
IconButton: () => IconButton,
|
|
16348
|
+
KeyboardShortcut: () => KeyboardShortcut,
|
|
16348
16349
|
Lottie: () => Lottie,
|
|
16349
16350
|
colors: () => colors,
|
|
16350
16351
|
fontFamilies: () => fontFamilies,
|
|
@@ -17411,12 +17412,34 @@ var Chat = {
|
|
|
17411
17412
|
Composer: ChatComposer
|
|
17412
17413
|
};
|
|
17413
17414
|
|
|
17415
|
+
// src/keyboard-shortcut/keyboard-shortcut-styles.ts
|
|
17416
|
+
var import_styled_components9 = __toESM(require("styled-components"), 1);
|
|
17417
|
+
var Wrapper = import_styled_components9.default.div`
|
|
17418
|
+
display: inline-flex;
|
|
17419
|
+
align-items: center;
|
|
17420
|
+
gap: 4px;
|
|
17421
|
+
`;
|
|
17422
|
+
var Key = import_styled_components9.default.kbd`
|
|
17423
|
+
padding: 1px 4px;
|
|
17424
|
+
border-radius: 4px;
|
|
17425
|
+
border: 1px solid ${colors["brown-70"]};
|
|
17426
|
+
border-bottom-width: 2px;
|
|
17427
|
+
${typographyMixin(typographyTypes.GEIST_LABEL_CAPTION_MEDIUM)};
|
|
17428
|
+
color: inherit;
|
|
17429
|
+
`;
|
|
17430
|
+
|
|
17431
|
+
// src/keyboard-shortcut/keyboard-shortcut.tsx
|
|
17432
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
17433
|
+
function KeyboardShortcut({ keys, className }) {
|
|
17434
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Wrapper, { className, children: keys.map((key2) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Key, { children: key2 }, key2)) });
|
|
17435
|
+
}
|
|
17436
|
+
|
|
17414
17437
|
// src/lottie/lottie.tsx
|
|
17415
17438
|
var import_react11 = require("react");
|
|
17416
17439
|
|
|
17417
17440
|
// src/lottie/lottie-styles.ts
|
|
17418
|
-
var
|
|
17419
|
-
var LottieContainer =
|
|
17441
|
+
var import_styled_components10 = __toESM(require("styled-components"), 1);
|
|
17442
|
+
var LottieContainer = import_styled_components10.default.div`
|
|
17420
17443
|
display: inline-flex;
|
|
17421
17444
|
width: ${({ $width }) => typeof $width === "number" ? `${$width}px` : $width};
|
|
17422
17445
|
height: ${({ $height }) => typeof $height === "number" ? `${$height}px` : $height};
|
|
@@ -17545,7 +17568,7 @@ function createLottieRef(animationRef) {
|
|
|
17545
17568
|
}
|
|
17546
17569
|
|
|
17547
17570
|
// src/lottie/lottie.tsx
|
|
17548
|
-
var
|
|
17571
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
17549
17572
|
var Lottie = (0, import_react11.forwardRef)(function Lottie2({
|
|
17550
17573
|
animationData: animationData2,
|
|
17551
17574
|
autoplay,
|
|
@@ -17577,7 +17600,7 @@ var Lottie = (0, import_react11.forwardRef)(function Lottie2({
|
|
|
17577
17600
|
onEnterFrame
|
|
17578
17601
|
});
|
|
17579
17602
|
(0, import_react11.useImperativeHandle)(ref, () => createLottieRef(animationRef), [animationRef]);
|
|
17580
|
-
return /* @__PURE__ */ (0,
|
|
17603
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
17581
17604
|
LottieContainer,
|
|
17582
17605
|
{
|
|
17583
17606
|
ref: containerRef,
|
|
@@ -17594,6 +17617,7 @@ var Lottie = (0, import_react11.forwardRef)(function Lottie2({
|
|
|
17594
17617
|
Chat,
|
|
17595
17618
|
Icon,
|
|
17596
17619
|
IconButton,
|
|
17620
|
+
KeyboardShortcut,
|
|
17597
17621
|
Lottie,
|
|
17598
17622
|
colors,
|
|
17599
17623
|
fontFamilies,
|