@kolkrabbi/kol-component 0.7.0 → 0.9.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 (131) hide show
  1. package/README.md +8 -0
  2. package/package.json +2 -5
  3. package/src/atoms/RotaryDial.jsx +35 -21
  4. package/src/hooks/colorMath.js +214 -0
  5. package/src/hooks/useEyedropper.js +124 -0
  6. package/src/index.js +15 -18
  7. package/src/molecules/ButtonGroup.jsx +45 -0
  8. package/src/molecules/ColorInputRow.jsx +146 -126
  9. package/src/molecules/PaletteHarmonyWheel.jsx +192 -0
  10. package/src/molecules/Slider.jsx +29 -14
  11. package/src/molecules/SplitToolButton.jsx +133 -0
  12. package/src/organisms/ContentFilters.jsx +2 -2
  13. package/src/organisms/GalleryCarousel.jsx +2 -1
  14. package/src/organisms/LoaderOverlay.jsx +11 -14
  15. package/src/organisms/MediaTileGallery.jsx +57 -0
  16. package/src/organisms/MediaViewer.jsx +89 -64
  17. package/src/atoms/PriceDisplay.jsx +0 -34
  18. package/src/atoms/TextPressure.jsx +0 -331
  19. package/src/atoms/TypeSample.jsx +0 -50
  20. package/src/atoms/TypeSpecCard.jsx +0 -42
  21. package/src/chess/ChessPiece.jsx +0 -191
  22. package/src/chess/apparatus/AlternativeControlsMock.jsx +0 -460
  23. package/src/chess/apparatus/ChessAnalysisLayout.jsx +0 -24
  24. package/src/chess/apparatus/ChessBoard.jsx +0 -210
  25. package/src/chess/apparatus/ChessBoardFullscreen.jsx +0 -49
  26. package/src/chess/apparatus/ChessBoardWithControls.jsx +0 -41
  27. package/src/chess/apparatus/ChessBoardWithSidebar.jsx +0 -122
  28. package/src/chess/apparatus/ChessSidebar.jsx +0 -131
  29. package/src/chess/apparatus/GameArchiveTable.jsx +0 -493
  30. package/src/chess/apparatus/GameSelector.jsx +0 -47
  31. package/src/chess/apparatus/NotationPanel.jsx +0 -70
  32. package/src/chess/apparatus/PlaybackControls.jsx +0 -52
  33. package/src/chess/apparatus/VariationTree.jsx +0 -137
  34. package/src/chess/assets/chess-extra-set/set-1/bishop-1.svg +0 -6
  35. package/src/chess/assets/chess-extra-set/set-1/bishop.svg +0 -6
  36. package/src/chess/assets/chess-extra-set/set-1/king-1.svg +0 -9
  37. package/src/chess/assets/chess-extra-set/set-1/king.svg +0 -7
  38. package/src/chess/assets/chess-extra-set/set-1/knight-1.svg +0 -8
  39. package/src/chess/assets/chess-extra-set/set-1/knight.svg +0 -7
  40. package/src/chess/assets/chess-extra-set/set-1/pawn-1.svg +0 -3
  41. package/src/chess/assets/chess-extra-set/set-1/pawn.svg +0 -3
  42. package/src/chess/assets/chess-extra-set/set-1/queen-1.svg +0 -11
  43. package/src/chess/assets/chess-extra-set/set-1/queen.svg +0 -6
  44. package/src/chess/assets/chess-extra-set/set-1/rook-1.svg +0 -7
  45. package/src/chess/assets/chess-extra-set/set-1/rook.svg +0 -11
  46. package/src/chess/assets/chess-extra-set/set-2/bishop-1.svg +0 -4
  47. package/src/chess/assets/chess-extra-set/set-2/bishop.svg +0 -4
  48. package/src/chess/assets/chess-extra-set/set-2/king-1.svg +0 -4
  49. package/src/chess/assets/chess-extra-set/set-2/king.svg +0 -4
  50. package/src/chess/assets/chess-extra-set/set-2/knight-1.svg +0 -5
  51. package/src/chess/assets/chess-extra-set/set-2/knight.svg +0 -5
  52. package/src/chess/assets/chess-extra-set/set-2/pawn-1.svg +0 -4
  53. package/src/chess/assets/chess-extra-set/set-2/pawn.svg +0 -4
  54. package/src/chess/assets/chess-extra-set/set-2/queen-1.svg +0 -4
  55. package/src/chess/assets/chess-extra-set/set-2/queen.svg +0 -4
  56. package/src/chess/assets/chess-extra-set/set-2/rook-1.svg +0 -5
  57. package/src/chess/assets/chess-extra-set/set-2/rook.svg +0 -5
  58. package/src/chess/assets/chess-extra-set/set-3/bishop-1.svg +0 -10
  59. package/src/chess/assets/chess-extra-set/set-3/bishop.svg +0 -3
  60. package/src/chess/assets/chess-extra-set/set-3/king-1.svg +0 -12
  61. package/src/chess/assets/chess-extra-set/set-3/king.svg +0 -3
  62. package/src/chess/assets/chess-extra-set/set-3/knight-1.svg +0 -3
  63. package/src/chess/assets/chess-extra-set/set-3/knight.svg +0 -10
  64. package/src/chess/assets/chess-extra-set/set-3/pawn-1.svg +0 -10
  65. package/src/chess/assets/chess-extra-set/set-3/pawn.svg +0 -10
  66. package/src/chess/assets/chess-extra-set/set-3/queen-1.svg +0 -10
  67. package/src/chess/assets/chess-extra-set/set-3/queen.svg +0 -3
  68. package/src/chess/assets/chess-extra-set/set-3/rook-1.svg +0 -3
  69. package/src/chess/assets/chess-extra-set/set-3/rook.svg +0 -11
  70. package/src/chess/assets/chess-vector-set/ABoard.svg +0 -164
  71. package/src/chess/assets/chess-vector-set/BBiskup.svg +0 -12
  72. package/src/chess/assets/chess-vector-set/BKing.svg +0 -15
  73. package/src/chess/assets/chess-vector-set/BKnight.svg +0 -13
  74. package/src/chess/assets/chess-vector-set/BPawn.svg +0 -7
  75. package/src/chess/assets/chess-vector-set/BQueen.svg +0 -6
  76. package/src/chess/assets/chess-vector-set/BRook.svg +0 -12
  77. package/src/chess/assets/chess-vector-set/WBiskup.svg +0 -14
  78. package/src/chess/assets/chess-vector-set/WKing.svg +0 -13
  79. package/src/chess/assets/chess-vector-set/WKnight.svg +0 -10
  80. package/src/chess/assets/chess-vector-set/WPawn.svg +0 -8
  81. package/src/chess/assets/chess-vector-set/WQueen.svg +0 -17
  82. package/src/chess/assets/chess-vector-set/WRook.svg +0 -13
  83. package/src/chess/context/ChessControlsContext.jsx +0 -379
  84. package/src/chess/dashboards/ChessHero.jsx +0 -303
  85. package/src/chess/index.js +0 -32
  86. package/src/chess/utils/parsePgnTree.js +0 -97
  87. package/src/dashboards/cards/DashAlertCard.jsx +0 -44
  88. package/src/dashboards/cards/DashChartCard.jsx +0 -44
  89. package/src/dashboards/cards/DashFeaturedCard.jsx +0 -42
  90. package/src/dashboards/cards/DashListCard.jsx +0 -87
  91. package/src/dashboards/cards/DashMetricCard.jsx +0 -59
  92. package/src/dashboards/cards/DashSlotCard.jsx +0 -45
  93. package/src/dashboards/cards/DashStackedBarCard.jsx +0 -70
  94. package/src/dashboards/cards/DashTableCard.jsx +0 -31
  95. package/src/dashboards/cards/_shared/CardHeader.jsx +0 -15
  96. package/src/dashboards/cards/_shared/LegendRow.jsx +0 -25
  97. package/src/dashboards/charts/Candlestick.jsx +0 -79
  98. package/src/dashboards/charts/DonutChart.jsx +0 -87
  99. package/src/dashboards/charts/Heatmap.jsx +0 -79
  100. package/src/dashboards/charts/Histogram.jsx +0 -56
  101. package/src/dashboards/charts/LineChart.jsx +0 -157
  102. package/src/dashboards/charts/ScatterPlot.jsx +0 -93
  103. package/src/dashboards/charts/Sparkline.jsx +0 -63
  104. package/src/dashboards/index.js +0 -28
  105. package/src/dashboards/layout/DashboardGrid.jsx +0 -30
  106. package/src/dashboards/layout/GridCard.jsx +0 -44
  107. package/src/dashboards/shared/DashTooltip.jsx +0 -6
  108. package/src/dashboards/shared/formatMetric.js +0 -26
  109. package/src/dashboards/shared/useChartTooltip.js +0 -25
  110. package/src/dashboards/shared/useCountUp.js +0 -107
  111. package/src/molecules/ArticleCard.jsx +0 -178
  112. package/src/molecules/WorkListItem.jsx +0 -83
  113. package/src/molecules/foundry/SpecimenSectionHeader.jsx +0 -89
  114. package/src/organisms/ArticleHeader.jsx +0 -95
  115. package/src/organisms/ColorLoader.jsx +0 -155
  116. package/src/organisms/DiagonalMarqueeRiver.jsx +0 -138
  117. package/src/organisms/ParallaxShelf.jsx +0 -141
  118. package/src/organisms/PortableTextRenderer.jsx +0 -115
  119. package/src/organisms/ProductDetailLayout.jsx +0 -189
  120. package/src/organisms/ScrollDriftGallery.jsx +0 -214
  121. package/src/organisms/StackHero.jsx +0 -83
  122. package/src/organisms/WorkCard.jsx +0 -120
  123. package/src/organisms/WorkViewToggle.jsx +0 -170
  124. package/src/organisms/foundry/FontPreviewSection.jsx +0 -187
  125. package/src/organisms/foundry/FoundryCharacterSets.jsx +0 -113
  126. package/src/organisms/foundry/GlyphMetricsGrid.jsx +0 -335
  127. package/src/organisms/foundry/TypefaceHero.jsx +0 -107
  128. package/src/organisms/foundry/TypefaceStyleSection.jsx +0 -163
  129. package/src/organisms/foundry/VariableFontSection.jsx +0 -158
  130. package/src/organisms/foundry/glyphData.js +0 -30
  131. package/src/organisms/foundry/index.js +0 -21
@@ -1,170 +0,0 @@
1
- import { useEffect, useRef, useState } from 'react'
2
- import { Icon } from '@kolkrabbi/kol-icons'
3
-
4
- /* taxonomy-ok: composites a segmented toggle + an inline search into one
5
- * space-trading control (organism); nests kol-icons's Icon (infrastructure). */
6
-
7
- const CUBIC_EASE = 'cubic-bezier(0.16, 1, 0.3, 1)'
8
- /* The pill uses a springier overshoot ease, distinct from CUBIC_EASE. */
9
- const PILL_EASE = 'cubic-bezier(0.34, 1.2, 0.64, 1)'
10
-
11
- /**
12
- * WorkViewToggle — the `/work` header control: a pill-shaped segmented
13
- * **Shelf ⟷ List** view switch with a sliding-pill highlight, beside an
14
- * expandable inline search. Resting: the two-option toggle + a collapsed
15
- * search icon-button. Opening search collapses the toggle to zero width (fades
16
- * out) and expands the field to full width, revealing a round close button on
17
- * the left — the three siblings trade horizontal space via animated
18
- * width/margin/opacity.
19
- *
20
- * Its own component, not a variant of DS ViewToggle / SegmentedToggle: the
21
- * animated sliding pill, the expanding active-option icon, and the embedded
22
- * search choreography are materially different. All animated numerics are
23
- * inline `style` (width/left/margin/opacity + the two eases).
24
- *
25
- * Controlled seam replacing the app's WorkViewContext: `view`/`onView` +
26
- * `query`/`onQuery`. `searchOpen` is internal state (open on the search icon,
27
- * close on the × / Escape); closing clears the controlled query via
28
- * `onQuery('')`. No router, no context.
29
- *
30
- * Text casing: labels are authored data (`shelfLabel`/`listLabel`), no
31
- * text-transform.
32
- *
33
- * @param {string} view 'shelf' | 'list' — active view; positions the pill + swaps icon/color
34
- * @param {Function} onView (mode) => void — fires on Shelf/List click
35
- * @param {string} query controlled search value (default '')
36
- * @param {Function} onQuery (value) => void — fires on input change; called with '' on close
37
- * @param {string} shelfLabel first option label (default 'Shelf')
38
- * @param {string} listLabel second option label (default 'List')
39
- * @param {string} shelfIcon first option icon name (default 'library')
40
- * @param {string} listIcon second option icon name (default 'list')
41
- * @param {string} placeholder search input placeholder (default '')
42
- * @param {string} className extra classes on the root
43
- */
44
- export default function WorkViewToggle({
45
- view = 'shelf',
46
- onView,
47
- query = '',
48
- onQuery,
49
- shelfLabel = 'Shelf',
50
- listLabel = 'List',
51
- shelfIcon = 'library',
52
- listIcon = 'list',
53
- placeholder = '',
54
- className = '',
55
- }) {
56
- const [open, setOpen] = useState(false)
57
- const inputRef = useRef(null)
58
-
59
- useEffect(() => {
60
- if (open) inputRef.current?.focus()
61
- }, [open])
62
-
63
- const closeSearch = () => {
64
- setOpen(false)
65
- onQuery?.('')
66
- }
67
-
68
- const isShelf = view === 'shelf'
69
- const isList = view === 'list'
70
-
71
- const collapse = `width 600ms ${CUBIC_EASE}, margin 600ms ${CUBIC_EASE}, opacity 300ms ${CUBIC_EASE}`
72
- const inactiveColor = 'color-mix(in srgb, var(--kol-surface-on-primary) 80%, transparent)'
73
-
74
- const optionIcon = (name, on) => (
75
- <span
76
- className="inline-flex overflow-hidden flex-shrink-0"
77
- style={{ width: on ? 20 : 0, marginRight: on ? 8 : 0, opacity: on ? 1 : 0, transition: collapse }}
78
- >
79
- <Icon name={name} size={20} />
80
- </span>
81
- )
82
-
83
- return (
84
- <div className={`flex items-center ${className}`.trim()}>
85
- {/* Close button — appears when search is open */}
86
- <span
87
- className="inline-flex overflow-hidden flex-shrink-0"
88
- style={{ width: open ? 36 : 0, marginRight: open ? 12 : 0, opacity: open ? 1 : 0, transition: collapse }}
89
- >
90
- <button
91
- type="button"
92
- className="flex items-center justify-center w-9 h-9 rounded-full bg-fg-96 transition-colors hover:bg-fg-88"
93
- style={{ color: 'var(--kol-surface-primary)' }}
94
- onClick={closeSearch}
95
- aria-label="Close search"
96
- >
97
- <Icon name="cross" size={20} />
98
- </button>
99
- </span>
100
-
101
- {/* Toggle — collapses when search is open */}
102
- <div
103
- className="relative flex items-center rounded-full bg-fg-04 h-9 overflow-hidden"
104
- style={{ width: open ? 0 : 176, marginRight: open ? 0 : 12, opacity: open ? 0 : 1, transition: collapse }}
105
- >
106
- <div
107
- className="absolute top-0 h-9 rounded-full bg-fg-96"
108
- style={{ width: 96, left: isShelf ? 0 : 80, transition: `left 600ms ${PILL_EASE}` }}
109
- />
110
-
111
- <button
112
- type="button"
113
- className="relative z-10 flex items-center justify-center rounded-full h-9 kol-helper-14 transition-colors duration-300"
114
- style={{
115
- width: 96,
116
- letterSpacing: 0,
117
- color: isShelf ? 'var(--kol-surface-primary)' : inactiveColor,
118
- paddingRight: isShelf ? undefined : 8,
119
- }}
120
- onClick={() => onView?.('shelf')}
121
- aria-pressed={isShelf}
122
- >
123
- {optionIcon(shelfIcon, isShelf)}
124
- {shelfLabel}
125
- </button>
126
- <button
127
- type="button"
128
- className="relative z-10 flex items-center justify-center rounded-full h-9 -ml-4 kol-helper-14 transition-colors duration-300"
129
- style={{
130
- width: 96,
131
- letterSpacing: 0,
132
- color: isList ? 'var(--kol-surface-primary)' : inactiveColor,
133
- paddingLeft: isList ? undefined : 8,
134
- }}
135
- onClick={() => onView?.('list')}
136
- aria-pressed={isList}
137
- >
138
- {optionIcon(listIcon, isList)}
139
- {listLabel}
140
- </button>
141
- </div>
142
-
143
- {/* Search — icon button expands to a search field */}
144
- <div
145
- className="flex items-center bg-fg-04 rounded-full h-9"
146
- style={{ width: open ? 280 : 36, transition: `width 600ms ${CUBIC_EASE}` }}
147
- >
148
- <button
149
- type="button"
150
- className={`flex items-center justify-center w-9 h-9 rounded-full text-auto flex-shrink-0 border border-transparent ${open ? '' : 'transition-colors hover:border-fg-12'}`}
151
- onClick={() => !open && setOpen(true)}
152
- aria-label="Search projects"
153
- >
154
- <Icon name="search" size={16} className="text-fg-80" />
155
- </button>
156
- {open && (
157
- <input
158
- ref={inputRef}
159
- type="text"
160
- value={query}
161
- onChange={(e) => onQuery?.(e.target.value)}
162
- placeholder={placeholder}
163
- className="bg-transparent outline-none kol-helper-14 flex-1 text-fg-80 caret-current pr-4 min-w-0"
164
- onKeyDown={(e) => { if (e.key === 'Escape') closeSearch() }}
165
- />
166
- )}
167
- </div>
168
- </div>
169
- )
170
- }
@@ -1,187 +0,0 @@
1
- import { useEffect, useRef, useState } from 'react'
2
- import Dropdown from '../../molecules/Dropdown.jsx'
3
- import Slider from '../../molecules/Slider.jsx'
4
- import SpecimenSectionHeader from '../../molecules/foundry/SpecimenSectionHeader.jsx'
5
- import { SPECIMEN_SAMPLE_TEXT } from './glyphData.js'
6
-
7
- /* taxonomy-ok: organism — nests Dropdown, Slider, SpecimenSectionHeader
8
- * (relative imports) plus a same-file FontPreviewItem. */
9
-
10
- const WEIGHT_VALUES = {
11
- Thin: 100,
12
- Extralight: 200,
13
- Light: 300,
14
- Regular: 400,
15
- Medium: 500,
16
- Semibold: 600,
17
- Bold: 700,
18
- Extrabold: 800,
19
- Black: 900,
20
- }
21
-
22
- /**
23
- * FontPreviewItem (same-file) — one preview block: a weight dropdown + Size /
24
- * Leading / Spacing sliders over an editable, in-family sample. Weight is
25
- * seeded from the section header (re-syncs when the parent's selection changes);
26
- * size/leading/spacing are local. Leading maps 0–50 → 90–140% line-height.
27
- */
28
- function FontPreviewItem({
29
- fontFamily,
30
- fontStyle = 'normal',
31
- text = SPECIMEN_SAMPLE_TEXT,
32
- initialWeight = 'Regular',
33
- initialSize = 64,
34
- initialLeading = 10,
35
- lineClamp = null,
36
- availableWeights,
37
- }) {
38
- const [size, setSize] = useState(initialSize)
39
- const [leading, setLeading] = useState(initialLeading)
40
- const [spacing, setSpacing] = useState(0)
41
- const [selectedWeight, setSelectedWeight] = useState(initialWeight)
42
- const editableRef = useRef(null)
43
-
44
- useEffect(() => setSelectedWeight(initialWeight), [initialWeight])
45
- useEffect(() => {
46
- if (editableRef.current && editableRef.current.textContent !== text) {
47
- editableRef.current.textContent = text
48
- }
49
- }, [text])
50
-
51
- const weightOptions = availableWeights.map((label) => ({ label, value: label }))
52
- const numericWeight = WEIGHT_VALUES[selectedWeight] || 400
53
-
54
- return (
55
- <div className="self-stretch min-h-40 p-6 rounded border border-fg-16 flex flex-col justify-start items-start gap-6 bg-surface-primary">
56
- <div className="self-stretch flex justify-start items-start gap-8">
57
- <div className="flex-shrink-0">
58
- <Dropdown
59
- options={weightOptions}
60
- value={selectedWeight}
61
- onChange={setSelectedWeight}
62
- variant="minimal"
63
- />
64
- </div>
65
- <div className="flex-1 flex justify-start items-start gap-8">
66
- <Slider label="Size" min={12} max={200} value={size} onChange={setSize} variant="minimal" className="flex-1" />
67
- <Slider
68
- label="Leading"
69
- min={0}
70
- max={50}
71
- value={leading}
72
- onChange={setLeading}
73
- variant="minimal"
74
- className="hidden md:flex flex-1"
75
- formatValue={(val) => 90 + val}
76
- />
77
- <Slider label="Spacing" min={-50} max={50} value={spacing} onChange={setSpacing} variant="minimal" className="hidden md:flex flex-1" />
78
- </div>
79
- </div>
80
-
81
- <div className="self-stretch rounded flex justify-start items-start overflow-hidden">
82
- <div
83
- ref={editableRef}
84
- contentEditable
85
- suppressContentEditableWarning
86
- className="flex-1 bg-transparent outline-none border-none text-auto"
87
- style={{
88
- fontFamily,
89
- fontStyle,
90
- fontWeight: numericWeight,
91
- fontSize: `${size}px`,
92
- lineHeight: `${90 + leading}%`,
93
- letterSpacing: `${spacing}px`,
94
- wordWrap: 'break-word',
95
- overflowWrap: 'break-word',
96
- whiteSpace: 'normal',
97
- hyphens: 'none',
98
- maxWidth: '100%',
99
- ...(lineClamp && {
100
- display: '-webkit-box',
101
- WebkitLineClamp: lineClamp,
102
- WebkitBoxOrient: 'vertical',
103
- overflow: 'hidden',
104
- }),
105
- }}
106
- />
107
- </div>
108
- </div>
109
- )
110
- }
111
-
112
- const DEFAULT_WEIGHTS = [
113
- 'Thin', 'Extralight', 'Light', 'Regular', 'Medium', 'Semibold', 'Bold', 'Extrabold', 'Black',
114
- ]
115
- const SIZE_LADDER = [
116
- { size: 96, lines: 1 },
117
- { size: 64, lines: 3 },
118
- { size: 48, lines: 4 },
119
- { size: 24, lines: 5, leading: 50 },
120
- ]
121
-
122
- /**
123
- * FontPreviewSection — a stacked multi-size preview: the shared header (weight +
124
- * Roman/Italic dropdowns) above a ladder of preview blocks at 96 / 64 / 48 / 24
125
- * px, each pre-seeded with the section's selected weight + italic state. Read a
126
- * family from display down to body size at a chosen weight.
127
- *
128
- * Font-agnostic: the monorepo's TGMalromur/Málrómur defaults are dropped —
129
- * `fontFamily` + `badgeText` are prop-driven. The size ladder + weight ramp are
130
- * sensible defaults, overridable via `sizes` / `availableWeights`.
131
- *
132
- * Text casing: badgeText, weight labels and style labels render verbatim.
133
- *
134
- * @param {string} props.fontFamily - Family rendered in every preview item.
135
- * @param {string} props.badgeText - Header title.
136
- * @param {boolean} props.showDropdown - Render the style dropdown; seeds italic (default true).
137
- * @param {string[]} props.availableWeights - Weight dropdown options (default 9-stop ramp).
138
- * @param {string} props.initialWeight - Initially selected weight (default 'Regular').
139
- * @param {Array<{size,lines,leading?}>} props.sizes - Size ladder (default 96/64/48/24).
140
- */
141
- const FontPreviewSection = ({
142
- fontFamily = 'system-ui, sans-serif',
143
- badgeText = 'Preview',
144
- showDropdown = true,
145
- availableWeights = DEFAULT_WEIGHTS,
146
- initialWeight = 'Regular',
147
- sizes = SIZE_LADDER,
148
- }) => {
149
- const [selectedStyleVariant, setSelectedStyleVariant] = useState(showDropdown ? 'italic' : 'roman')
150
- const [selectedWeight, setSelectedWeight] = useState(initialWeight)
151
- const isItalic = selectedStyleVariant === 'italic'
152
- const weightOptions = availableWeights.map((label) => ({ label, value: label }))
153
-
154
- return (
155
- <section className="w-full py-12 lg:py-16">
156
- <div className="max-w-[1400px] mx-auto flex flex-col gap-8">
157
- <SpecimenSectionHeader
158
- selectedStyle={selectedStyleVariant}
159
- onStyleChange={setSelectedStyleVariant}
160
- showDropdown={showDropdown}
161
- badgeText={badgeText}
162
- icon="foundation"
163
- size="sm"
164
- selectedWeight={selectedWeight}
165
- onWeightChange={setSelectedWeight}
166
- showWeightDropdown={availableWeights.length > 0}
167
- weightOptions={weightOptions}
168
- />
169
-
170
- {sizes.map(({ size, lines, leading }) => (
171
- <FontPreviewItem
172
- key={size}
173
- fontFamily={fontFamily}
174
- fontStyle={isItalic ? 'italic' : 'normal'}
175
- initialWeight={selectedWeight}
176
- availableWeights={availableWeights}
177
- initialSize={size}
178
- initialLeading={leading}
179
- lineClamp={lines}
180
- />
181
- ))}
182
- </div>
183
- </section>
184
- )
185
- }
186
-
187
- export default FontPreviewSection
@@ -1,113 +0,0 @@
1
- import { useState } from 'react'
2
- import Button from '../../atoms/Button.jsx'
3
- import SpecimenSectionHeader from '../../molecules/foundry/SpecimenSectionHeader.jsx'
4
- import { glyphSets, glyphCategories } from './glyphData.js'
5
-
6
- /* taxonomy-ok: organism — nests Button + SpecimenSectionHeader (relative
7
- * imports) plus same-file GlyphCategory / GlyphItem. */
8
-
9
- /** GlyphItem (same-file) — one bordered glyph cell that flips to the inverse
10
- * surface on hover. Display-only (character sets aren't selectable). */
11
- function GlyphItem({ glyph, fontFamily, fontStyle }) {
12
- return (
13
- <div
14
- className="aspect-square border rounded-lg flex items-center justify-center text-3xl leading-none transition-colors duration-300 w-16 h-16 hover:bg-surface-inverse"
15
- style={{ borderColor: 'var(--kol-border-default)', fontFamily, fontStyle }}
16
- >
17
- {glyph}
18
- </div>
19
- )
20
- }
21
-
22
- /** GlyphCategory (same-file) — a labeled category title over a wrap grid of
23
- * GlyphItems, all rendered in the chosen family/style. */
24
- function GlyphCategory({ title, glyphs, fontFamily, fontStyle, className = '' }) {
25
- return (
26
- <div className={`w-full flex flex-col gap-4 ${className}`.trim()}>
27
- <h3 className="kol-mono-12">{title}</h3>
28
- <div className="flex flex-wrap gap-4 w-full">
29
- {glyphs?.map((glyph, i) => (
30
- <GlyphItem key={i} glyph={glyph} fontFamily={fontFamily} fontStyle={fontStyle} />
31
- ))}
32
- </div>
33
- </div>
34
- )
35
- }
36
-
37
- /**
38
- * FoundryCharacterSets — the character-set browser: the shared header ("Character
39
- * Set" + Roman/Italic dropdown) above a stack of glyph categories rendered in
40
- * the chosen family/style. Collapsed by default to the first two categories
41
- * under a fade with a "Show All Glyphs" reveal; clicking expands to every
42
- * category.
43
- *
44
- * Light-theme fix ([source-bug]): the monorepo faded with a hardcoded
45
- * `rgba(18,18,21,…)` — the app's literal dark background baked in, which broke
46
- * in light theme. Here the mask runs `transparent → var(--kol-surface-primary)`
47
- * so it matches whatever surface it sits on, in either theme. `fontFamily` is
48
- * prop-driven (no brand default).
49
- *
50
- * Text casing: label, category titles and the reveal button render verbatim.
51
- *
52
- * @param {string} props.fontFamily - Family every glyph renders in.
53
- * @param {boolean} props.showDropdown - Render the Roman/Italic dropdown (default true).
54
- * @param {number} props.collapsedCount - Categories shown when collapsed (default 2).
55
- */
56
- const FoundryCharacterSets = ({
57
- fontFamily = 'system-ui, sans-serif',
58
- showDropdown = true,
59
- collapsedCount = 2,
60
- }) => {
61
- const [selectedStyle, setSelectedStyle] = useState('italic')
62
- const [showAll, setShowAll] = useState(false)
63
-
64
- const visibleCategories = showAll ? glyphCategories : glyphCategories.slice(0, collapsedCount)
65
- const fontStyle = selectedStyle === 'italic' ? 'italic' : 'normal'
66
-
67
- return (
68
- <section className="w-full py-12 lg:py-16">
69
- <div className="max-w-[1400px] mx-auto flex flex-col gap-8">
70
- <SpecimenSectionHeader
71
- label="Character Set"
72
- size="sm"
73
- selectedStyle={selectedStyle}
74
- onStyleChange={setSelectedStyle}
75
- showDropdown={showDropdown}
76
- />
77
-
78
- <div className="w-full flex flex-col gap-12 relative">
79
- {visibleCategories.map((category, index) => (
80
- <GlyphCategory
81
- key={category.key}
82
- title={category.title}
83
- glyphs={glyphSets[category.key]}
84
- fontFamily={fontFamily}
85
- fontStyle={fontStyle}
86
- className={!showAll && index === collapsedCount - 1 ? 'relative' : ''}
87
- />
88
- ))}
89
-
90
- {!showAll && (
91
- <>
92
- {/* Theme-aware fade (fixes the hardcoded rgba(18,18,21) dark mask). */}
93
- <div
94
- className="absolute bottom-0 left-0 right-0 h-64 pointer-events-none"
95
- style={{
96
- background:
97
- 'linear-gradient(to bottom, transparent 13%, var(--kol-surface-primary) 86%)',
98
- }}
99
- />
100
- <div className="absolute bottom-0 left-0 right-0 flex justify-center">
101
- <Button onClick={() => setShowAll(true)} variant="outline">
102
- Show All Glyphs
103
- </Button>
104
- </div>
105
- </>
106
- )}
107
- </div>
108
- </div>
109
- </section>
110
- )
111
- }
112
-
113
- export default FoundryCharacterSets