@kolkrabbi/kol-component 0.5.0 → 0.7.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.
Files changed (97) hide show
  1. package/package.json +5 -3
  2. package/src/atoms/Button.jsx +9 -5
  3. package/src/atoms/Input.jsx +9 -6
  4. package/src/atoms/Textarea.jsx +36 -5
  5. package/src/atoms/ToggleSwitch.jsx +31 -4
  6. package/src/chess/ChessPiece.jsx +191 -0
  7. package/src/chess/apparatus/AlternativeControlsMock.jsx +460 -0
  8. package/src/chess/apparatus/ChessAnalysisLayout.jsx +24 -0
  9. package/src/chess/apparatus/ChessBoard.jsx +210 -0
  10. package/src/chess/apparatus/ChessBoardFullscreen.jsx +49 -0
  11. package/src/chess/apparatus/ChessBoardWithControls.jsx +41 -0
  12. package/src/chess/apparatus/ChessBoardWithSidebar.jsx +122 -0
  13. package/src/chess/apparatus/ChessSidebar.jsx +131 -0
  14. package/src/chess/apparatus/GameArchiveTable.jsx +493 -0
  15. package/src/chess/apparatus/GameSelector.jsx +47 -0
  16. package/src/chess/apparatus/NotationPanel.jsx +70 -0
  17. package/src/chess/apparatus/PlaybackControls.jsx +52 -0
  18. package/src/chess/apparatus/VariationTree.jsx +137 -0
  19. package/src/chess/assets/chess-extra-set/set-1/bishop-1.svg +6 -0
  20. package/src/chess/assets/chess-extra-set/set-1/bishop.svg +6 -0
  21. package/src/chess/assets/chess-extra-set/set-1/king-1.svg +9 -0
  22. package/src/chess/assets/chess-extra-set/set-1/king.svg +7 -0
  23. package/src/chess/assets/chess-extra-set/set-1/knight-1.svg +8 -0
  24. package/src/chess/assets/chess-extra-set/set-1/knight.svg +7 -0
  25. package/src/chess/assets/chess-extra-set/set-1/pawn-1.svg +3 -0
  26. package/src/chess/assets/chess-extra-set/set-1/pawn.svg +3 -0
  27. package/src/chess/assets/chess-extra-set/set-1/queen-1.svg +11 -0
  28. package/src/chess/assets/chess-extra-set/set-1/queen.svg +6 -0
  29. package/src/chess/assets/chess-extra-set/set-1/rook-1.svg +7 -0
  30. package/src/chess/assets/chess-extra-set/set-1/rook.svg +11 -0
  31. package/src/chess/assets/chess-extra-set/set-2/bishop-1.svg +4 -0
  32. package/src/chess/assets/chess-extra-set/set-2/bishop.svg +4 -0
  33. package/src/chess/assets/chess-extra-set/set-2/king-1.svg +4 -0
  34. package/src/chess/assets/chess-extra-set/set-2/king.svg +4 -0
  35. package/src/chess/assets/chess-extra-set/set-2/knight-1.svg +5 -0
  36. package/src/chess/assets/chess-extra-set/set-2/knight.svg +5 -0
  37. package/src/chess/assets/chess-extra-set/set-2/pawn-1.svg +4 -0
  38. package/src/chess/assets/chess-extra-set/set-2/pawn.svg +4 -0
  39. package/src/chess/assets/chess-extra-set/set-2/queen-1.svg +4 -0
  40. package/src/chess/assets/chess-extra-set/set-2/queen.svg +4 -0
  41. package/src/chess/assets/chess-extra-set/set-2/rook-1.svg +5 -0
  42. package/src/chess/assets/chess-extra-set/set-2/rook.svg +5 -0
  43. package/src/chess/assets/chess-extra-set/set-3/bishop-1.svg +10 -0
  44. package/src/chess/assets/chess-extra-set/set-3/bishop.svg +3 -0
  45. package/src/chess/assets/chess-extra-set/set-3/king-1.svg +12 -0
  46. package/src/chess/assets/chess-extra-set/set-3/king.svg +3 -0
  47. package/src/chess/assets/chess-extra-set/set-3/knight-1.svg +3 -0
  48. package/src/chess/assets/chess-extra-set/set-3/knight.svg +10 -0
  49. package/src/chess/assets/chess-extra-set/set-3/pawn-1.svg +10 -0
  50. package/src/chess/assets/chess-extra-set/set-3/pawn.svg +10 -0
  51. package/src/chess/assets/chess-extra-set/set-3/queen-1.svg +10 -0
  52. package/src/chess/assets/chess-extra-set/set-3/queen.svg +3 -0
  53. package/src/chess/assets/chess-extra-set/set-3/rook-1.svg +3 -0
  54. package/src/chess/assets/chess-extra-set/set-3/rook.svg +11 -0
  55. package/src/chess/assets/chess-vector-set/ABoard.svg +164 -0
  56. package/src/chess/assets/chess-vector-set/BBiskup.svg +12 -0
  57. package/src/chess/assets/chess-vector-set/BKing.svg +15 -0
  58. package/src/chess/assets/chess-vector-set/BKnight.svg +13 -0
  59. package/src/chess/assets/chess-vector-set/BPawn.svg +7 -0
  60. package/src/chess/assets/chess-vector-set/BQueen.svg +6 -0
  61. package/src/chess/assets/chess-vector-set/BRook.svg +12 -0
  62. package/src/chess/assets/chess-vector-set/WBiskup.svg +14 -0
  63. package/src/chess/assets/chess-vector-set/WKing.svg +13 -0
  64. package/src/chess/assets/chess-vector-set/WKnight.svg +10 -0
  65. package/src/chess/assets/chess-vector-set/WPawn.svg +8 -0
  66. package/src/chess/assets/chess-vector-set/WQueen.svg +17 -0
  67. package/src/chess/assets/chess-vector-set/WRook.svg +13 -0
  68. package/src/chess/context/ChessControlsContext.jsx +379 -0
  69. package/src/chess/dashboards/ChessHero.jsx +303 -0
  70. package/src/chess/index.js +32 -0
  71. package/src/chess/utils/parsePgnTree.js +97 -0
  72. package/src/dashboards/cards/DashAlertCard.jsx +44 -0
  73. package/src/dashboards/cards/DashChartCard.jsx +44 -0
  74. package/src/dashboards/cards/DashFeaturedCard.jsx +42 -0
  75. package/src/dashboards/cards/DashListCard.jsx +87 -0
  76. package/src/dashboards/cards/DashMetricCard.jsx +59 -0
  77. package/src/dashboards/cards/DashSlotCard.jsx +45 -0
  78. package/src/dashboards/cards/DashStackedBarCard.jsx +70 -0
  79. package/src/dashboards/cards/DashTableCard.jsx +31 -0
  80. package/src/dashboards/cards/_shared/CardHeader.jsx +15 -0
  81. package/src/dashboards/cards/_shared/LegendRow.jsx +25 -0
  82. package/src/dashboards/charts/Candlestick.jsx +79 -0
  83. package/src/dashboards/charts/DonutChart.jsx +87 -0
  84. package/src/dashboards/charts/Heatmap.jsx +79 -0
  85. package/src/dashboards/charts/Histogram.jsx +56 -0
  86. package/src/dashboards/charts/LineChart.jsx +157 -0
  87. package/src/dashboards/charts/ScatterPlot.jsx +93 -0
  88. package/src/dashboards/charts/Sparkline.jsx +63 -0
  89. package/src/dashboards/index.js +28 -0
  90. package/src/dashboards/layout/DashboardGrid.jsx +30 -0
  91. package/src/dashboards/layout/GridCard.jsx +44 -0
  92. package/src/dashboards/shared/DashTooltip.jsx +6 -0
  93. package/src/dashboards/shared/formatMetric.js +26 -0
  94. package/src/dashboards/shared/useChartTooltip.js +25 -0
  95. package/src/dashboards/shared/useCountUp.js +107 -0
  96. package/src/molecules/Dropdown.jsx +50 -96
  97. package/src/molecules/Slider.jsx +4 -14
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kolkrabbi/kol-component",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
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",
@@ -8,7 +8,9 @@
8
8
  "module": "./src/index.js",
9
9
  "exports": {
10
10
  ".": "./src/index.js",
11
- "./foundry": "./src/organisms/foundry/index.js"
11
+ "./foundry": "./src/organisms/foundry/index.js",
12
+ "./dashboards": "./src/dashboards/index.js",
13
+ "./chess": "./src/chess/index.js"
12
14
  },
13
15
  "files": [
14
16
  "src",
@@ -24,7 +26,7 @@
24
26
  "dependencies": {
25
27
  "@floating-ui/react": "^0.27.19",
26
28
  "embla-carousel-react": "^8.6.0",
27
- "@kolkrabbi/kol-icons": "0.4.0"
29
+ "@kolkrabbi/kol-icons": "0.5.0"
28
30
  },
29
31
  "peerDependencies": {
30
32
  "react": "^18.3.0 || ^19.0.0",
@@ -82,11 +82,15 @@ const Button = ({
82
82
 
83
83
  // Add kol-btn-animate class if animateIcon is true to disable default hover states
84
84
  const animateClass = animateIcon ? 'kol-btn-animate' : ''
85
- const quietClass = quiet && !pressed ? 'kol-btn-quiet' : ''
86
- const selectedClass = selected ? 'kol-btn-selected' : ''
87
- const pressedClass = pressed ? 'kol-btn-pressed' : ''
88
-
89
- const combinedClass = `kol-btn ${variantClass} ${sizeClass} ${animateClass} ${quietClass} ${selectedClass} ${pressedClass} ${className}`.trim().replace(/\s+/g, ' ')
85
+ // `selected` is a legacy alias of `pressed` both express the aria-pressed
86
+ // toggle-on state and render through .kol-btn-pressed. (The old
87
+ // .kol-btn-selected class never had a CSS rule, so `selected` was a no-op.)
88
+ // Resolve to one flag so the quiet-drop and the pressed fill agree.
89
+ const isPressed = pressed !== undefined ? pressed : selected
90
+ const quietClass = quiet && !isPressed ? 'kol-btn-quiet' : ''
91
+ const pressedClass = isPressed ? 'kol-btn-pressed' : ''
92
+
93
+ const combinedClass = `kol-btn ${variantClass} ${sizeClass} ${animateClass} ${quietClass} ${pressedClass} ${className}`.trim().replace(/\s+/g, ' ')
90
94
 
91
95
  // Render icon with optional hover state
92
96
  const renderIcon = (iconName, iconHoverName) => {
@@ -3,11 +3,11 @@ import { Icon } from '@kolkrabbi/kol-icons'
3
3
  /**
4
4
  * Input — single-input atom built on the .kol-control shell.
5
5
  *
6
- * variant="filled" (default) — persistent solid bg
7
- * variant="ghost" borderless at rest, reveals on hover/focus
8
- * variant="outline" — bordered, transparent bg (also: the
9
- * forced-reveal / "is-edited" state of a
10
- * ghost field flip variant to outline)
6
+ * variant="filled" (default) — persistent solid bg (= button primary chrome)
7
+ * variant="outline" bordered, transparent bg THE secondary
8
+ * treatment (2026-07-08 chrome law: one
9
+ * secondary, always subordinate to filled)
10
+ * variant="ghost" — legacy alias, resolves to outline
11
11
  *
12
12
  * size="sm" / "md" (default) / "lg" — matched padding + type class
13
13
  *
@@ -57,9 +57,12 @@ export default function Input({
57
57
  const fixedChars = typeof chars === 'number'
58
58
  const resolvedIconSize = iconSize ?? ICON_SIZE[size] ?? 14
59
59
 
60
+ // ghost folds into outline (2026-07-08 chrome law): one secondary treatment.
61
+ const resolvedVariant = variant === 'ghost' ? 'outline' : variant
62
+
60
63
  const shellCls = [
61
64
  'kol-control',
62
- `kol-control--${variant}`,
65
+ `kol-control--${resolvedVariant}`,
63
66
  `kol-control-${size}`,
64
67
  SIZE_TYPE[size],
65
68
  'cursor-text',
@@ -1,3 +1,4 @@
1
+
1
2
  import { Icon } from '@kolkrabbi/kol-icons'
2
3
 
3
4
  /**
@@ -11,8 +12,10 @@ import { Icon } from '@kolkrabbi/kol-icons'
11
12
  * size="sm" / "md" (default) / "lg" — padding + type class
12
13
  *
13
14
  * Default rows = 3 (kept short — long content gets scrollbars instead of
14
- * dominating the panel). No native resize handle fixed size, scrollable
15
- * overflow. The kol resize-corner icon in the bottom-right is decorative.
15
+ * dominating the panel). Resize is real (2026-07-08): native resize is
16
+ * OFF (Firefox's built-in grip cannot be hidden any other way) and the
17
+ * kol-icon-set-v1 `resize-grip` icon IS the drag handle — corner drag,
18
+ * both axes, min 120×40. One grip, every browser.
16
19
  *
17
20
  * Controlled OR uncontrolled:
18
21
  * - pass `value` + `onChange` for controlled,
@@ -34,9 +37,12 @@ export default function Textarea({
34
37
  className = '',
35
38
  ...props
36
39
  }) {
40
+ // ghost folds into outline (2026-07-08 chrome law): one secondary treatment.
41
+ const resolvedVariant = variant === 'ghost' ? 'outline' : variant
42
+
37
43
  const wrapperCls = [
38
44
  'kol-control',
39
- `kol-control--${variant}`,
45
+ `kol-control--${resolvedVariant}`,
40
46
  'kol-control--textarea',
41
47
  `kol-control-${size}`,
42
48
  SIZE_TYPE[size],
@@ -62,8 +68,33 @@ export default function Textarea({
62
68
  style={{ resize: 'none' }}
63
69
  {...props}
64
70
  />
65
- <span aria-hidden="true" className="kol-textarea-resize-icon text-meta pointer-events-none">
66
- <Icon name="resize-corner" size={12} />
71
+ <span
72
+ aria-hidden="true"
73
+ className="kol-textarea-resize-icon"
74
+ onPointerDown={(e) => {
75
+ // The grip IS the resize handle — corner drag, both axes
76
+ // (min 120×40). Width lands on the shell, height on the textarea.
77
+ const shell = e.currentTarget.parentElement
78
+ const ta = shell?.querySelector('textarea')
79
+ if (!shell || !ta) return
80
+ e.preventDefault()
81
+ const startX = e.clientX
82
+ const startY = e.clientY
83
+ const startW = shell.offsetWidth
84
+ const startH = ta.offsetHeight
85
+ const move = (ev) => {
86
+ shell.style.width = `${Math.max(startW + ev.clientX - startX, 120)}px`
87
+ ta.style.height = `${Math.max(startH + ev.clientY - startY, 40)}px`
88
+ }
89
+ const up = () => {
90
+ window.removeEventListener('pointermove', move)
91
+ window.removeEventListener('pointerup', up)
92
+ }
93
+ window.addEventListener('pointermove', move)
94
+ window.addEventListener('pointerup', up)
95
+ }}
96
+ >
97
+ <Icon name="resize-grip" size={12} />
67
98
  </span>
68
99
  </label>
69
100
  )
@@ -1,11 +1,30 @@
1
1
  import React from 'react'
2
2
 
3
+ /**
4
+ * ToggleSwitch — bare by default (2026-07-08 chrome law rewrite).
5
+ *
6
+ * variant="bare" (default) — label + track, no box
7
+ * variant="primary" — filled shell (surface-secondary), button geometry
8
+ * variant="outline" — bordered shell (border-oq-16), button geometry
9
+ *
10
+ * size="sm" / "md" (default) / "lg" — shells match button heights
11
+ * (26/32/40); the track scales with size in all variants.
12
+ *
13
+ * Legacy aliases: 'plain' → bare · 'default' (the old boxed look) → outline.
14
+ * Type via kol-mono-{12,14,16}; no auto-uppercase — casing is authored at
15
+ * the call site (KOL no-auto-casing rule).
16
+ */
17
+
18
+ const SIZE_TYPE = { sm: 'kol-mono-12', md: 'kol-mono-14', lg: 'kol-mono-16' }
19
+ const LEGACY_VARIANTS = { plain: 'bare', default: 'outline' }
20
+
3
21
  const ToggleSwitch = ({
4
22
  label,
5
23
  checked = false,
6
24
  onChange,
7
25
  onToggle,
8
- variant = 'default',
26
+ variant = 'bare',
27
+ size = 'md',
9
28
  className = '',
10
29
  hint,
11
30
  ...props
@@ -15,12 +34,20 @@ const ToggleSwitch = ({
15
34
  if (onChange) onChange(!checked)
16
35
  }
17
36
 
18
- const variantClass = variant === 'plain' ? 'toggle-switch--plain' : ''
37
+ const resolvedVariant = LEGACY_VARIANTS[variant] || variant
38
+
39
+ const cls = [
40
+ 'toggle-switch',
41
+ `toggle-switch--${resolvedVariant}`,
42
+ `toggle-switch--${size}`,
43
+ SIZE_TYPE[size],
44
+ className,
45
+ ].filter(Boolean).join(' ')
19
46
 
20
47
  return (
21
48
  <button
22
49
  type="button"
23
- className={`toggle-switch ${variantClass} ${className}`.trim()}
50
+ className={cls}
24
51
  data-state={checked ? 'on' : 'off'}
25
52
  onClick={handleClick}
26
53
  aria-pressed={checked}
@@ -29,7 +56,7 @@ const ToggleSwitch = ({
29
56
  <span className="toggle-switch-label">
30
57
  {label}
31
58
  {hint ? (
32
- <span className="ml-2 opacity-60 normal-case tracking-normal kol-helper-10">
59
+ <span className="ml-2 opacity-60 kol-helper-10">
33
60
  {hint}
34
61
  </span>
35
62
  ) : null}
@@ -0,0 +1,191 @@
1
+ // Load default chess vector set
2
+ const svgModules = import.meta.glob('./assets/chess-vector-set/*.svg', {
3
+ eager: true,
4
+ query: '?raw',
5
+ import: 'default'
6
+ })
7
+
8
+ // Load extra chess sets (set-1, set-2, set-3)
9
+ const extraSet1Modules = import.meta.glob('./assets/chess-extra-set/set-1/*.svg', {
10
+ eager: true,
11
+ query: '?raw',
12
+ import: 'default'
13
+ })
14
+
15
+ const extraSet2Modules = import.meta.glob('./assets/chess-extra-set/set-2/*.svg', {
16
+ eager: true,
17
+ query: '?raw',
18
+ import: 'default'
19
+ })
20
+
21
+ const extraSet3Modules = import.meta.glob('./assets/chess-extra-set/set-3/*.svg', {
22
+ eager: true,
23
+ query: '?raw',
24
+ import: 'default'
25
+ })
26
+
27
+ const PIECE_KEY_MAP = {
28
+ white: {
29
+ king: 'WKing',
30
+ queen: 'WQueen',
31
+ rook: 'WRook',
32
+ bishop: 'WBiskup',
33
+ knight: 'WKnight',
34
+ pawn: 'WPawn'
35
+ },
36
+ black: {
37
+ king: 'BKing',
38
+ queen: 'BQueen',
39
+ rook: 'BRook',
40
+ bishop: 'BBiskup',
41
+ knight: 'BKnight',
42
+ pawn: 'BPawn'
43
+ }
44
+ }
45
+
46
+ // Map for extra sets (use simpler naming convention)
47
+ const EXTRA_SET_PIECE_MAP = {
48
+ white: {
49
+ king: 'king-1',
50
+ queen: 'queen-1',
51
+ rook: 'rook-1',
52
+ bishop: 'bishop-1',
53
+ knight: 'knight-1',
54
+ pawn: 'pawn-1'
55
+ },
56
+ black: {
57
+ king: 'king',
58
+ queen: 'queen',
59
+ rook: 'rook',
60
+ bishop: 'bishop',
61
+ knight: 'knight',
62
+ pawn: 'pawn'
63
+ }
64
+ }
65
+
66
+ const PIECE_CACHE = Object.entries(svgModules).reduce((acc, [path, svgContent]) => {
67
+ const fileName = path.split('/').pop() || ''
68
+ const key = fileName.replace('.svg', '')
69
+ acc[key] = svgContent
70
+ return acc
71
+ }, {})
72
+
73
+ // Cache for extra sets
74
+ const EXTRA_SET_1_CACHE = Object.entries(extraSet1Modules).reduce((acc, [path, svgContent]) => {
75
+ const fileName = path.split('/').pop() || ''
76
+ const key = fileName.replace('.svg', '')
77
+ acc[key] = svgContent
78
+ return acc
79
+ }, {})
80
+
81
+ const EXTRA_SET_2_CACHE = Object.entries(extraSet2Modules).reduce((acc, [path, svgContent]) => {
82
+ const fileName = path.split('/').pop() || ''
83
+ const key = fileName.replace('.svg', '')
84
+ acc[key] = svgContent
85
+ return acc
86
+ }, {})
87
+
88
+ const EXTRA_SET_3_CACHE = Object.entries(extraSet3Modules).reduce((acc, [path, svgContent]) => {
89
+ const fileName = path.split('/').pop() || ''
90
+ const key = fileName.replace('.svg', '')
91
+ acc[key] = svgContent
92
+ return acc
93
+ }, {})
94
+
95
+ const normalizeSize = (value) => {
96
+ if (typeof value === 'number') {
97
+ return `${value}px`
98
+ }
99
+ if (typeof value === 'string') {
100
+ return value
101
+ }
102
+ return '64px'
103
+ }
104
+
105
+ const applySizeToMarkup = (markup) => {
106
+ let updated = markup
107
+ updated = updated.replace(/width="[^"]*"/i, 'width="100%"')
108
+ updated = updated.replace(/height="[^"]*"/i, 'height="100%"')
109
+ return updated
110
+ }
111
+
112
+ const ChessPieceBase = ({
113
+ size = 64,
114
+ className = '',
115
+ style = {},
116
+ children
117
+ }) => {
118
+ const dimension = normalizeSize(size)
119
+
120
+ return (
121
+ <div
122
+ className={`relative inline-flex aspect-square items-center justify-center ${className}`}
123
+ style={{
124
+ width: dimension,
125
+ height: dimension,
126
+ ...style
127
+ }}
128
+ >
129
+ {children}
130
+ </div>
131
+ )
132
+ }
133
+
134
+ const ChessPiece = ({
135
+ piece,
136
+ color = 'white',
137
+ size = 64,
138
+ className = '',
139
+ style = {},
140
+ pieceSet = 'default'
141
+ }) => {
142
+ const normalizedColor = color === 'black' ? 'black' : 'white'
143
+
144
+ let pieceKey
145
+ let cache
146
+
147
+ // Select piece set and cache
148
+ if (pieceSet === 'set-1') {
149
+ pieceKey = EXTRA_SET_PIECE_MAP[normalizedColor]?.[piece]
150
+ cache = EXTRA_SET_1_CACHE
151
+ } else if (pieceSet === 'set-2') {
152
+ pieceKey = EXTRA_SET_PIECE_MAP[normalizedColor]?.[piece]
153
+ cache = EXTRA_SET_2_CACHE
154
+ } else if (pieceSet === 'set-3') {
155
+ pieceKey = EXTRA_SET_PIECE_MAP[normalizedColor]?.[piece]
156
+ cache = EXTRA_SET_3_CACHE
157
+ } else {
158
+ // Default set
159
+ pieceKey = PIECE_KEY_MAP[normalizedColor]?.[piece]
160
+ cache = PIECE_CACHE
161
+ }
162
+
163
+ const markup = pieceKey ? cache[pieceKey] : null
164
+
165
+ if (!markup) {
166
+ console.warn(`ChessPiece: No asset found for ${normalizedColor} ${piece} in set ${pieceSet}`)
167
+ return null
168
+ }
169
+
170
+ return (
171
+ <ChessPieceBase
172
+ size={size}
173
+ className={`text-current ${className}`}
174
+ style={style}
175
+ >
176
+ <span
177
+ className="block h-full w-full"
178
+ style={{
179
+ display: 'inline-flex',
180
+ alignItems: 'center',
181
+ justifyContent: 'center'
182
+ }}
183
+ dangerouslySetInnerHTML={{ __html: applySizeToMarkup(markup) }}
184
+ />
185
+ </ChessPieceBase>
186
+ )
187
+ }
188
+
189
+ ChessPiece.Base = ChessPieceBase
190
+
191
+ export default ChessPiece