@kolkrabbi/kol-component 0.235.0 → 0.237.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 (52) hide show
  1. package/package.json +4 -4
  2. package/src/atoms/Button.jsx +10 -2
  3. package/src/atoms/Kbd.jsx +6 -3
  4. package/src/index.js +17 -11
  5. package/src/molecules/ColorInputRow.jsx +1 -1
  6. package/src/molecules/ColorRamp.jsx +1 -1
  7. package/src/molecules/CopyButton.jsx +1 -1
  8. package/src/molecules/Dropdown.jsx +5 -2
  9. package/src/molecules/KindPreview.jsx +6 -6
  10. package/src/{atoms/RotaryDial.jsx → molecules/Knob.jsx} +20 -2
  11. package/src/molecules/KnobPanel.jsx +116 -0
  12. package/src/molecules/LabeledControl.jsx +22 -0
  13. package/src/molecules/MediaPlayer.jsx +107 -0
  14. package/src/molecules/Modal.jsx +1 -1
  15. package/src/molecules/MultiSelect.jsx +70 -0
  16. package/src/molecules/OptionRow.jsx +1 -1
  17. package/src/molecules/PlayTile.jsx +70 -0
  18. package/src/molecules/PlaybackBar.jsx +7 -16
  19. package/src/molecules/RowMenuButton.jsx +1 -1
  20. package/src/molecules/ShapeDropdown.jsx +3 -3
  21. package/src/molecules/Slider.jsx +93 -56
  22. package/src/molecules/SliderPanel.jsx +88 -0
  23. package/src/molecules/SplitToolButton.jsx +1 -1
  24. package/src/molecules/SwatchControls.jsx +1 -1
  25. package/src/molecules/TabChips.jsx +39 -0
  26. package/src/molecules/TiltBento.jsx +5 -5
  27. package/src/organisms/ColumnBrowser.jsx +1 -1
  28. package/src/organisms/ContentFilters.jsx +1 -1
  29. package/src/organisms/CurveEditor.jsx +2 -2
  30. package/src/organisms/FeaturedCarousel.jsx +4 -4
  31. package/src/organisms/KeyframeEditor.jsx +3 -3
  32. package/src/organisms/LayerStack.jsx +2 -2
  33. package/src/organisms/MediaLibrary.jsx +1 -1
  34. package/src/organisms/MediaLibraryPages.jsx +20 -20
  35. package/src/organisms/RecordManager.jsx +1 -1
  36. package/src/organisms/SectionHero.jsx +3 -3
  37. package/src/organisms/SectionNewsletter.jsx +2 -1
  38. package/src/organisms/ShellSearchOverlay.jsx +8 -8
  39. package/src/organisms/SpectrumGrid.jsx +1 -1
  40. package/src/organisms/ToolPalette.jsx +1 -1
  41. package/src/{atoms/HlsVideo.jsx → utilities/BackgroundVideo.jsx} +6 -2
  42. package/src/utilities/ErrorBoundary.jsx +1 -1
  43. package/src/utilities/QuadrantSync.jsx +4 -1
  44. package/src/utilities/armLongPress.js +18 -0
  45. package/src/utilities/mediaSearch.js +2 -2
  46. package/src/utilities/tone.js +6 -0
  47. package/src/atoms/AudioPlayer.jsx +0 -51
  48. package/src/molecules/AudioPreview.jsx +0 -140
  49. package/src/molecules/AudioSheet.jsx +0 -48
  50. package/src/molecules/VideoSheet.jsx +0 -25
  51. /package/src/{atoms → molecules}/ActionButton.jsx +0 -0
  52. /package/src/{atoms → molecules}/ColorSwatch.jsx +0 -0
@@ -1,140 +0,0 @@
1
- /* eslint-disable react-hooks/refs -- floating-ui's `refs.setReference` is a callback ref, not a ref read; the DS Tooltip wires it the same way */
2
- import { useEffect, useRef, useState } from 'react';
3
- import IconFrame from '../atoms/IconFrame.jsx';
4
- import Slider from './Slider.jsx';
5
- import { usePopover, PopoverPanel } from '../utilities/Popover.jsx';
6
- import { readCover } from '../utilities/id3.js';
7
-
8
- /* taxonomy-ok: molecule — nests IconFrame (atom) + Slider / PopoverPanel (relative). */
9
-
10
- /**
11
- * AudioPreview · AudioTile · VideoTile — kol-r2b2's players, promoted verbatim
12
- * (ColumnBrowserMediaFacts 2026-08-27; the tiles re-ruled the same day,
13
- * PlayDiscAndVideoBar): players composed from DS parts (IconFrame · Slider ·
14
- * Popover) — the DS AudioPlayer is a native <audio controls>, whose one-row
15
- * layout can't be reshaped. Finder model (user ruling 2026-08-27): the COLUMN
16
- * gets a square tile over full-bleed artwork with ONE control — the Finder
17
- * play/pause disc (`.kol-play-disc`: round, hidden at rest, shown on hover of
18
- * `.kol-media-tile`) — `AudioTile` (artwork = the file's embedded ID3 cover,
19
- * `readCover`) / `VideoTile` (artwork = the poster; the media element is hidden
20
- * — the tile never shows a decoded video frame). Timeline + volume belong to
21
- * the OVERLAY / Quick Look — `AudioPreview` (play/pause · Slider seek with an
22
- * m:ss readout · volume behind `slider-01`, a vertical `slider-black` range in
23
- * a PopoverPanel, placement top; no title line — the name sits in the facts)
24
- * and `VideoSheet` (the QuickTime bar).
25
- *
26
- * @param {string} src the media URL
27
- * @param {string} poster VideoTile — the poster URL
28
- * @param {Function} onDuration AudioPreview — (seconds) => void once metadata lands
29
- * @param {string} className AudioPreview — wrapper classes (the width is the consumer's)
30
- * @param {ReactNode} fallback AudioTile — shown when the file carries no cover art (2026-09-23:
31
- * it was an empty square, so a WAV read as a missing file)
32
- */
33
- export const formatLength = (s) => `${Math.floor(s / 60)}:${String(Math.floor(s % 60)).padStart(2, '0')}`;
34
- const fmt = formatLength;
35
-
36
- function useCover(src) {
37
- const [cover, setCover] = useState(null);
38
- useEffect(() => {
39
- let live = true;
40
- readCover(src).then((url) => { if (live) setCover(url); });
41
- return () => { live = false; };
42
- }, [src]);
43
- return cover;
44
- }
45
-
46
- /* One disc for both tiles — the ruled Finder play/pause. The theme's
47
- * `.kol-play-disc` paints it after the IconFrame variants, so nothing is restated here. */
48
- function PlayDisc({ playing, onClick }) {
49
- return (
50
- <IconFrame name={playing ? 'pause' : 'play'} variant="secondary" size="lg" radius="full" onClick={onClick} aria-label={playing ? 'Pause' : 'Play'} className="relative kol-play-disc" />
51
- );
52
- }
53
-
54
- export function AudioTile({ src, fallback = null }) {
55
- const ref = useRef(null);
56
- const [playing, setPlaying] = useState(false);
57
- const cover = useCover(src);
58
- return (
59
- <div className="kol-media-tile relative w-full aspect-square rounded overflow-hidden flex items-center justify-center">
60
- <audio ref={ref} src={src} preload="metadata" onPlay={() => setPlaying(true)} onPause={() => setPlaying(false)} onEnded={() => setPlaying(false)} />
61
- {cover ? <img src={cover} alt="" className="absolute inset-0 w-full h-full object-cover" /> : fallback && <div className="absolute inset-0 flex items-center justify-center">{fallback}</div>}
62
- <PlayDisc playing={playing} onClick={() => (playing ? ref.current.pause() : ref.current.play())} />
63
- </div>
64
- );
65
- }
66
-
67
- export function VideoTile({ src, poster }) {
68
- const ref = useRef(null);
69
- const [playing, setPlaying] = useState(false);
70
- return (
71
- <div className="kol-media-tile relative w-full aspect-square rounded overflow-hidden flex items-center justify-center">
72
- {/* Same as audio: the media element is hidden, the artwork (the poster) is the tile. */}
73
- <video ref={ref} src={src} playsInline preload="metadata" className={poster ? 'hidden' : 'absolute inset-0 w-full h-full object-cover'} onPlay={() => setPlaying(true)} onPause={() => setPlaying(false)} onEnded={() => setPlaying(false)} />
74
- {poster && <img src={poster} alt="" className="absolute inset-0 w-full h-full object-cover" />}
75
- <PlayDisc playing={playing} onClick={() => (playing ? ref.current.pause() : ref.current.play())} />
76
- </div>
77
- );
78
- }
79
-
80
- function useAudio(onDuration) {
81
- const ref = useRef(null);
82
- const [playing, setPlaying] = useState(false);
83
- const [time, setTime] = useState(0);
84
- const [duration, setDuration] = useState(0);
85
- const toggle = () => (playing ? ref.current.pause() : ref.current.play());
86
- const element = (src) => (
87
- <audio
88
- ref={ref}
89
- src={src}
90
- preload="metadata"
91
- onPlay={() => setPlaying(true)}
92
- onPause={() => setPlaying(false)}
93
- onEnded={() => setPlaying(false)}
94
- onTimeUpdate={(e) => setTime(e.target.currentTime)}
95
- onLoadedMetadata={(e) => { setDuration(e.target.duration); onDuration?.(e.target.duration); }}
96
- />
97
- );
98
- return { ref, playing, time, duration, toggle, element, setTime };
99
- }
100
-
101
- export default function AudioPreview({ src, className = '', onDuration }) {
102
- const { ref, playing, time, duration, toggle, element, setTime } = useAudio(onDuration);
103
- const [volume, setVolume] = useState(100);
104
- const [open, setOpen] = useState(false);
105
- const pop = usePopover({ open, onOpenChange: setOpen, placement: 'top' });
106
-
107
- return (
108
- <div className={`flex items-center gap-2 ${className}`}>
109
- {element(src)}
110
- <IconFrame name={playing ? 'pause' : 'play'} variant="ghost" size="sm" onClick={toggle} />
111
- <Slider
112
- className="flex-1"
113
- min={0}
114
- max={duration || 0}
115
- step={0.1}
116
- value={time}
117
- onChange={(v) => { ref.current.currentTime = v; setTime(v); }}
118
- formatValue={fmt}
119
- displayWidth={5}
120
- />
121
- <span ref={pop.refs.setReference} {...pop.getReferenceProps()} className="inline-flex">
122
- <IconFrame name="slider-01" variant={open ? 'secondary' : 'ghost'} size="sm" onClick={() => {}} />
123
- </span>
124
- <PopoverPanel popover={pop} className="p-2">
125
- {/* DS track class on a native range, turned upright: the one thing the DS Slider can't do. */}
126
- <div className="w-6 h-24 flex items-center justify-center">
127
- <input
128
- type="range"
129
- min={0}
130
- max={100}
131
- value={volume}
132
- onChange={(e) => { const v = Number(e.target.value); ref.current.volume = v / 100; setVolume(v); }}
133
- className="slider-black cursor-pointer w-24 -rotate-90"
134
- aria-label="Volume"
135
- />
136
- </div>
137
- </PopoverPanel>
138
- </div>
139
- );
140
- }
@@ -1,48 +0,0 @@
1
- import { useEffect, useState } from 'react'
2
- import usePlayback from '../hooks/usePlayback.js'
3
- import PlaybackBar, { clock } from './PlaybackBar.jsx'
4
- import QuickLookFrame from './QuickLookFrame.jsx'
5
- import FileIcon from '../atoms/FileIcon.jsx'
6
- import { readCover } from '../utilities/id3.js'
7
-
8
- /* taxonomy-ok: molecule — nests PlaybackBar + QuickLookFrame (relative) + FileIcon (atom). */
9
-
10
- function useCover(src) {
11
- const [cover, setCover] = useState(null)
12
- useEffect(() => {
13
- let live = true
14
- readCover(src).then((url) => { if (live) setCover(url) }).catch(() => {})
15
- return () => { live = false }
16
- }, [src])
17
- return cover
18
- }
19
-
20
- /**
21
- * AudioSheet — Audio in the preview window. audio in the Quick Look window, Finder's layout (user 2026-09-23, the reference
22
- * shot): the artwork square left, `Time: mm:ss` beside it, the QuickTime bar docked in the
23
- * window's footer. Artwork = the file's embedded ID3 `APIC` cover (`readCover`); no cover →
24
- * `FileIcon`, the same page the tile shows. The `cover` / `sheet` variants are gone — there is
25
- * one window now, and it is the sheet.
26
- *
27
- * @param {string} src the audio URL
28
- * @param {string} ext the extension, for the no-cover icon
29
- * @param {Function} onDuration (seconds) => void once metadata lands
30
- * @param {Object} frame the window's header — `QuickLookFrame`'s title · meta · actions · onClose
31
- */
32
- export default function AudioSheet({ src, ext, onDuration, frame }) {
33
- const { ref, handlers, bar } = usePlayback((el) => onDuration?.(el.duration))
34
- const cover = useCover(src)
35
- return (
36
- <QuickLookFrame {...frame} footer={<PlaybackBar {...bar} docked />}>
37
- <audio ref={ref} src={src} preload="metadata" {...handlers} />
38
- <div className="kol-quicklook-audio">
39
- <div className="kol-quicklook-art">
40
- {cover
41
- ? <img src={cover} alt="" className="w-full h-full object-cover" />
42
- : <FileIcon ext={ext} glyph="music-note" className="w-[52%]" />}
43
- </div>
44
- <span className="kol-mono-16 text-meta">Time: <b className="text-strong">{clock(bar.duration)}</b></span>
45
- </div>
46
- </QuickLookFrame>
47
- )
48
- }
@@ -1,25 +0,0 @@
1
- import usePlayback from '../hooks/usePlayback.js'
2
- import PlaybackBar from './PlaybackBar.jsx'
3
- import QuickLookFrame from './QuickLookFrame.jsx'
4
-
5
- /* taxonomy-ok: molecule — nests PlaybackBar + QuickLookFrame (relative). */
6
-
7
- /**
8
- * VideoSheet — A video in the preview window. video in the Quick Look window: the frame at the video's own aspect ratio, the
9
- * QuickTime bar docked in the window's footer (2026-09-23 — it floated over the picture, which
10
- * made video the one kind whose controls sat on the media). No native controls; click on the
11
- * video toggles play; no autoplay (user 2026-09-23 — Quick Look opens paused), `playsInline`, `preload="metadata"`.
12
- *
13
- * @param {string} src the video URL
14
- * @param {string} poster the poster URL
15
- * @param {Function} onMeta ({ w, h, len }) => void once metadata lands — the window's facts
16
- * @param {Object} frame the window's header — `QuickLookFrame`'s title · meta · actions · onClose
17
- */
18
- export default function VideoSheet({ src, poster, onMeta, frame }) {
19
- const { ref, handlers, bar } = usePlayback((el) => onMeta?.({ w: el.videoWidth, h: el.videoHeight, len: el.duration }))
20
- return (
21
- <QuickLookFrame {...frame} footer={<PlaybackBar {...bar} docked />}>
22
- <video ref={ref} src={src} poster={poster} playsInline preload="metadata" className="kol-quicklook-media" onClick={bar.onToggle} {...handlers} />
23
- </QuickLookFrame>
24
- )
25
- }
File without changes
File without changes