@proteos/ui 0.76.0 → 0.78.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 (32) hide show
  1. package/dist/index.cjs +991 -5
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +592 -2
  4. package/dist/index.d.ts +592 -2
  5. package/dist/index.js +969 -9
  6. package/dist/index.js.map +1 -1
  7. package/package.json +3 -2
  8. package/src/components/data-table/data-table.scroll-restore.test.tsx +97 -0
  9. package/src/components/data-table/data-table.test.tsx +22 -2
  10. package/src/components/data-table/data-table.tsx +31 -1
  11. package/src/components/frame-strip/frame-strip.stories.tsx +61 -0
  12. package/src/components/frame-strip/frame-strip.test.tsx +112 -0
  13. package/src/components/frame-strip/frame-strip.tsx +131 -0
  14. package/src/components/frame-strip/index.ts +2 -0
  15. package/src/components/media-player/index.ts +22 -0
  16. package/src/components/media-player/media-controls.tsx +145 -0
  17. package/src/components/media-player/media-markers.tsx +108 -0
  18. package/src/components/media-player/media-peaks.test.ts +68 -0
  19. package/src/components/media-player/media-peaks.ts +78 -0
  20. package/src/components/media-player/media-player.stories.tsx +243 -0
  21. package/src/components/media-player/media-player.test.tsx +318 -0
  22. package/src/components/media-player/media-player.tsx +334 -0
  23. package/src/components/media-player/media-time.ts +27 -0
  24. package/src/components/media-player/media-timeline.test.tsx +133 -0
  25. package/src/components/media-player/media-timeline.tsx +78 -0
  26. package/src/components/media-player/media-video-stage.tsx +113 -0
  27. package/src/components/media-player/media-waveform.tsx +330 -0
  28. package/src/components/slider/index.ts +2 -0
  29. package/src/components/slider/slider.stories.tsx +32 -0
  30. package/src/components/slider/slider.test.tsx +35 -0
  31. package/src/components/slider/slider.tsx +89 -0
  32. package/src/index.ts +3 -0
@@ -0,0 +1,35 @@
1
+ import { fireEvent, render, screen } from '@testing-library/react'
2
+ import { describe, expect, it, vi } from 'vitest'
3
+
4
+ import { Slider } from './slider'
5
+
6
+ describe('Slider', () => {
7
+ it('exposes its name and value', () => {
8
+ render(<Slider aria-label="Volume" value={30} maxValue={100} />)
9
+ const slider = screen.getByRole('slider', { name: 'Volume' })
10
+ expect(slider).toHaveValue('30')
11
+ })
12
+
13
+ it('announces valueText instead of the number', () => {
14
+ render(<Slider aria-label="Seek" value={48} maxValue={156} valueText="48 seconds" />)
15
+ expect(screen.getByRole('slider')).toHaveAttribute('aria-valuetext', '48 seconds')
16
+ })
17
+
18
+ it('keeps valueText current when the value changes', () => {
19
+ const { rerender } = render(<Slider aria-label="Seek" value={1} maxValue={9} valueText="one" />)
20
+ rerender(<Slider aria-label="Seek" value={2} maxValue={9} valueText="two" />)
21
+ expect(screen.getByRole('slider')).toHaveAttribute('aria-valuetext', 'two')
22
+ })
23
+
24
+ it('reports changes', () => {
25
+ const onChange = vi.fn()
26
+ render(<Slider aria-label="Seek" value={10} maxValue={100} onChange={onChange} />)
27
+ fireEvent.change(screen.getByRole('slider'), { target: { value: '40' } })
28
+ expect(onChange).toHaveBeenCalledWith(40)
29
+ })
30
+
31
+ it('disables its input', () => {
32
+ render(<Slider aria-label="Seek" value={0} maxValue={100} isDisabled />)
33
+ expect(screen.getByRole('slider')).toBeDisabled()
34
+ })
35
+ })
@@ -0,0 +1,89 @@
1
+ import { useLayoutEffect, useRef } from 'react'
2
+ import {
3
+ Slider as AriaSlider,
4
+ type SliderProps as AriaSliderProps,
5
+ SliderThumb,
6
+ SliderTrack,
7
+ } from 'react-aria-components'
8
+ import { tv, type VariantProps } from 'tailwind-variants'
9
+
10
+ import { cn } from '../../lib/cn'
11
+
12
+ /* =========================================================================
13
+ Slider — a single-thumb numeric range on RAC Slider. Thin rail, ink fill,
14
+ small ink thumb; the 18px track is the pointer target around the 4px rail.
15
+ `valueText` replaces the announced number when the raw value means little
16
+ on its own (a media position reads "48 seconds of 2 minutes").
17
+ ========================================================================= */
18
+
19
+ export const sliderStyles = tv({
20
+ slots: {
21
+ root: 'flex w-full items-center',
22
+ track: 'group relative h-[18px] w-full cursor-pointer data-[disabled]:cursor-default',
23
+ rail: 'absolute inset-x-0 top-1/2 h-1 -translate-y-1/2 overflow-hidden rounded-pill bg-surface-3',
24
+ fill: 'h-full bg-ink',
25
+ thumb: [
26
+ 'top-1/2 size-3 rounded-pill bg-ink',
27
+ 'shadow-[var(--shadow-rest)]',
28
+ 'data-[focus-visible]:shadow-[var(--shadow-focus)]',
29
+ 'data-[disabled]:hidden',
30
+ ],
31
+ },
32
+ })
33
+
34
+ export type SliderVariants = VariantProps<typeof sliderStyles>
35
+
36
+ export type SliderProps = Omit<
37
+ AriaSliderProps<number>,
38
+ 'children' | 'value' | 'defaultValue' | 'onChange' | 'onChangeEnd' | 'orientation'
39
+ > & {
40
+ value: number
41
+ onChange?: (value: number) => void
42
+ onChangeEnd?: (value: number) => void
43
+ /** Spoken in place of the number. */
44
+ valueText?: string
45
+ }
46
+
47
+ export function Slider({
48
+ value,
49
+ onChange,
50
+ onChangeEnd,
51
+ valueText,
52
+ className,
53
+ ...props
54
+ }: SliderProps) {
55
+ const styles = sliderStyles()
56
+ const inputRef = useRef<HTMLInputElement>(null)
57
+
58
+ // RAC writes its formatted number onto the thumb's input on every render and
59
+ // offers no override, so ours has to land after it.
60
+ useLayoutEffect(() => {
61
+ if (valueText !== undefined) inputRef.current?.setAttribute('aria-valuetext', valueText)
62
+ })
63
+
64
+ return (
65
+ <AriaSlider
66
+ {...props}
67
+ value={value}
68
+ {...(onChange ? { onChange } : {})}
69
+ {...(onChangeEnd ? { onChangeEnd } : {})}
70
+ className={(renderProps) =>
71
+ cn(styles.root(), typeof className === 'function' ? className(renderProps) : className)
72
+ }
73
+ >
74
+ <SliderTrack className={styles.track()}>
75
+ {({ state }) => (
76
+ <>
77
+ <div className={styles.rail()}>
78
+ <div
79
+ className={styles.fill()}
80
+ style={{ width: `${state.getThumbPercent(0) * 100}%` }}
81
+ />
82
+ </div>
83
+ <SliderThumb inputRef={inputRef} className={styles.thumb()} />
84
+ </>
85
+ )}
86
+ </SliderTrack>
87
+ </AriaSlider>
88
+ )
89
+ }
package/src/index.ts CHANGED
@@ -26,12 +26,14 @@ export * from './components/error-animation'
26
26
  export * from './components/field'
27
27
  export * from './components/file-field'
28
28
  export * from './components/file-viewer'
29
+ export * from './components/frame-strip'
29
30
  export * from './components/glow-frame'
30
31
  export * from './components/icon-picker'
31
32
  export * from './components/image-attachment'
32
33
  export * from './components/image-lightbox'
33
34
  export * from './components/input'
34
35
  export * from './components/kbd'
36
+ export * from './components/media-player'
35
37
  export * from './components/menu'
36
38
  export * from './components/message-composer'
37
39
  export * from './components/modal'
@@ -49,6 +51,7 @@ export * from './components/segmented-control'
49
51
  export * from './components/select'
50
52
  export * from './components/separator'
51
53
  export * from './components/skeleton'
54
+ export * from './components/slider'
52
55
  export * from './components/stepper'
53
56
  export * from './components/success-animation'
54
57
  export * from './components/switch'