@furystack/shades-common-components 12.2.0 → 12.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/CHANGELOG.md +56 -0
- package/esm/components/form.d.ts +5 -2
- package/esm/components/form.d.ts.map +1 -1
- package/esm/components/form.js +28 -6
- package/esm/components/form.js.map +1 -1
- package/esm/components/form.spec.js +207 -0
- package/esm/components/form.spec.js.map +1 -1
- package/esm/components/index.d.ts +1 -0
- package/esm/components/index.d.ts.map +1 -1
- package/esm/components/index.js +1 -0
- package/esm/components/index.js.map +1 -1
- package/esm/components/markdown/index.d.ts +5 -0
- package/esm/components/markdown/index.d.ts.map +1 -0
- package/esm/components/markdown/index.js +5 -0
- package/esm/components/markdown/index.js.map +1 -0
- package/esm/components/markdown/markdown-display.d.ts +19 -0
- package/esm/components/markdown/markdown-display.d.ts.map +1 -0
- package/esm/components/markdown/markdown-display.js +149 -0
- package/esm/components/markdown/markdown-display.js.map +1 -0
- package/esm/components/markdown/markdown-display.spec.d.ts +2 -0
- package/esm/components/markdown/markdown-display.spec.d.ts.map +1 -0
- package/esm/components/markdown/markdown-display.spec.js +191 -0
- package/esm/components/markdown/markdown-display.spec.js.map +1 -0
- package/esm/components/markdown/markdown-editor.d.ts +25 -0
- package/esm/components/markdown/markdown-editor.d.ts.map +1 -0
- package/esm/components/markdown/markdown-editor.js +113 -0
- package/esm/components/markdown/markdown-editor.js.map +1 -0
- package/esm/components/markdown/markdown-editor.spec.d.ts +2 -0
- package/esm/components/markdown/markdown-editor.spec.d.ts.map +1 -0
- package/esm/components/markdown/markdown-editor.spec.js +111 -0
- package/esm/components/markdown/markdown-editor.spec.js.map +1 -0
- package/esm/components/markdown/markdown-input.d.ts +29 -0
- package/esm/components/markdown/markdown-input.d.ts.map +1 -0
- package/esm/components/markdown/markdown-input.js +100 -0
- package/esm/components/markdown/markdown-input.js.map +1 -0
- package/esm/components/markdown/markdown-input.spec.d.ts +2 -0
- package/esm/components/markdown/markdown-input.spec.d.ts.map +1 -0
- package/esm/components/markdown/markdown-input.spec.js +215 -0
- package/esm/components/markdown/markdown-input.spec.js.map +1 -0
- package/esm/components/markdown/markdown-parser.d.ts +82 -0
- package/esm/components/markdown/markdown-parser.d.ts.map +1 -0
- package/esm/components/markdown/markdown-parser.js +274 -0
- package/esm/components/markdown/markdown-parser.js.map +1 -0
- package/esm/components/markdown/markdown-parser.spec.d.ts +2 -0
- package/esm/components/markdown/markdown-parser.spec.d.ts.map +1 -0
- package/esm/components/markdown/markdown-parser.spec.js +229 -0
- package/esm/components/markdown/markdown-parser.spec.js.map +1 -0
- package/esm/components/styles.d.ts +1 -0
- package/esm/components/styles.d.ts.map +1 -1
- package/esm/components/styles.js.map +1 -1
- package/esm/components/typography.d.ts.map +1 -1
- package/esm/components/typography.js +26 -14
- package/esm/components/typography.js.map +1 -1
- package/esm/services/css-variable-theme.d.ts +3 -0
- package/esm/services/css-variable-theme.d.ts.map +1 -1
- package/esm/services/css-variable-theme.js +3 -0
- package/esm/services/css-variable-theme.js.map +1 -1
- package/esm/services/css-variable-theme.spec.js +3 -0
- package/esm/services/css-variable-theme.spec.js.map +1 -1
- package/esm/services/default-dark-palette.d.ts +8 -0
- package/esm/services/default-dark-palette.d.ts.map +1 -0
- package/esm/services/default-dark-palette.js +56 -0
- package/esm/services/default-dark-palette.js.map +1 -0
- package/esm/services/default-dark-theme.d.ts +3 -0
- package/esm/services/default-dark-theme.d.ts.map +1 -1
- package/esm/services/default-dark-theme.js +7 -4
- package/esm/services/default-dark-theme.js.map +1 -1
- package/esm/services/default-light-theme.d.ts +3 -0
- package/esm/services/default-light-theme.d.ts.map +1 -1
- package/esm/services/default-light-theme.js +3 -0
- package/esm/services/default-light-theme.js.map +1 -1
- package/esm/services/index.d.ts +1 -0
- package/esm/services/index.d.ts.map +1 -1
- package/esm/services/index.js +1 -0
- package/esm/services/index.js.map +1 -1
- package/esm/services/theme-provider-service.d.ts +10 -1
- package/esm/services/theme-provider-service.d.ts.map +1 -1
- package/esm/services/theme-provider-service.js.map +1 -1
- package/package.json +2 -2
- package/src/components/form.spec.tsx +309 -0
- package/src/components/form.tsx +31 -8
- package/src/components/index.ts +1 -0
- package/src/components/markdown/index.ts +4 -0
- package/src/components/markdown/markdown-display.spec.tsx +243 -0
- package/src/components/markdown/markdown-display.tsx +202 -0
- package/src/components/markdown/markdown-editor.spec.tsx +142 -0
- package/src/components/markdown/markdown-editor.tsx +167 -0
- package/src/components/markdown/markdown-input.spec.tsx +274 -0
- package/src/components/markdown/markdown-input.tsx +143 -0
- package/src/components/markdown/markdown-parser.spec.ts +258 -0
- package/src/components/markdown/markdown-parser.ts +333 -0
- package/src/components/styles.tsx +1 -0
- package/src/components/typography.tsx +28 -15
- package/src/services/css-variable-theme.spec.ts +3 -0
- package/src/services/css-variable-theme.ts +3 -0
- package/src/services/default-dark-palette.ts +57 -0
- package/src/services/default-dark-theme.ts +7 -4
- package/src/services/default-light-theme.ts +3 -0
- package/src/services/index.ts +1 -0
- package/src/services/theme-provider-service.ts +7 -1
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zero-dependency Markdown parser that converts a Markdown string into an AST.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export type MarkdownNode = HeadingNode | ParagraphNode | CodeBlockNode | BlockquoteNode | ListNode | HorizontalRuleNode
|
|
6
|
+
|
|
7
|
+
export type HeadingNode = {
|
|
8
|
+
type: 'heading'
|
|
9
|
+
level: 1 | 2 | 3 | 4 | 5 | 6
|
|
10
|
+
children: InlineNode[]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type ParagraphNode = {
|
|
14
|
+
type: 'paragraph'
|
|
15
|
+
children: InlineNode[]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type CodeBlockNode = {
|
|
19
|
+
type: 'codeBlock'
|
|
20
|
+
language?: string
|
|
21
|
+
content: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type BlockquoteNode = {
|
|
25
|
+
type: 'blockquote'
|
|
26
|
+
children: MarkdownNode[]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type ListNode = {
|
|
30
|
+
type: 'list'
|
|
31
|
+
ordered: boolean
|
|
32
|
+
items: ListItemNode[]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type HorizontalRuleNode = {
|
|
36
|
+
type: 'horizontalRule'
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type ListItemNode = {
|
|
40
|
+
children: InlineNode[]
|
|
41
|
+
checkbox?: 'checked' | 'unchecked'
|
|
42
|
+
sourceLineIndex: number
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type InlineNode = TextNode | BoldNode | ItalicNode | InlineCodeNode | LinkNode | ImageNode
|
|
46
|
+
|
|
47
|
+
export type TextNode = { type: 'text'; content: string }
|
|
48
|
+
export type BoldNode = { type: 'bold'; children: InlineNode[] }
|
|
49
|
+
export type ItalicNode = { type: 'italic'; children: InlineNode[] }
|
|
50
|
+
export type InlineCodeNode = { type: 'code'; content: string }
|
|
51
|
+
export type LinkNode = { type: 'link'; href: string; children: InlineNode[] }
|
|
52
|
+
export type ImageNode = { type: 'image'; src: string; alt: string }
|
|
53
|
+
|
|
54
|
+
const HORIZONTAL_RULE_RE = /^(\*{3,}|-{3,}|_{3,})\s*$/
|
|
55
|
+
const HEADING_RE = /^(#{1,6})\s+(.*)/
|
|
56
|
+
const FENCED_CODE_OPEN_RE = /^```(\w*)\s*$/
|
|
57
|
+
const FENCED_CODE_CLOSE_RE = /^```\s*$/
|
|
58
|
+
const UNORDERED_LIST_RE = /^(\s*)[-*]\s+(.*)/
|
|
59
|
+
const ORDERED_LIST_RE = /^(\s*)\d+\.\s+(.*)/
|
|
60
|
+
const BLOCKQUOTE_RE = /^>\s?(.*)/
|
|
61
|
+
const CHECKBOX_UNCHECKED_RE = /^\[[ ]\]\s+(.*)/
|
|
62
|
+
const CHECKBOX_CHECKED_RE = /^\[[xX]\]\s+(.*)/
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Parse inline Markdown formatting into an array of InlineNodes.
|
|
66
|
+
*
|
|
67
|
+
* Known limitations:
|
|
68
|
+
* - Backslash escapes (e.g. `\*`) are not supported; special characters are always interpreted.
|
|
69
|
+
* - Underscore `_` markers are not restricted to word boundaries, so identifiers like
|
|
70
|
+
* `some_variable_name` may produce false italic/bold matches.
|
|
71
|
+
*/
|
|
72
|
+
export const parseInline = (text: string): InlineNode[] => {
|
|
73
|
+
const nodes: InlineNode[] = []
|
|
74
|
+
let pos = 0
|
|
75
|
+
|
|
76
|
+
while (pos < text.length) {
|
|
77
|
+
// Inline code
|
|
78
|
+
if (text[pos] === '`') {
|
|
79
|
+
const closeIdx = text.indexOf('`', pos + 1)
|
|
80
|
+
if (closeIdx !== -1) {
|
|
81
|
+
nodes.push({ type: 'code', content: text.slice(pos + 1, closeIdx) })
|
|
82
|
+
pos = closeIdx + 1
|
|
83
|
+
continue
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Image 
|
|
88
|
+
if (text[pos] === '!' && text[pos + 1] === '[') {
|
|
89
|
+
const altClose = text.indexOf(']', pos + 2)
|
|
90
|
+
if (altClose !== -1 && text[altClose + 1] === '(') {
|
|
91
|
+
const srcClose = text.indexOf(')', altClose + 2)
|
|
92
|
+
if (srcClose !== -1) {
|
|
93
|
+
const alt = text.slice(pos + 2, altClose)
|
|
94
|
+
const src = text.slice(altClose + 2, srcClose)
|
|
95
|
+
nodes.push({ type: 'image', src, alt })
|
|
96
|
+
pos = srcClose + 1
|
|
97
|
+
continue
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Link [text](href)
|
|
103
|
+
if (text[pos] === '[') {
|
|
104
|
+
const textClose = text.indexOf(']', pos + 1)
|
|
105
|
+
if (textClose !== -1 && text[textClose + 1] === '(') {
|
|
106
|
+
const hrefClose = text.indexOf(')', textClose + 2)
|
|
107
|
+
if (hrefClose !== -1) {
|
|
108
|
+
const linkText = text.slice(pos + 1, textClose)
|
|
109
|
+
const href = text.slice(textClose + 2, hrefClose)
|
|
110
|
+
nodes.push({ type: 'link', href, children: parseInline(linkText) })
|
|
111
|
+
pos = hrefClose + 1
|
|
112
|
+
continue
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Bold+Italic (***text***) or Bold (**text**) or Italic (*text*)
|
|
118
|
+
if (text[pos] === '*' || text[pos] === '_') {
|
|
119
|
+
const marker = text[pos]
|
|
120
|
+
|
|
121
|
+
// Count consecutive markers
|
|
122
|
+
let markerCount = 0
|
|
123
|
+
while (pos + markerCount < text.length && text[pos + markerCount] === marker) {
|
|
124
|
+
markerCount++
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (markerCount >= 3) {
|
|
128
|
+
const closeIdx = text.indexOf(marker.repeat(3), pos + 3)
|
|
129
|
+
if (closeIdx !== -1) {
|
|
130
|
+
const inner = text.slice(pos + 3, closeIdx)
|
|
131
|
+
nodes.push({ type: 'bold', children: [{ type: 'italic', children: parseInline(inner) }] })
|
|
132
|
+
pos = closeIdx + 3
|
|
133
|
+
continue
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (markerCount >= 2) {
|
|
138
|
+
const closeIdx = text.indexOf(marker.repeat(2), pos + 2)
|
|
139
|
+
if (closeIdx !== -1) {
|
|
140
|
+
const inner = text.slice(pos + 2, closeIdx)
|
|
141
|
+
nodes.push({ type: 'bold', children: parseInline(inner) })
|
|
142
|
+
pos = closeIdx + 2
|
|
143
|
+
continue
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (markerCount >= 1) {
|
|
148
|
+
const closeIdx = text.indexOf(marker, pos + 1)
|
|
149
|
+
if (closeIdx !== -1) {
|
|
150
|
+
const inner = text.slice(pos + 1, closeIdx)
|
|
151
|
+
nodes.push({ type: 'italic', children: parseInline(inner) })
|
|
152
|
+
pos = closeIdx + 1
|
|
153
|
+
continue
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Plain text — consume until the next special character
|
|
159
|
+
let end = pos + 1
|
|
160
|
+
while (end < text.length && !['`', '!', '[', '*', '_'].includes(text[end])) {
|
|
161
|
+
end++
|
|
162
|
+
}
|
|
163
|
+
const content = text.slice(pos, end)
|
|
164
|
+
const lastNode = nodes[nodes.length - 1]
|
|
165
|
+
if (lastNode?.type === 'text') {
|
|
166
|
+
lastNode.content += content
|
|
167
|
+
} else {
|
|
168
|
+
nodes.push({ type: 'text', content })
|
|
169
|
+
}
|
|
170
|
+
pos = end
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return nodes
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Parse a Markdown string into an array of block-level MarkdownNodes.
|
|
178
|
+
*/
|
|
179
|
+
export const parseMarkdown = (source: string): MarkdownNode[] => {
|
|
180
|
+
const lines = source.split('\n')
|
|
181
|
+
const nodes: MarkdownNode[] = []
|
|
182
|
+
let i = 0
|
|
183
|
+
|
|
184
|
+
while (i < lines.length) {
|
|
185
|
+
const line = lines[i]
|
|
186
|
+
|
|
187
|
+
// Blank lines — skip
|
|
188
|
+
if (line.trim() === '') {
|
|
189
|
+
i++
|
|
190
|
+
continue
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Fenced code block
|
|
194
|
+
const codeMatch = FENCED_CODE_OPEN_RE.exec(line)
|
|
195
|
+
if (codeMatch) {
|
|
196
|
+
const language = codeMatch[1] || undefined
|
|
197
|
+
const codeLines: string[] = []
|
|
198
|
+
i++
|
|
199
|
+
while (i < lines.length && !FENCED_CODE_CLOSE_RE.test(lines[i])) {
|
|
200
|
+
codeLines.push(lines[i])
|
|
201
|
+
i++
|
|
202
|
+
}
|
|
203
|
+
nodes.push({ type: 'codeBlock', language, content: codeLines.join('\n') })
|
|
204
|
+
i++ // skip closing ```
|
|
205
|
+
continue
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Horizontal rule
|
|
209
|
+
if (HORIZONTAL_RULE_RE.test(line)) {
|
|
210
|
+
nodes.push({ type: 'horizontalRule' })
|
|
211
|
+
i++
|
|
212
|
+
continue
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Heading
|
|
216
|
+
const headingMatch = HEADING_RE.exec(line)
|
|
217
|
+
if (headingMatch) {
|
|
218
|
+
const level = headingMatch[1].length as 1 | 2 | 3 | 4 | 5 | 6
|
|
219
|
+
nodes.push({ type: 'heading', level, children: parseInline(headingMatch[2]) })
|
|
220
|
+
i++
|
|
221
|
+
continue
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Blockquote
|
|
225
|
+
const bqMatch = BLOCKQUOTE_RE.exec(line)
|
|
226
|
+
if (bqMatch) {
|
|
227
|
+
const bqLines: string[] = []
|
|
228
|
+
while (i < lines.length) {
|
|
229
|
+
const bqLineMatch = BLOCKQUOTE_RE.exec(lines[i])
|
|
230
|
+
if (bqLineMatch) {
|
|
231
|
+
bqLines.push(bqLineMatch[1])
|
|
232
|
+
i++
|
|
233
|
+
} else {
|
|
234
|
+
break
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
nodes.push({ type: 'blockquote', children: parseMarkdown(bqLines.join('\n')) })
|
|
238
|
+
continue
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Unordered list
|
|
242
|
+
const ulMatch = UNORDERED_LIST_RE.exec(line)
|
|
243
|
+
if (ulMatch) {
|
|
244
|
+
const items: ListItemNode[] = []
|
|
245
|
+
while (i < lines.length) {
|
|
246
|
+
const itemMatch = UNORDERED_LIST_RE.exec(lines[i])
|
|
247
|
+
if (!itemMatch) break
|
|
248
|
+
const itemText = itemMatch[2]
|
|
249
|
+
const checkedMatch = CHECKBOX_CHECKED_RE.exec(itemText)
|
|
250
|
+
const uncheckedMatch = CHECKBOX_UNCHECKED_RE.exec(itemText)
|
|
251
|
+
if (checkedMatch) {
|
|
252
|
+
items.push({
|
|
253
|
+
children: parseInline(checkedMatch[1]),
|
|
254
|
+
checkbox: 'checked',
|
|
255
|
+
sourceLineIndex: i,
|
|
256
|
+
})
|
|
257
|
+
} else if (uncheckedMatch) {
|
|
258
|
+
items.push({
|
|
259
|
+
children: parseInline(uncheckedMatch[1]),
|
|
260
|
+
checkbox: 'unchecked',
|
|
261
|
+
sourceLineIndex: i,
|
|
262
|
+
})
|
|
263
|
+
} else {
|
|
264
|
+
items.push({
|
|
265
|
+
children: parseInline(itemText),
|
|
266
|
+
sourceLineIndex: i,
|
|
267
|
+
})
|
|
268
|
+
}
|
|
269
|
+
i++
|
|
270
|
+
}
|
|
271
|
+
nodes.push({ type: 'list', ordered: false, items })
|
|
272
|
+
continue
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Ordered list
|
|
276
|
+
const olMatch = ORDERED_LIST_RE.exec(line)
|
|
277
|
+
if (olMatch) {
|
|
278
|
+
const items: ListItemNode[] = []
|
|
279
|
+
while (i < lines.length) {
|
|
280
|
+
const itemMatch = ORDERED_LIST_RE.exec(lines[i])
|
|
281
|
+
if (!itemMatch) break
|
|
282
|
+
items.push({
|
|
283
|
+
children: parseInline(itemMatch[2]),
|
|
284
|
+
sourceLineIndex: i,
|
|
285
|
+
})
|
|
286
|
+
i++
|
|
287
|
+
}
|
|
288
|
+
nodes.push({ type: 'list', ordered: true, items })
|
|
289
|
+
continue
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Paragraph — collect consecutive non-blank, non-block-start lines
|
|
293
|
+
const paraLines: string[] = []
|
|
294
|
+
while (i < lines.length) {
|
|
295
|
+
const pLine = lines[i]
|
|
296
|
+
if (pLine.trim() === '') break
|
|
297
|
+
if (HEADING_RE.test(pLine)) break
|
|
298
|
+
if (FENCED_CODE_OPEN_RE.test(pLine)) break
|
|
299
|
+
if (HORIZONTAL_RULE_RE.test(pLine)) break
|
|
300
|
+
if (BLOCKQUOTE_RE.test(pLine)) break
|
|
301
|
+
if (UNORDERED_LIST_RE.test(pLine)) break
|
|
302
|
+
if (ORDERED_LIST_RE.test(pLine)) break
|
|
303
|
+
paraLines.push(pLine)
|
|
304
|
+
i++
|
|
305
|
+
}
|
|
306
|
+
if (paraLines.length > 0) {
|
|
307
|
+
nodes.push({ type: 'paragraph', children: parseInline(paraLines.join(' ')) })
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return nodes
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const TOGGLE_UNCHECKED_RE = /^(\s*[-*]\s+)\[ \]/
|
|
315
|
+
const TOGGLE_CHECKED_RE = /^(\s*[-*]\s+)\[[xX]\]/
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Toggle a checkbox at the given source line index in the raw Markdown string.
|
|
319
|
+
* Only matches checkboxes in unordered list items (`- [ ]` or `* [x]`).
|
|
320
|
+
* Returns the updated string.
|
|
321
|
+
*/
|
|
322
|
+
export const toggleCheckbox = (source: string, sourceLineIndex: number): string => {
|
|
323
|
+
const lines = source.split('\n')
|
|
324
|
+
if (sourceLineIndex < 0 || sourceLineIndex >= lines.length) return source
|
|
325
|
+
|
|
326
|
+
const line = lines[sourceLineIndex]
|
|
327
|
+
if (TOGGLE_UNCHECKED_RE.test(line)) {
|
|
328
|
+
lines[sourceLineIndex] = line.replace(TOGGLE_UNCHECKED_RE, '$1[x]')
|
|
329
|
+
} else if (TOGGLE_CHECKED_RE.test(line)) {
|
|
330
|
+
lines[sourceLineIndex] = line.replace(TOGGLE_CHECKED_RE, '$1[ ]')
|
|
331
|
+
}
|
|
332
|
+
return lines.join('\n')
|
|
333
|
+
}
|
|
@@ -3,8 +3,8 @@ import { Shade, createComponent } from '@furystack/shades'
|
|
|
3
3
|
import { buildTransition, cssVariableTheme } from '../services/css-variable-theme.js'
|
|
4
4
|
import { paletteMainColors } from '../services/palette-css-vars.js'
|
|
5
5
|
import type { Palette } from '../services/theme-provider-service.js'
|
|
6
|
-
import { Icon } from './icons/icon.js'
|
|
7
6
|
import { clipboard } from './icons/icon-definitions.js'
|
|
7
|
+
import { Icon } from './icons/icon.js'
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Typography variant determines semantic HTML tag and default styles.
|
|
@@ -50,74 +50,85 @@ type VariantDef = {
|
|
|
50
50
|
lineHeight: string
|
|
51
51
|
letterSpacing: string
|
|
52
52
|
textTransform?: string
|
|
53
|
-
|
|
53
|
+
marginTop?: string
|
|
54
|
+
marginBottom?: string
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
const variantDefs: Record<TypographyVariant, VariantDef> = {
|
|
57
58
|
h1: {
|
|
58
|
-
fontSize: cssVariableTheme.typography.fontSize.
|
|
59
|
+
fontSize: cssVariableTheme.typography.fontSize.xxxxl,
|
|
59
60
|
fontWeight: cssVariableTheme.typography.fontWeight.bold,
|
|
60
61
|
lineHeight: cssVariableTheme.typography.lineHeight.tight,
|
|
61
62
|
letterSpacing: cssVariableTheme.typography.letterSpacing.tight,
|
|
62
|
-
|
|
63
|
+
marginBottom: '0.3em',
|
|
63
64
|
},
|
|
64
65
|
h2: {
|
|
65
|
-
fontSize: cssVariableTheme.typography.fontSize.
|
|
66
|
+
fontSize: cssVariableTheme.typography.fontSize.xxxl,
|
|
66
67
|
fontWeight: cssVariableTheme.typography.fontWeight.bold,
|
|
67
68
|
lineHeight: cssVariableTheme.typography.lineHeight.tight,
|
|
68
69
|
letterSpacing: cssVariableTheme.typography.letterSpacing.dense,
|
|
69
|
-
|
|
70
|
+
marginTop: '1.5em',
|
|
71
|
+
marginBottom: '0.3em',
|
|
70
72
|
},
|
|
71
73
|
h3: {
|
|
72
|
-
fontSize: cssVariableTheme.typography.fontSize.
|
|
74
|
+
fontSize: cssVariableTheme.typography.fontSize.xxl,
|
|
73
75
|
fontWeight: cssVariableTheme.typography.fontWeight.semibold,
|
|
74
76
|
lineHeight: cssVariableTheme.typography.lineHeight.tight,
|
|
75
77
|
letterSpacing: cssVariableTheme.typography.letterSpacing.normal,
|
|
76
|
-
|
|
78
|
+
marginTop: '1.25em',
|
|
79
|
+
marginBottom: '0.25em',
|
|
77
80
|
},
|
|
78
81
|
h4: {
|
|
79
|
-
fontSize: cssVariableTheme.typography.fontSize.
|
|
82
|
+
fontSize: cssVariableTheme.typography.fontSize.xl,
|
|
80
83
|
fontWeight: cssVariableTheme.typography.fontWeight.semibold,
|
|
81
84
|
lineHeight: cssVariableTheme.typography.lineHeight.tight,
|
|
82
85
|
letterSpacing: cssVariableTheme.typography.letterSpacing.wide,
|
|
83
|
-
|
|
86
|
+
marginTop: '1em',
|
|
87
|
+
marginBottom: '0.25em',
|
|
84
88
|
},
|
|
85
89
|
h5: {
|
|
86
90
|
fontSize: cssVariableTheme.typography.fontSize.lg,
|
|
87
91
|
fontWeight: cssVariableTheme.typography.fontWeight.medium,
|
|
88
92
|
lineHeight: cssVariableTheme.typography.lineHeight.normal,
|
|
89
93
|
letterSpacing: cssVariableTheme.typography.letterSpacing.normal,
|
|
94
|
+
marginTop: '0.75em',
|
|
95
|
+
marginBottom: '0.35em',
|
|
90
96
|
},
|
|
91
97
|
h6: {
|
|
92
98
|
fontSize: cssVariableTheme.typography.fontSize.md,
|
|
93
99
|
fontWeight: cssVariableTheme.typography.fontWeight.medium,
|
|
94
100
|
lineHeight: cssVariableTheme.typography.lineHeight.normal,
|
|
95
101
|
letterSpacing: cssVariableTheme.typography.letterSpacing.wide,
|
|
96
|
-
|
|
102
|
+
marginTop: '0.5em',
|
|
103
|
+
marginBottom: '0.2em',
|
|
97
104
|
},
|
|
98
105
|
subtitle1: {
|
|
99
106
|
fontSize: cssVariableTheme.typography.fontSize.md,
|
|
100
107
|
fontWeight: cssVariableTheme.typography.fontWeight.medium,
|
|
101
108
|
lineHeight: cssVariableTheme.typography.lineHeight.normal,
|
|
102
109
|
letterSpacing: cssVariableTheme.typography.letterSpacing.wide,
|
|
110
|
+
marginBottom: '0.35em',
|
|
103
111
|
},
|
|
104
112
|
subtitle2: {
|
|
105
113
|
fontSize: cssVariableTheme.typography.fontSize.sm,
|
|
106
114
|
fontWeight: cssVariableTheme.typography.fontWeight.medium,
|
|
107
115
|
lineHeight: cssVariableTheme.typography.lineHeight.normal,
|
|
108
116
|
letterSpacing: '0.1px',
|
|
117
|
+
marginBottom: '0.25em',
|
|
109
118
|
},
|
|
110
119
|
body1: {
|
|
111
120
|
fontSize: cssVariableTheme.typography.fontSize.md,
|
|
112
121
|
fontWeight: cssVariableTheme.typography.fontWeight.normal,
|
|
113
122
|
lineHeight: cssVariableTheme.typography.lineHeight.relaxed,
|
|
114
123
|
letterSpacing: cssVariableTheme.typography.letterSpacing.wide,
|
|
124
|
+
marginBottom: '0.75em',
|
|
115
125
|
},
|
|
116
126
|
body2: {
|
|
117
127
|
fontSize: cssVariableTheme.typography.fontSize.sm,
|
|
118
128
|
fontWeight: cssVariableTheme.typography.fontWeight.normal,
|
|
119
129
|
lineHeight: cssVariableTheme.typography.lineHeight.relaxed,
|
|
120
130
|
letterSpacing: cssVariableTheme.typography.letterSpacing.wide,
|
|
131
|
+
marginBottom: '0.5em',
|
|
121
132
|
},
|
|
122
133
|
caption: {
|
|
123
134
|
fontSize: cssVariableTheme.typography.fontSize.xs,
|
|
@@ -131,6 +142,7 @@ const variantDefs: Record<TypographyVariant, VariantDef> = {
|
|
|
131
142
|
lineHeight: cssVariableTheme.typography.lineHeight.normal,
|
|
132
143
|
letterSpacing: cssVariableTheme.typography.letterSpacing.widest,
|
|
133
144
|
textTransform: 'uppercase',
|
|
145
|
+
marginBottom: '0.5em',
|
|
134
146
|
},
|
|
135
147
|
}
|
|
136
148
|
|
|
@@ -146,10 +158,11 @@ const buildVariantCssRules = (): Record<string, Record<string, string>> => {
|
|
|
146
158
|
if (def.textTransform) {
|
|
147
159
|
rule.textTransform = def.textTransform
|
|
148
160
|
}
|
|
149
|
-
if (def.
|
|
150
|
-
rule.
|
|
151
|
-
|
|
152
|
-
|
|
161
|
+
if (def.marginTop) {
|
|
162
|
+
rule.marginTop = def.marginTop
|
|
163
|
+
}
|
|
164
|
+
if (def.marginBottom) {
|
|
165
|
+
rule.marginBottom = def.marginBottom
|
|
153
166
|
}
|
|
154
167
|
rules[`&[data-variant="${variant}"]`] = rule
|
|
155
168
|
}
|
|
@@ -59,6 +59,9 @@ describe('css-variable-theme', () => {
|
|
|
59
59
|
expect(cssVariableTheme.typography.fontFamily).toBe('var(--shades-theme-typography-font-family)')
|
|
60
60
|
expect(cssVariableTheme.typography.fontSize.xs).toBe('var(--shades-theme-typography-font-size-xs)')
|
|
61
61
|
expect(cssVariableTheme.typography.fontSize.md).toBe('var(--shades-theme-typography-font-size-md)')
|
|
62
|
+
expect(cssVariableTheme.typography.fontSize.xxl).toBe('var(--shades-theme-typography-font-size-xxl)')
|
|
63
|
+
expect(cssVariableTheme.typography.fontSize.xxxl).toBe('var(--shades-theme-typography-font-size-xxxl)')
|
|
64
|
+
expect(cssVariableTheme.typography.fontSize.xxxxl).toBe('var(--shades-theme-typography-font-size-xxxxl)')
|
|
62
65
|
expect(cssVariableTheme.typography.fontWeight.normal).toBe('var(--shades-theme-typography-font-weight-normal)')
|
|
63
66
|
expect(cssVariableTheme.typography.fontWeight.bold).toBe('var(--shades-theme-typography-font-weight-bold)')
|
|
64
67
|
expect(cssVariableTheme.typography.lineHeight.tight).toBe('var(--shades-theme-typography-line-height-tight)')
|
|
@@ -102,6 +102,9 @@ export const cssVariableTheme = {
|
|
|
102
102
|
md: 'var(--shades-theme-typography-font-size-md)',
|
|
103
103
|
lg: 'var(--shades-theme-typography-font-size-lg)',
|
|
104
104
|
xl: 'var(--shades-theme-typography-font-size-xl)',
|
|
105
|
+
xxl: 'var(--shades-theme-typography-font-size-xxl)',
|
|
106
|
+
xxxl: 'var(--shades-theme-typography-font-size-xxxl)',
|
|
107
|
+
xxxxl: 'var(--shades-theme-typography-font-size-xxxxl)',
|
|
105
108
|
},
|
|
106
109
|
fontWeight: {
|
|
107
110
|
normal: 'var(--shades-theme-typography-font-weight-normal)',
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { Palette } from './theme-provider-service.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Color palette optimized for dark backgrounds (#121212).
|
|
5
|
+
* Uses lighter Material Design color variants to ensure
|
|
6
|
+
* WCAG AA contrast (≥4.5:1) against the dark theme background.
|
|
7
|
+
*/
|
|
8
|
+
export const defaultDarkPalette: Palette = {
|
|
9
|
+
primary: {
|
|
10
|
+
light: '#9fa8da',
|
|
11
|
+
lightContrast: '#000000',
|
|
12
|
+
main: '#7986cb',
|
|
13
|
+
mainContrast: '#000000',
|
|
14
|
+
dark: '#5c6bc0',
|
|
15
|
+
darkContrast: '#ffffff',
|
|
16
|
+
},
|
|
17
|
+
secondary: {
|
|
18
|
+
light: '#4aedc4',
|
|
19
|
+
lightContrast: '#000000',
|
|
20
|
+
main: '#1de9b6',
|
|
21
|
+
mainContrast: '#000000',
|
|
22
|
+
dark: '#14a37f',
|
|
23
|
+
darkContrast: '#ffffff',
|
|
24
|
+
},
|
|
25
|
+
error: {
|
|
26
|
+
light: '#ef9a9a',
|
|
27
|
+
lightContrast: '#000000',
|
|
28
|
+
main: '#ef5350',
|
|
29
|
+
mainContrast: '#000000',
|
|
30
|
+
dark: '#e53935',
|
|
31
|
+
darkContrast: '#ffffff',
|
|
32
|
+
},
|
|
33
|
+
warning: {
|
|
34
|
+
light: '#ffb74d',
|
|
35
|
+
lightContrast: '#000000',
|
|
36
|
+
main: '#ff9800',
|
|
37
|
+
mainContrast: '#000000',
|
|
38
|
+
dark: '#f57c00',
|
|
39
|
+
darkContrast: '#000000',
|
|
40
|
+
},
|
|
41
|
+
info: {
|
|
42
|
+
light: '#90caf9',
|
|
43
|
+
lightContrast: '#000000',
|
|
44
|
+
main: '#42a5f5',
|
|
45
|
+
mainContrast: '#000000',
|
|
46
|
+
dark: '#1e88e5',
|
|
47
|
+
darkContrast: '#ffffff',
|
|
48
|
+
},
|
|
49
|
+
success: {
|
|
50
|
+
light: '#81c784',
|
|
51
|
+
lightContrast: '#000000',
|
|
52
|
+
main: '#4caf50',
|
|
53
|
+
mainContrast: '#000000',
|
|
54
|
+
dark: '#388e3c',
|
|
55
|
+
darkContrast: '#ffffff',
|
|
56
|
+
},
|
|
57
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { defaultDarkPalette } from './default-dark-palette.js'
|
|
2
2
|
import type { Theme } from './theme-provider-service.js'
|
|
3
3
|
|
|
4
4
|
export const defaultDarkTheme = {
|
|
@@ -16,10 +16,10 @@ export const defaultDarkTheme = {
|
|
|
16
16
|
disabledBackground: 'rgba(255, 255, 255, 0.12)',
|
|
17
17
|
},
|
|
18
18
|
background: {
|
|
19
|
-
default: '#
|
|
20
|
-
paper: '#
|
|
19
|
+
default: '#121212',
|
|
20
|
+
paper: '#1e1e1e',
|
|
21
21
|
},
|
|
22
|
-
palette:
|
|
22
|
+
palette: defaultDarkPalette,
|
|
23
23
|
divider: 'rgba(255, 255, 255, 0.12)',
|
|
24
24
|
action: {
|
|
25
25
|
hoverBackground: 'rgba(255, 255, 255, 0.08)',
|
|
@@ -54,6 +54,9 @@ export const defaultDarkTheme = {
|
|
|
54
54
|
md: '14px',
|
|
55
55
|
lg: '16px',
|
|
56
56
|
xl: '24px',
|
|
57
|
+
xxl: '30px',
|
|
58
|
+
xxxl: '36px',
|
|
59
|
+
xxxxl: '48px',
|
|
57
60
|
},
|
|
58
61
|
fontWeight: {
|
|
59
62
|
normal: '400',
|
package/src/services/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * from './click-away-service.js'
|
|
2
2
|
export * from './collection-service.js'
|
|
3
3
|
export * from './css-variable-theme.js'
|
|
4
|
+
export * from './default-dark-palette.js'
|
|
4
5
|
export * from './default-dark-theme.js'
|
|
5
6
|
export * from './default-light-theme.js'
|
|
6
7
|
export * from './default-palette.js'
|
|
@@ -158,8 +158,14 @@ export type FontSizeScale = {
|
|
|
158
158
|
md: string
|
|
159
159
|
/** 16px - large body, icons */
|
|
160
160
|
lg: string
|
|
161
|
-
/** 24px -
|
|
161
|
+
/** 24px - subheadings, large UI elements */
|
|
162
162
|
xl: string
|
|
163
|
+
/** 30px - small headings (h3) */
|
|
164
|
+
xxl: string
|
|
165
|
+
/** 36px - medium headings (h2) */
|
|
166
|
+
xxxl: string
|
|
167
|
+
/** 48px - large headings (h1) */
|
|
168
|
+
xxxxl: string
|
|
163
169
|
}
|
|
164
170
|
|
|
165
171
|
/**
|