@kolkrabbi/kol-component 0.12.4 → 0.12.6
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/package.json +3 -3
- package/src/atoms/Figure.jsx +2 -2
- package/src/molecules/CodeBlock.jsx +101 -75
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kolkrabbi/kol-component",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.6",
|
|
4
4
|
"description": "KOL design-system components — atoms through organisms, emitting canonical kol-* classes. Pairs with @kolkrabbi/kol-theme for styling.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"@floating-ui/react": "^0.27.19",
|
|
25
25
|
"embla-carousel-react": "^8.6.0",
|
|
26
|
-
"
|
|
27
|
-
"@kolkrabbi/kol-icons": "0.8.
|
|
26
|
+
"react-syntax-highlighter": "^16.1.1",
|
|
27
|
+
"@kolkrabbi/kol-icons": "0.8.4"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
30
|
"framer-motion": "^12.0.0",
|
package/src/atoms/Figure.jsx
CHANGED
|
@@ -14,14 +14,14 @@
|
|
|
14
14
|
export default function Figure({ label, caption, aspect = '5/3', className = '', children }) {
|
|
15
15
|
return (
|
|
16
16
|
<figure className={`kol-prose-figure ${className}`.trim()}>
|
|
17
|
-
{label && <div className="kol-
|
|
17
|
+
{label && <div className="kol-doc-eyebrow mb-2">{label}</div>}
|
|
18
18
|
<div
|
|
19
19
|
className="border border-fg-08 rounded overflow-hidden"
|
|
20
20
|
style={aspect ? { aspectRatio: aspect } : undefined}
|
|
21
21
|
>
|
|
22
22
|
{children}
|
|
23
23
|
</div>
|
|
24
|
-
{caption && <figcaption className="kol-caption-
|
|
24
|
+
{caption && <figcaption className="kol-doc-caption mt-3">{caption}</figcaption>}
|
|
25
25
|
</figure>
|
|
26
26
|
)
|
|
27
27
|
}
|
|
@@ -1,96 +1,122 @@
|
|
|
1
1
|
import { useState } from 'react'
|
|
2
|
-
import {
|
|
2
|
+
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
|
|
3
|
+
import { oneDark } from 'react-syntax-highlighter/dist/esm/styles/prism'
|
|
4
|
+
import { Icon } from '@kolkrabbi/kol-icons'
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
|
-
* CodeBlock —
|
|
6
|
-
*
|
|
7
|
-
*
|
|
7
|
+
* CodeBlock — REPLICATED from the elder reference
|
|
8
|
+
* (kol-website/packages/ui/src/molecules/CodeBlock.jsx, 2026-07-28 user
|
|
9
|
+
* mandate): react-syntax-highlighter (Prism) with the oneDark theme flattened
|
|
10
|
+
* onto KOL chrome — transparent bg, 14px/1.6 mono, no text-shadow — a single
|
|
11
|
+
* filename-or-language chip, and a 32×32 icon copy button (`copy` glyph →
|
|
12
|
+
* check on copied). Chrome lives in kol-theme (kol-components-molecules.css).
|
|
8
13
|
*
|
|
9
|
-
*
|
|
10
|
-
* • Portable Text: `value={{ code, language, filename }}`
|
|
11
|
-
*
|
|
12
|
-
* •
|
|
13
|
-
* • Children: `<CodeBlock language="js">{'…'}</CodeBlock>` (the original API).
|
|
14
|
-
*
|
|
15
|
-
* Token colors are theme concerns: refractor's `token x` classes render as
|
|
16
|
-
* `kol-tok kol-tok-x` spans, colored by kol-theme (light + dark). An
|
|
17
|
-
* unregistered/absent language renders plain — never crashes.
|
|
18
|
-
*
|
|
19
|
-
* DOM order is copy → filename → lang → pre so the `.kol-codeblock-*` + pre
|
|
20
|
-
* adjacency rules in the chrome CSS can pad the pre when chips are present.
|
|
14
|
+
* Input shapes (first match wins per field):
|
|
15
|
+
* • Portable Text: `value={{ code, language, filename }}`
|
|
16
|
+
* • Direct props: `code` / `language` / `filename`
|
|
17
|
+
* • Children: `<CodeBlock language="js">{'…'}</CodeBlock>`
|
|
21
18
|
*/
|
|
22
19
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
if (node.type === 'text') return node.value
|
|
29
|
-
const cls = (node.properties?.className || [])
|
|
30
|
-
.map((c) => (c === 'token' ? 'kol-tok' : `kol-tok-${c}`))
|
|
31
|
-
.join(' ')
|
|
32
|
-
return (
|
|
33
|
-
<span key={key} className={cls}>
|
|
34
|
-
{node.children.map(renderNode)}
|
|
35
|
-
</span>
|
|
36
|
-
)
|
|
37
|
-
}
|
|
20
|
+
const CheckMarkIcon = () => (
|
|
21
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
22
|
+
<path d="M19.4697 6.41987C19.7626 6.12697 20.2373 6.12697 20.5302 6.41987C20.8231 6.71276 20.8231 7.18752 20.5302 7.48041L10.9443 17.0663C9.87038 18.1401 8.12951 18.1401 7.05561 17.0663L3.46967 13.4804C3.17678 13.1875 3.17678 12.7128 3.46967 12.4199C3.76256 12.127 4.23732 12.127 4.53022 12.4199L8.11615 16.0058C8.60427 16.4938 9.39561 16.4938 9.88373 16.0058L19.4697 6.41987Z" fill="currentColor"/>
|
|
23
|
+
</svg>
|
|
24
|
+
)
|
|
38
25
|
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
26
|
+
const syntaxTheme = (foregroundToken = 80) => ({
|
|
27
|
+
...oneDark,
|
|
28
|
+
'pre[class*="language-"]': {
|
|
29
|
+
...oneDark['pre[class*="language-"]'],
|
|
30
|
+
background: 'transparent',
|
|
31
|
+
margin: 0,
|
|
32
|
+
padding: 0,
|
|
33
|
+
fontSize: '14px',
|
|
34
|
+
lineHeight: '1.6',
|
|
35
|
+
textShadow: 'none',
|
|
36
|
+
letterSpacing: '0',
|
|
37
|
+
overflow: 'visible',
|
|
38
|
+
border: 'none',
|
|
39
|
+
borderRadius: 0,
|
|
40
|
+
color: `color-mix(in srgb, var(--kol-surface-on-primary) ${foregroundToken}%, transparent)`
|
|
41
|
+
},
|
|
42
|
+
'code[class*="language-"]': {
|
|
43
|
+
...oneDark['code[class*="language-"]'],
|
|
44
|
+
background: 'transparent',
|
|
45
|
+
fontFamily: 'var(--kol-font-family-mono)',
|
|
46
|
+
fontSize: '14px',
|
|
47
|
+
textShadow: 'none',
|
|
48
|
+
letterSpacing: '0',
|
|
49
|
+
textDecoration: 'none',
|
|
50
|
+
display: 'block',
|
|
51
|
+
borderRadius: 0,
|
|
52
|
+
border: 'none',
|
|
53
|
+
color: `color-mix(in srgb, var(--kol-surface-on-primary) ${foregroundToken}%, transparent)`
|
|
54
|
+
},
|
|
55
|
+
comment: {
|
|
56
|
+
...oneDark['comment'],
|
|
57
|
+
fontStyle: 'normal'
|
|
46
58
|
}
|
|
47
|
-
}
|
|
59
|
+
})
|
|
48
60
|
|
|
49
|
-
|
|
50
|
-
* checkmark), ported 2026-07-28. Positioning comes from .kol-codeblock-copy. */
|
|
51
|
-
function CopyControl({ text }) {
|
|
61
|
+
export default function CodeBlock({ children, code: codeProp, language: languageProp, filename: filenameProp, value }) {
|
|
52
62
|
const [copied, setCopied] = useState(false)
|
|
53
|
-
|
|
63
|
+
|
|
64
|
+
const code = String(value?.code ?? codeProp ?? children ?? '')
|
|
65
|
+
const language = value?.language ?? languageProp ?? 'text'
|
|
66
|
+
const filename = value?.filename ?? filenameProp
|
|
67
|
+
|
|
68
|
+
const handleCopy = async () => {
|
|
54
69
|
try {
|
|
55
|
-
await navigator.clipboard.writeText(
|
|
70
|
+
await navigator.clipboard.writeText(code)
|
|
56
71
|
setCopied(true)
|
|
57
|
-
setTimeout(() => setCopied(false),
|
|
72
|
+
setTimeout(() => setCopied(false), 2000)
|
|
58
73
|
} catch {
|
|
59
74
|
/* clipboard blocked — silent */
|
|
60
75
|
}
|
|
61
76
|
}
|
|
62
|
-
return (
|
|
63
|
-
<button
|
|
64
|
-
type="button"
|
|
65
|
-
className="kol-codeblock-copy"
|
|
66
|
-
onClick={onCopy}
|
|
67
|
-
aria-label={copied ? 'Copied' : 'Copy to clipboard'}
|
|
68
|
-
title={copied ? 'Copied' : 'Copy'}
|
|
69
|
-
>
|
|
70
|
-
{copied ? (
|
|
71
|
-
<svg viewBox="0 0 20 20" width="14" height="14" aria-hidden="true">
|
|
72
|
-
<path d="M5 10 L9 14 L15 6" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" />
|
|
73
|
-
</svg>
|
|
74
|
-
) : (
|
|
75
|
-
<svg viewBox="0 0 20 20" width="14" height="14" aria-hidden="true">
|
|
76
|
-
<rect x="6" y="3" width="10" height="12" rx="1" fill="none" stroke="currentColor" strokeWidth="1.4" />
|
|
77
|
-
<rect x="4" y="6" width="10" height="12" rx="1" fill="none" stroke="currentColor" strokeWidth="1.4" />
|
|
78
|
-
</svg>
|
|
79
|
-
)}
|
|
80
|
-
</button>
|
|
81
|
-
)
|
|
82
|
-
}
|
|
83
77
|
|
|
84
|
-
export default function CodeBlock({ children, code, language, filename, value }) {
|
|
85
|
-
const src = String(code ?? value?.code ?? children ?? '')
|
|
86
|
-
const lang = language ?? value?.language
|
|
87
|
-
const file = filename ?? value?.filename
|
|
88
78
|
return (
|
|
89
|
-
<div className="kol-codeblock">
|
|
90
|
-
<
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
79
|
+
<div className="kol-codeblock-wrapper">
|
|
80
|
+
<div className="kol-codeblock">
|
|
81
|
+
{(filename || (language && language !== 'text')) && (
|
|
82
|
+
<div className="kol-codeblock-filename">{filename || language}</div>
|
|
83
|
+
)}
|
|
84
|
+
<SyntaxHighlighter
|
|
85
|
+
language={language}
|
|
86
|
+
style={syntaxTheme(80)}
|
|
87
|
+
customStyle={{
|
|
88
|
+
margin: 0,
|
|
89
|
+
padding: 0,
|
|
90
|
+
background: 'transparent',
|
|
91
|
+
whiteSpace: 'pre-wrap',
|
|
92
|
+
wordBreak: 'break-word',
|
|
93
|
+
overflow: 'visible',
|
|
94
|
+
height: 'auto',
|
|
95
|
+
maxHeight: 'none',
|
|
96
|
+
}}
|
|
97
|
+
wrapLines={true}
|
|
98
|
+
wrapLongLines={true}
|
|
99
|
+
PreTag="div"
|
|
100
|
+
lineProps={{
|
|
101
|
+
style: {
|
|
102
|
+
border: 'none',
|
|
103
|
+
background: 'transparent',
|
|
104
|
+
display: 'block',
|
|
105
|
+
}
|
|
106
|
+
}}
|
|
107
|
+
>
|
|
108
|
+
{code}
|
|
109
|
+
</SyntaxHighlighter>
|
|
110
|
+
<button
|
|
111
|
+
type="button"
|
|
112
|
+
className="kol-codeblock-copy"
|
|
113
|
+
onClick={handleCopy}
|
|
114
|
+
aria-label={copied ? 'Code copied!' : 'Copy code'}
|
|
115
|
+
title={copied ? 'Code copied!' : 'Copy code'}
|
|
116
|
+
>
|
|
117
|
+
{copied ? <CheckMarkIcon /> : <Icon name="copy" size={16} />}
|
|
118
|
+
</button>
|
|
119
|
+
</div>
|
|
94
120
|
</div>
|
|
95
121
|
)
|
|
96
122
|
}
|