@kolkrabbi/kol-component 0.1.2 → 0.3.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 (110) hide show
  1. package/package.json +12 -4
  2. package/src/atoms/AnimatedTitle.jsx +108 -0
  3. package/src/atoms/AssetGrid.jsx +29 -0
  4. package/src/{primitives → atoms}/AssetPlaceholder.jsx +4 -2
  5. package/src/atoms/Avatar.jsx +4 -4
  6. package/src/atoms/Button.jsx +23 -9
  7. package/src/atoms/CopyButton.jsx +50 -0
  8. package/src/atoms/CurveOverlay.jsx +180 -0
  9. package/src/atoms/DocsToc.jsx +48 -0
  10. package/src/{molecules → atoms}/DropdownTagFilter.jsx +2 -1
  11. package/src/atoms/EmptyState.jsx +22 -0
  12. package/src/atoms/Figure.jsx +27 -0
  13. package/src/atoms/HlsVideo.jsx +56 -0
  14. package/src/atoms/Input.jsx +7 -2
  15. package/src/atoms/OverlayGlassPanel.jsx +40 -0
  16. package/src/atoms/PriceDisplay.jsx +34 -0
  17. package/src/atoms/ProsePreview.jsx +53 -0
  18. package/src/atoms/RotaryDial.jsx +150 -0
  19. package/src/atoms/SearchInput.jsx +108 -0
  20. package/src/{molecules → atoms}/SegmentedToggle.jsx +33 -14
  21. package/src/atoms/TextPressure.jsx +331 -0
  22. package/src/atoms/TiltCard.jsx +127 -0
  23. package/src/atoms/ToggleCheckbox.jsx +1 -1
  24. package/src/atoms/ToggleSwitch.jsx +1 -1
  25. package/src/atoms/TypeSample.jsx +50 -0
  26. package/src/atoms/TypeSpecCard.jsx +42 -0
  27. package/src/{molecules → atoms}/ViewToggle.jsx +6 -3
  28. package/src/graphics/Graphic.jsx +56 -18
  29. package/src/graphics/graphicData.js +19 -0
  30. package/src/hooks/cssVar.js +53 -0
  31. package/src/hooks/useAxisAnimation.js +91 -0
  32. package/src/hooks/usePrefersReducedMotion.js +21 -0
  33. package/src/hooks/useTilt.js +49 -0
  34. package/src/index.js +119 -39
  35. package/src/{primitives → molecules}/Accordion.jsx +4 -1
  36. package/src/molecules/AlignmentGrid.jsx +53 -0
  37. package/src/molecules/ArticleCard.jsx +178 -0
  38. package/src/molecules/CardFeatureItem.jsx +130 -0
  39. package/src/molecules/CodeBlock.jsx +11 -0
  40. package/src/molecules/ColorInputRow.jsx +179 -0
  41. package/src/molecules/ColorRamp.jsx +114 -0
  42. package/src/{atoms → molecules}/ColorSwatch.jsx +1 -1
  43. package/src/molecules/Dropdown.jsx +3 -2
  44. package/src/molecules/FramedMediaBand.jsx +56 -0
  45. package/src/{primitives → molecules}/Image.jsx +1 -1
  46. package/src/molecules/ImageBlock.jsx +34 -0
  47. package/src/molecules/MediaCard.jsx +97 -0
  48. package/src/molecules/MediaRow.jsx +49 -0
  49. package/src/molecules/MenuItem.jsx +3 -2
  50. package/src/molecules/MenuPopover.jsx +11 -96
  51. package/src/molecules/SelectionOverlay.jsx +108 -0
  52. package/src/molecules/ShapeDropdown.jsx +92 -0
  53. package/src/molecules/ShellDrawer.jsx +169 -0
  54. package/src/molecules/ShellSearchOverlay.jsx +177 -0
  55. package/src/{atoms → molecules}/Slider.jsx +1 -1
  56. package/src/molecules/SpecList.jsx +30 -0
  57. package/src/molecules/SpectrumControls.jsx +504 -0
  58. package/src/molecules/SwatchControls.jsx +217 -0
  59. package/src/molecules/TabsRow.jsx +87 -0
  60. package/src/molecules/VideoBlock.jsx +86 -0
  61. package/src/molecules/WorkListItem.jsx +83 -0
  62. package/src/molecules/foundry/SpecimenSectionHeader.jsx +89 -0
  63. package/src/organisms/ArticleHeader.jsx +95 -0
  64. package/src/organisms/AsciiCursor.jsx +526 -0
  65. package/src/organisms/BentoCard.jsx +187 -0
  66. package/src/organisms/Canvas.jsx +299 -0
  67. package/src/organisms/ColorLoader.jsx +155 -0
  68. package/src/{molecules → organisms}/ContentFilters.jsx +2 -2
  69. package/src/organisms/CtaGlobal.jsx +67 -0
  70. package/src/organisms/DiagonalMarqueeRiver.jsx +138 -0
  71. package/src/organisms/EditorShell.jsx +111 -0
  72. package/src/organisms/ErrorBoundary.jsx +70 -0
  73. package/src/organisms/FeatureSplit.jsx +82 -0
  74. package/src/organisms/FeaturedCarousel.jsx +258 -0
  75. package/src/organisms/FeaturesCardSection.jsx +90 -0
  76. package/src/organisms/FullBleedHero.jsx +111 -0
  77. package/src/organisms/GalleryCarousel.jsx +83 -0
  78. package/src/organisms/LoaderOverlay.jsx +30 -0
  79. package/src/organisms/MediaViewer.jsx +95 -0
  80. package/src/organisms/NewsletterBand.jsx +122 -0
  81. package/src/organisms/ParallaxShelf.jsx +141 -0
  82. package/src/organisms/PortableTextRenderer.jsx +115 -0
  83. package/src/organisms/ProductDetailLayout.jsx +189 -0
  84. package/src/organisms/ScrollDriftGallery.jsx +214 -0
  85. package/src/organisms/SpectrumGrid.jsx +90 -0
  86. package/src/organisms/StackHero.jsx +83 -0
  87. package/src/organisms/WorkCard.jsx +120 -0
  88. package/src/organisms/WorkViewToggle.jsx +170 -0
  89. package/src/organisms/foundry/FontPreviewSection.jsx +187 -0
  90. package/src/organisms/foundry/FoundryCharacterSets.jsx +113 -0
  91. package/src/organisms/foundry/GlyphMetricsGrid.jsx +335 -0
  92. package/src/organisms/foundry/TypefaceHero.jsx +107 -0
  93. package/src/organisms/foundry/TypefaceStyleSection.jsx +163 -0
  94. package/src/organisms/foundry/VariableFontSection.jsx +158 -0
  95. package/src/organisms/foundry/glyphData.js +30 -0
  96. package/src/organisms/foundry/index.js +21 -0
  97. package/src/primitives/CodeBlock.jsx +0 -41
  98. /package/src/{molecules → atoms}/Badge.jsx +0 -0
  99. /package/src/{primitives → atoms}/ExitPreview.jsx +0 -0
  100. /package/src/{primitives → atoms}/FullscreenOverlay.jsx +0 -0
  101. /package/src/{molecules → atoms}/LabeledControl.jsx +0 -0
  102. /package/src/{molecules → atoms}/Pill.jsx +0 -0
  103. /package/src/{molecules → atoms}/Popover.jsx +0 -0
  104. /package/src/{molecules → atoms}/QuantityInput.jsx +0 -0
  105. /package/src/{molecules → atoms}/QuantityStepper.jsx +0 -0
  106. /package/src/{molecules → atoms}/Section.jsx +0 -0
  107. /package/src/{molecules → atoms}/SectionLabel.jsx +0 -0
  108. /package/src/{molecules → atoms}/Tag.jsx +0 -0
  109. /package/src/{molecules → atoms}/ToggleBracket.jsx +0 -0
  110. /package/src/{primitives → organisms}/Carousel.jsx +0 -0
@@ -4,29 +4,54 @@
4
4
  * Globs ./svg/<category>/<name>.svg at build time. Usage:
5
5
  * <Graphic category="patterns" name="pattern-05" />
6
6
  *
7
- * When an asset is missing, renders an AssetPlaceholder with the
8
- * category/name labeledmissing graphics are visible rather than silently
9
- * empty. Matches the logo loader pattern (loader lives with assets).
7
+ * The raw SVG strings (~4.8 MB) live in ./graphicData.js behind a single
8
+ * dynamic importtheir chunk streams in parallel with boot instead of
9
+ * bloating the consumer's entry chunk (same pattern as kol-loader's Icon).
10
+ * Until the chunk lands, renders a sized empty box (no layout shift); a
11
+ * genuinely missing asset renders an AssetPlaceholder so it's visible rather
12
+ * than silently empty.
10
13
  */
11
- import AssetPlaceholder from '../primitives/AssetPlaceholder.jsx'
14
+ import { useEffect, useState } from 'react'
15
+ import AssetPlaceholder from '../atoms/AssetPlaceholder.jsx'
12
16
 
13
- const svgModules = import.meta.glob('./svg/**/*.svg', { eager: true, query: '?raw', import: 'default' })
17
+ let RAW = null // category name raw svg, once the chunk resolves
18
+ let loadPromise = null
19
+ const subscribers = new Set()
14
20
 
15
- // category name raw SVG string. Exported so gallery/table consumers
16
- // (e.g. AssetTable) read raw markup without a cross-package glob.
17
- export const GRAPHIC_RAW = Object.entries(svgModules).reduce((acc, [path, svg]) => {
21
+ const loadGraphics = () => {
22
+ if (!loadPromise) {
23
+ loadPromise = import('./graphicData.js').then((mod) => {
24
+ RAW = mod.GRAPHIC_RAW
25
+ subscribers.forEach((fn) => fn())
26
+ return mod
27
+ })
28
+ }
29
+ return loadPromise
30
+ }
31
+
32
+ // Start fetching at module eval — in flight alongside the rest of boot.
33
+ loadGraphics()
34
+
35
+ const useGraphicsReady = () => {
36
+ const [ready, setReady] = useState(() => !!RAW)
37
+ useEffect(() => {
38
+ if (RAW) return undefined
39
+ const cb = () => setReady(true)
40
+ subscribers.add(cb)
41
+ loadGraphics()
42
+ return () => subscribers.delete(cb)
43
+ }, [])
44
+ return ready
45
+ }
46
+
47
+ /* Inventory (category → sorted names) from a keys-only glob — paths resolve at
48
+ * build time without pulling any SVG content into the chunk. */
49
+ export const GRAPHICS = Object.keys(import.meta.glob('./svg/**/*.svg')).reduce((acc, path) => {
18
50
  const [category, file] = path.replace('./svg/', '').split('/')
19
- const name = file.replace('.svg', '')
20
- if (!acc[category]) acc[category] = {}
21
- acc[category][name] = svg
51
+ ;(acc[category] ||= []).push(file.replace('.svg', ''))
22
52
  return acc
23
53
  }, {})
24
-
25
- const GRAPHIC_CACHE = GRAPHIC_RAW
26
-
27
- export const GRAPHICS = Object.fromEntries(
28
- Object.entries(GRAPHIC_CACHE).map(([category, items]) => [category, Object.keys(items).sort()])
29
- )
54
+ for (const names of Object.values(GRAPHICS)) names.sort()
30
55
 
31
56
  export default function Graphic({
32
57
  category,
@@ -36,7 +61,20 @@ export default function Graphic({
36
61
  title,
37
62
  aspectRatio = '1 / 1',
38
63
  }) {
39
- const raw = GRAPHIC_CACHE[category]?.[name]
64
+ const ready = useGraphicsReady()
65
+
66
+ // Chunk still streaming — hold a same-sized box so layout doesn't shift.
67
+ if (!ready) {
68
+ return (
69
+ <span
70
+ aria-hidden="true"
71
+ className={`kol-graphic inline-flex w-full h-auto ${className}`.trim()}
72
+ style={{ aspectRatio, ...style }}
73
+ />
74
+ )
75
+ }
76
+
77
+ const raw = RAW[category]?.[name]
40
78
  if (!raw) {
41
79
  if (import.meta.env.DEV) console.warn(`Graphic: ${category}/${name} not found`)
42
80
  return <AssetPlaceholder category={category} name={name} aspectRatio={aspectRatio} note="pending" className={className} />
@@ -0,0 +1,19 @@
1
+ /**
2
+ * graphicData — the raw SVG map behind <Graphic>.
3
+ *
4
+ * ~4.8 MB of illustration SVGs (some wrap embedded raster images) eager-inline
5
+ * here, but this module is only reached via the dynamic `import()` in
6
+ * Graphic.jsx — the bundler splits it into its own async chunk instead of
7
+ * folding it into the consumer's entry chunk. Same pattern as kol-loader's
8
+ * iconData.js (kol-labs-single 2026-06-19 entry-chunk fix).
9
+ */
10
+ const svgModules = import.meta.glob('./svg/**/*.svg', { eager: true, query: '?raw', import: 'default' })
11
+
12
+ // category → name → raw SVG string.
13
+ export const GRAPHIC_RAW = Object.entries(svgModules).reduce((acc, [path, svg]) => {
14
+ const [category, file] = path.replace('./svg/', '').split('/')
15
+ const name = file.replace('.svg', '')
16
+ if (!acc[category]) acc[category] = {}
17
+ acc[category][name] = svg
18
+ return acc
19
+ }, {})
@@ -0,0 +1,53 @@
1
+ /**
2
+ * CSS-variable + color utilities for token-doc widgets (ColorRamp,
3
+ * SpectrumGrid, swatch tables). One probe-based resolver instead of the
4
+ * three inline getComputedStyle forks the monorepo sources carried.
5
+ * Plain functions, not hooks — they live here because src/hooks is the
6
+ * taxonomy's only non-component folder.
7
+ */
8
+
9
+ /** Read a custom property's raw value off an element (default: :root). */
10
+ export function resolveCssVar(name, el) {
11
+ if (typeof document === 'undefined') return ''
12
+ const target = el || document.documentElement
13
+ const prop = name.startsWith('--') ? name : `--${name}`
14
+ return getComputedStyle(target).getPropertyValue(prop).trim()
15
+ }
16
+
17
+ /**
18
+ * Resolve ANY css color expression (var(), color-mix(), keywords) to the
19
+ * browser's computed `rgb(...)`/`rgba(...)` form via a detached probe —
20
+ * getPropertyValue alone can't evaluate color-mix.
21
+ */
22
+ export function resolveCssColor(value, el) {
23
+ if (typeof document === 'undefined') return ''
24
+ const probe = document.createElement('span')
25
+ probe.style.display = 'none'
26
+ probe.style.color = value
27
+ ;(el || document.body).appendChild(probe)
28
+ const out = getComputedStyle(probe).color
29
+ probe.remove()
30
+ return out
31
+ }
32
+
33
+ /**
34
+ * Perceptual is-this-color-light test (BT.709 luma) — drives contrast
35
+ * label flips on swatches. Accepts hex or rgb()/rgba() strings; any other
36
+ * expression goes through resolveCssColor first.
37
+ */
38
+ export function isLight(color) {
39
+ let r, g, b
40
+ const hex = color.match(/^#([0-9a-f]{3}|[0-9a-f]{6})$/i)
41
+ if (hex) {
42
+ let h = hex[1]
43
+ if (h.length === 3) h = h.split('').map((c) => c + c).join('')
44
+ r = parseInt(h.slice(0, 2), 16)
45
+ g = parseInt(h.slice(2, 4), 16)
46
+ b = parseInt(h.slice(4, 6), 16)
47
+ } else {
48
+ const rgb = (color.startsWith('rgb') ? color : resolveCssColor(color)).match(/[\d.]+/g)
49
+ if (!rgb) return false
50
+ ;[r, g, b] = rgb.map(Number)
51
+ }
52
+ return (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255 > 0.55
53
+ }
@@ -0,0 +1,91 @@
1
+ import { useCallback, useEffect, useRef, useState } from 'react'
2
+ import usePrefersReducedMotion from './usePrefersReducedMotion.js'
3
+
4
+ /**
5
+ * useAxisAnimation — ping-pong rAF over a variable-font axis range.
6
+ *
7
+ * Owns a single numeric value that auto-oscillates between `min` and `max`
8
+ * (bouncing at each end) while `running` is true, then hands the value + a
9
+ * setter back to the caller so a slider can scrub it (scrub = call `setValue`,
10
+ * flip `running` off). This is the reusable core lifted out of the monorepo's
11
+ * VariableFontSection, whose auto-oscillator was inline and unguarded.
12
+ *
13
+ * Timing uses the `performance.now()` interval-accumulator pattern (the same
14
+ * shape as fontviewer's GlyphAnimator) rather than a `Date.now()` / `delta>16`
15
+ * throttle — the accumulator carries the sub-interval remainder so the step is
16
+ * frame-rate independent and doesn't jitter.
17
+ *
18
+ * Reduced-motion gate: when the user asks for reduced motion the loop never
19
+ * mounts and the hook returns its static default (`initial`, else the range
20
+ * midpoint) — the widget still renders and stays scrubbable, it just doesn't
21
+ * auto-play. `reduced` is returned so the caller can reflect it in its
22
+ * play/pause affordance.
23
+ *
24
+ * The value is axis-agnostic: the render boundary decides whether it feeds
25
+ * `fontWeight` (wght) or `font-variation-settings` (wdth/slnt/…).
26
+ *
27
+ * @param {Object} opts
28
+ * @param {number} opts.min - Lower bound of the oscillation + slider min (default 300).
29
+ * @param {number} opts.max - Upper bound of the oscillation + slider max (default 900).
30
+ * @param {number} opts.step - Units advanced per frame (default 2).
31
+ * @param {number} opts.fps - Target frame cadence for the accumulator (default 60).
32
+ * @param {boolean} opts.running - Whether the loop is active (default true). Gated by reduced-motion.
33
+ * @param {number} opts.initial - Starting / reduced-motion static value (default range midpoint).
34
+ * @returns {{ value: number, setValue: (v:number)=>void, reduced: boolean }}
35
+ */
36
+ export default function useAxisAnimation({
37
+ min = 300,
38
+ max = 900,
39
+ step = 2,
40
+ fps = 60,
41
+ running = true,
42
+ initial,
43
+ } = {}) {
44
+ const reduced = usePrefersReducedMotion()
45
+ const staticDefault = initial ?? Math.round((min + max) / 2)
46
+
47
+ const [value, setValueState] = useState(staticDefault)
48
+ const valueRef = useRef(staticDefault)
49
+ const dirRef = useRef(1)
50
+ const rafRef = useRef(null)
51
+
52
+ // External override (slider scrub). Keeps the ref in sync so a subsequent
53
+ // resume ping-pongs from where the user left it, not from a stale value.
54
+ const setValue = useCallback((v) => {
55
+ valueRef.current = v
56
+ setValueState(v)
57
+ }, [])
58
+
59
+ useEffect(() => {
60
+ if (reduced || !running) return
61
+
62
+ const interval = 1000 / fps
63
+ let last = performance.now()
64
+
65
+ const animate = (now) => {
66
+ const elapsed = now - last
67
+ if (elapsed >= interval) {
68
+ let next = valueRef.current + dirRef.current * step
69
+ if (next >= max) {
70
+ next = max
71
+ dirRef.current = -1
72
+ } else if (next <= min) {
73
+ next = min
74
+ dirRef.current = 1
75
+ }
76
+ valueRef.current = next
77
+ setValueState(next)
78
+ // Carry the sub-interval remainder so the cadence stays even.
79
+ last = now - (elapsed % interval)
80
+ }
81
+ rafRef.current = requestAnimationFrame(animate)
82
+ }
83
+
84
+ rafRef.current = requestAnimationFrame(animate)
85
+ return () => {
86
+ if (rafRef.current) cancelAnimationFrame(rafRef.current)
87
+ }
88
+ }, [reduced, running, min, max, step, fps])
89
+
90
+ return { value, setValue, reduced }
91
+ }
@@ -0,0 +1,21 @@
1
+ import { useEffect, useState } from 'react'
2
+
3
+ /**
4
+ * True when the user asks for reduced motion. The DS-wide motion gate:
5
+ * every animated/effect component checks this and renders its static
6
+ * form when true (the monorepo sources never did — added on recreate).
7
+ */
8
+ export default function usePrefersReducedMotion() {
9
+ const [reduced, setReduced] = useState(
10
+ () => typeof window !== 'undefined' && window.matchMedia('(prefers-reduced-motion: reduce)').matches,
11
+ )
12
+
13
+ useEffect(() => {
14
+ const mq = window.matchMedia('(prefers-reduced-motion: reduce)')
15
+ const onChange = () => setReduced(mq.matches)
16
+ mq.addEventListener('change', onChange)
17
+ return () => mq.removeEventListener('change', onChange)
18
+ }, [])
19
+
20
+ return reduced
21
+ }
@@ -0,0 +1,49 @@
1
+ import { useRef } from 'react'
2
+ import { useMotionValue, useSpring, useTransform } from 'framer-motion'
3
+
4
+ /**
5
+ * Pointer-driven 3D tilt (framer-motion springs) — the ONE tilt hook.
6
+ * Ported from the monorepo's useBentoTiltMotion; TiltCard, BentoCard and
7
+ * friends all compose this instead of forking their own.
8
+ *
9
+ * Returns `{ ref, style, onMouseMove, onMouseLeave, motionValues }` —
10
+ * spread `ref`/handlers on a `motion.div` and pass `style` to it.
11
+ * `motionValues` exposes the raw springs for derived variants
12
+ * (e.g. TiltCard's `grounded` zone-snapping).
13
+ *
14
+ * Defaults are the design: tilt ±4°, spring 350/35, perspective 700,
15
+ * rest position center (0.5/0.5).
16
+ */
17
+ export default function useTilt({
18
+ magnitude = 4,
19
+ perspective = 700,
20
+ stiffness = 350,
21
+ damping = 35,
22
+ } = {}) {
23
+ const ref = useRef(null)
24
+ const mouseX = useMotionValue(0.5)
25
+ const mouseY = useMotionValue(0.5)
26
+
27
+ const rotateX = useSpring(useTransform(mouseY, [0, 1], [magnitude, -magnitude]), { stiffness, damping })
28
+ const rotateY = useSpring(useTransform(mouseX, [0, 1], [-magnitude, magnitude]), { stiffness, damping })
29
+
30
+ const onMouseMove = (e) => {
31
+ const rect = ref.current?.getBoundingClientRect()
32
+ if (!rect) return
33
+ mouseX.set((e.clientX - rect.left) / rect.width)
34
+ mouseY.set((e.clientY - rect.top) / rect.height)
35
+ }
36
+
37
+ const onMouseLeave = () => {
38
+ mouseX.set(0.5)
39
+ mouseY.set(0.5)
40
+ }
41
+
42
+ return {
43
+ ref,
44
+ style: { rotateX, rotateY, transformStyle: 'preserve-3d', transformPerspective: perspective },
45
+ onMouseMove,
46
+ onMouseLeave,
47
+ motionValues: { mouseX, mouseY, rotateX, rotateY },
48
+ }
49
+ }
package/src/index.js CHANGED
@@ -1,59 +1,139 @@
1
1
  /**
2
2
  * @kol/component - Canonical KOL design-system primitives
3
3
  *
4
- * Shared atoms/molecules consumed by both apps/web (via @kol/ui re-export)
5
- * and apps/brand. Components emit canonical kol-* classes; CSS lives in
6
- * @kol/theme (kol-components-*.css).
4
+ * Shared atoms/molecules/organisms consumed by both apps/web (via @kol/ui
5
+ * re-export) and apps/brand. Components emit canonical kol-* classes; CSS
6
+ * lives in @kol/theme (kol-components-*.css).
7
+ *
8
+ * Placement follows the taxonomy rules in
9
+ * docs/documentation/02-components/02-placement.md:
10
+ * atom — nests no KOL component (kol-loader Icon/Graphic are
11
+ * infrastructure, they don't count)
12
+ * molecule — nests at least one KOL component
13
+ * organism — a self-contained composed UI region
14
+ * `scripts/validate-taxonomy.mjs` enforces the closed folder set and the
15
+ * downward-only import rule (atoms never import molecules/organisms).
7
16
  */
8
17
 
9
- export { default as Divider } from './atoms/Divider.jsx'
10
- export { default as DropdownTagFilter } from './molecules/DropdownTagFilter.jsx'
11
- export { default as QuantityInput } from './molecules/QuantityInput.jsx'
12
- export { default as QuantityStepper } from './molecules/QuantityStepper.jsx'
13
- export { default as Pill } from './molecules/Pill.jsx'
14
- export { default as ToggleCheckbox } from './atoms/ToggleCheckbox.jsx'
15
- export { default as ToggleSwitch } from './atoms/ToggleSwitch.jsx'
16
- export { default as ToggleBracket } from './molecules/ToggleBracket.jsx'
17
- export { Icon } from '@kolkrabbi/kol-loader'
18
- export { default as Tag } from './molecules/Tag.jsx'
19
- export { default as Badge } from './molecules/Badge.jsx'
20
- export { default as SectionLabel } from './molecules/SectionLabel.jsx'
21
- export { default as Section } from './molecules/Section.jsx'
22
- export { default as Button } from './atoms/Button.jsx'
23
- export { default as Input } from './atoms/Input.jsx'
24
- export { default as Slider } from './atoms/Slider.jsx'
25
- export { default as Dropdown } from './molecules/Dropdown.jsx'
26
- export { usePopover, PopoverPanel, Tooltip } from './molecules/Popover.jsx'
27
- export { MenuItem, MenuDropdownItem, MenuDropdownDivider, MenuDropdownNest } from './molecules/MenuItem.jsx'
28
-
29
- /* ── Folded from apps/brand (Phase 4 — brand is canonical, brand wins) ────── */
30
18
  // atoms
19
+ export { default as AnimatedTitle } from './atoms/AnimatedTitle.jsx'
20
+ export { default as AssetGrid } from './atoms/AssetGrid.jsx'
21
+ export { default as AssetPlaceholder } from './atoms/AssetPlaceholder.jsx'
31
22
  export { default as Avatar } from './atoms/Avatar.jsx'
32
- export { default as ColorSwatch } from './atoms/ColorSwatch.jsx'
23
+ export { default as Badge } from './atoms/Badge.jsx'
24
+ export { default as Button } from './atoms/Button.jsx'
25
+ export { default as CopyButton } from './atoms/CopyButton.jsx'
26
+ export { default as CurveOverlay } from './atoms/CurveOverlay.jsx'
27
+ export { default as Divider } from './atoms/Divider.jsx'
28
+ export { default as DocsToc } from './atoms/DocsToc.jsx'
29
+ export { default as DropdownTagFilter } from './atoms/DropdownTagFilter.jsx'
30
+ export { default as EmptyState } from './atoms/EmptyState.jsx'
31
+ export { default as ExitPreview } from './atoms/ExitPreview.jsx'
32
+ export { default as Figure } from './atoms/Figure.jsx'
33
+ export { default as FullscreenOverlay } from './atoms/FullscreenOverlay.jsx'
34
+ export { default as HlsVideo } from './atoms/HlsVideo.jsx'
35
+ export { default as Input } from './atoms/Input.jsx'
33
36
  export { default as Label } from './atoms/Label.jsx'
37
+ export { default as LabeledControl } from './atoms/LabeledControl.jsx'
38
+ export { default as OverlayGlassPanel } from './atoms/OverlayGlassPanel.jsx'
39
+ export { default as Pill } from './atoms/Pill.jsx'
40
+ export { usePopover, PopoverPanel, Tooltip } from './atoms/Popover.jsx'
41
+ export { default as PriceDisplay } from './atoms/PriceDisplay.jsx'
42
+ export { default as ProsePreview } from './atoms/ProsePreview.jsx'
43
+ export { default as QuantityInput } from './atoms/QuantityInput.jsx'
44
+ export { default as QuantityStepper } from './atoms/QuantityStepper.jsx'
45
+ export { default as RotaryDial } from './atoms/RotaryDial.jsx'
46
+ export { default as SearchInput } from './atoms/SearchInput.jsx'
47
+ export { default as Section } from './atoms/Section.jsx'
48
+ export { default as SectionLabel } from './atoms/SectionLabel.jsx'
49
+ export { default as SegmentedToggle } from './atoms/SegmentedToggle.jsx'
34
50
  export { default as Stepper } from './atoms/Stepper.jsx'
51
+ export { default as Tag } from './atoms/Tag.jsx'
35
52
  export { default as Textarea } from './atoms/Textarea.jsx'
53
+ export { default as ToggleBracket } from './atoms/ToggleBracket.jsx'
54
+ export { default as ToggleCheckbox } from './atoms/ToggleCheckbox.jsx'
55
+ export { default as ToggleSwitch } from './atoms/ToggleSwitch.jsx'
56
+ export { default as TextPressure } from './atoms/TextPressure.jsx'
57
+ export { default as TiltCard } from './atoms/TiltCard.jsx'
36
58
  export { default as TransparentX } from './atoms/TransparentX.jsx'
59
+ export { default as TypeSample } from './atoms/TypeSample.jsx'
60
+ export { default as TypeSpecCard } from './atoms/TypeSpecCard.jsx'
61
+ export { default as ViewToggle } from './atoms/ViewToggle.jsx'
62
+
37
63
  // molecules
38
- export { default as ContentFilters } from './molecules/ContentFilters.jsx'
39
- export { default as LabeledControl } from './molecules/LabeledControl.jsx'
64
+ export { Accordion, AccordionPanel } from './molecules/Accordion.jsx'
65
+ /* monorepo sets (P6–P10) molecule members */
66
+ export { default as AlignmentGrid } from './molecules/AlignmentGrid.jsx'
67
+ export { default as ArticleCard } from './molecules/ArticleCard.jsx'
68
+ export { default as ImageBlock } from './molecules/ImageBlock.jsx'
69
+ export { default as SelectionOverlay } from './molecules/SelectionOverlay.jsx'
70
+ export { default as VideoBlock, getEmbedUrl } from './molecules/VideoBlock.jsx'
71
+ export { default as WorkListItem } from './molecules/WorkListItem.jsx'
72
+ export { default as CardFeatureItem } from './molecules/CardFeatureItem.jsx'
73
+ export { default as CodeBlock } from './molecules/CodeBlock.jsx'
74
+ export { default as ColorInputRow } from './molecules/ColorInputRow.jsx'
75
+ export { default as ColorRamp } from './molecules/ColorRamp.jsx'
76
+ export { default as ColorSwatch } from './molecules/ColorSwatch.jsx'
77
+ export { default as Dropdown } from './molecules/Dropdown.jsx'
78
+ export { default as FramedMediaBand } from './molecules/FramedMediaBand.jsx'
79
+ export { default as Image } from './molecules/Image.jsx'
80
+ export { default as MediaCard } from './molecules/MediaCard.jsx'
81
+ export { default as MediaRow } from './molecules/MediaRow.jsx'
82
+ export { MenuItem, MenuDropdownItem, MenuDropdownDivider, MenuDropdownNest } from './molecules/MenuItem.jsx'
40
83
  export { MenuPopover } from './molecules/MenuPopover.jsx'
41
84
  export { ModalProvider, useModal } from './molecules/Modal.jsx'
42
85
  export { default as PropertyInput } from './molecules/PropertyInput.jsx'
43
- export { default as SegmentedToggle } from './molecules/SegmentedToggle.jsx'
44
- export { default as ViewToggle } from './molecules/ViewToggle.jsx'
45
- // primitives
46
- export { Accordion, AccordionPanel } from './primitives/Accordion.jsx'
47
- export { default as AssetPlaceholder } from './primitives/AssetPlaceholder.jsx'
48
- export { default as Carousel } from './primitives/Carousel.jsx'
49
- export { default as CodeBlock } from './primitives/CodeBlock.jsx'
50
- export { default as ExitPreview } from './primitives/ExitPreview.jsx'
51
- export { default as FullscreenOverlay } from './primitives/FullscreenOverlay.jsx'
52
- export { default as Image } from './primitives/Image.jsx'
53
- // graphics (SVG illustration loader — globs its own ./graphics/svg/**)
54
- export { default as Graphic, GRAPHICS, GRAPHIC_RAW } from './graphics/Graphic.jsx'
86
+ export { default as ShapeDropdown } from './molecules/ShapeDropdown.jsx'
87
+ export { default as ShellDrawer } from './molecules/ShellDrawer.jsx'
88
+ export { default as ShellSearchOverlay } from './molecules/ShellSearchOverlay.jsx'
89
+ export { default as Slider } from './molecules/Slider.jsx'
90
+ export { default as SpecList } from './molecules/SpecList.jsx'
91
+ export { default as SpectrumControls, HueStrip, SBSquare, WheelTriangle } from './molecules/SpectrumControls.jsx'
92
+ export { default as SwatchControls, SwatchStack, EyedropPick } from './molecules/SwatchControls.jsx'
93
+ export { default as TabsRow } from './molecules/TabsRow.jsx'
94
+
55
95
  // organisms
96
+ /* monorepo sets (P6–P10) — organism members. Foundry members are isolated
97
+ under the './foundry' subpath (opentype-heavy) — not re-exported here. */
98
+ export { default as ArticleHeader } from './organisms/ArticleHeader.jsx'
99
+ export { default as Canvas, CanvasFrame, PanViewport, CANVAS_VIRTUAL_W, DEFAULT_ASPECTS, CANVAS_DEFAULTS } from './organisms/Canvas.jsx'
100
+ export { default as DiagonalMarqueeRiver } from './organisms/DiagonalMarqueeRiver.jsx'
101
+ export { default as EditorShell } from './organisms/EditorShell.jsx'
102
+ export { default as GalleryCarousel } from './organisms/GalleryCarousel.jsx'
103
+ export { default as ParallaxShelf } from './organisms/ParallaxShelf.jsx'
104
+ export { default as PortableTextRenderer, slugify } from './organisms/PortableTextRenderer.jsx'
105
+ export { default as ProductDetailLayout } from './organisms/ProductDetailLayout.jsx'
106
+ export { default as ScrollDriftGallery } from './organisms/ScrollDriftGallery.jsx'
107
+ export { default as StackHero } from './organisms/StackHero.jsx'
108
+ export { default as WorkCard } from './organisms/WorkCard.jsx'
109
+ export { default as WorkViewToggle } from './organisms/WorkViewToggle.jsx'
110
+ export { default as AsciiCursor } from './organisms/AsciiCursor.jsx'
111
+ export { default as BentoCard } from './organisms/BentoCard.jsx'
112
+ export { default as Carousel } from './organisms/Carousel.jsx'
113
+ export { default as ColorLoader } from './organisms/ColorLoader.jsx'
114
+ export { default as ContentFilters } from './organisms/ContentFilters.jsx'
115
+ export { default as CtaGlobal } from './organisms/CtaGlobal.jsx'
116
+ export { default as ErrorBoundary } from './organisms/ErrorBoundary.jsx'
117
+ export { default as FeatureSplit } from './organisms/FeatureSplit.jsx'
118
+ export { default as FeaturedCarousel } from './organisms/FeaturedCarousel.jsx'
119
+ export { default as FeaturesCardSection } from './organisms/FeaturesCardSection.jsx'
120
+ export { default as FullBleedHero } from './organisms/FullBleedHero.jsx'
121
+ export { default as LoaderOverlay } from './organisms/LoaderOverlay.jsx'
122
+ export { default as MediaViewer } from './organisms/MediaViewer.jsx'
123
+ export { default as NewsletterBand } from './organisms/NewsletterBand.jsx'
124
+ export { default as SpectrumGrid } from './organisms/SpectrumGrid.jsx'
56
125
  export { default as Table } from './organisms/Table.jsx'
126
+
127
+ // loaders (re-export — infrastructure, documented on /docs/loaders)
128
+ export { Icon } from '@kolkrabbi/kol-loader'
129
+
130
+ // graphics (SVG illustration loader — globs its own ./graphics/svg/**)
131
+ export { default as Graphic, GRAPHICS } from './graphics/Graphic.jsx'
132
+
57
133
  // hooks
134
+ export { default as usePrefersReducedMotion } from './hooks/usePrefersReducedMotion.js'
58
135
  export { default as useReveal } from './hooks/useReveal.js'
59
136
  export { default as useScrollSpy } from './hooks/useScrollSpy.js'
137
+ export { default as useTilt } from './hooks/useTilt.js'
138
+ export { default as useAxisAnimation } from './hooks/useAxisAnimation.js'
139
+ export { resolveCssVar, resolveCssColor, isLight } from './hooks/cssVar.js'
@@ -1,6 +1,9 @@
1
1
  /**
2
2
  * Accordion — collapsible panel group.
3
3
  *
4
+ * taxonomy-ok: molecule — Accordion nests AccordionPanel in this same file,
5
+ * which the import-based validator can't see.
6
+ *
4
7
  * Composition-based: <Accordion> wraps <AccordionPanel> children. Each panel
5
8
  * owns its open/closed state independently (additive — multiple can be open).
6
9
  * For single-open behavior, manage state from the parent and pass controlled
@@ -37,7 +40,7 @@ export function AccordionPanel({
37
40
  >
38
41
  <span className="kol-accordion-title kol-helper-12 uppercase tracking-widest">{title}</span>
39
42
  {meta && <span className="kol-accordion-meta kol-helper-12 uppercase tracking-widest text-fg-48 ml-auto mr-3">{meta}</span>}
40
- <span className="kol-accordion-chevron font-mono text-[18px] text-fg-48 min-w-3 transition-colors duration-[120ms] ml-auto" aria-hidden="true">{open ? '−' : '+'}</span>
43
+ <span className="kol-accordion-chevron kol-helper-16 text-fg-48 min-w-3 transition-colors duration-[120ms] ml-auto" aria-hidden="true">{open ? '−' : '+'}</span>
41
44
  </button>
42
45
  {open && <div className="kol-accordion-body pt-2 pb-6">{children}</div>}
43
46
  </div>
@@ -0,0 +1,53 @@
1
+ import Button from '../atoms/Button.jsx'
2
+
3
+ /**
4
+ * AlignmentGrid — a six-cell alignment row: horizontal start/center/end then
5
+ * vertical start/center/end, each a quiet icon Button that emits an
6
+ * `(axis, mode)` alignment intent. Driven off a static config array (the
7
+ * default 6, overridable via `items`). Presentation only — the consumer wires
8
+ * `onAlign` to whatever "align these" means in its context (align a box to the
9
+ * canvas bounds, align a multi-selection's common bbox, …).
10
+ *
11
+ * Ported from the brand editor's AlignmentPanel with the store coupling
12
+ * dropped (per lobby spec): `useComposeState().alignSelected` → an `onAlign`
13
+ * prop; the hand-rolled `kol-btn-quiet` buttons → DS `Button` (quiet +
14
+ * iconOnly) so the DS owns the button atom; `EditorIcon` → the DS Icon
15
+ * (through Button).
16
+ *
17
+ * [icon-gap] The source's `align-h-{start,center,end}` / `align-v-{start,
18
+ * center,end}` glyph names are NOT in the loader. Remapped to the closest
19
+ * existing loader glyphs (stroke/layout): `align-horizontal-{left,center,
20
+ * right}` and `align-vertical-{top,center,bottom}`. Same six align marks,
21
+ * different names — no visual gap.
22
+ *
23
+ * @param {(axis:'h'|'v', mode:'start'|'center'|'end') => void} onAlign fired on cell click
24
+ * @param {Array} items [{ axis, mode, icon, title }] cells (default the standard 6)
25
+ */
26
+ const ALIGN_BUTTONS = [
27
+ { axis: 'h', mode: 'start', icon: 'align-horizontal-left', title: 'Align left' },
28
+ { axis: 'h', mode: 'center', icon: 'align-horizontal-center', title: 'Align horizontal center' },
29
+ { axis: 'h', mode: 'end', icon: 'align-horizontal-right', title: 'Align right' },
30
+ { axis: 'v', mode: 'start', icon: 'align-vertical-top', title: 'Align top' },
31
+ { axis: 'v', mode: 'center', icon: 'align-vertical-center', title: 'Align vertical center' },
32
+ { axis: 'v', mode: 'end', icon: 'align-vertical-bottom', title: 'Align bottom' },
33
+ ]
34
+
35
+ export default function AlignmentGrid({ onAlign, items = ALIGN_BUTTONS }) {
36
+ return (
37
+ <div className="grid grid-cols-6 gap-1">
38
+ {items.map((b) => (
39
+ <Button
40
+ key={`${b.axis}-${b.mode}`}
41
+ quiet
42
+ iconOnly={b.icon}
43
+ iconSize={16}
44
+ onClick={() => onAlign?.(b.axis, b.mode)}
45
+ title={b.title}
46
+ aria-label={b.title}
47
+ className="w-full"
48
+ style={{ height: 28, padding: 6 }}
49
+ />
50
+ ))}
51
+ </div>
52
+ )
53
+ }