@sesamehr/react-design-system 3.4.0 → 3.5.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 (68) hide show
  1. package/dist/Button/index.d.ts +1 -1
  2. package/dist/Display/AiTag/AiTag.d.ts +33 -0
  3. package/dist/Display/AiTag/index.d.ts +21 -0
  4. package/dist/Display/Avatar/index.d.ts +2 -2
  5. package/dist/Display/Badge/index.d.ts +1 -1
  6. package/dist/Display/BrandLogo/BrandLogo.d.ts +53 -0
  7. package/dist/Display/BrandLogo/brandLogoArt.d.ts +29 -0
  8. package/dist/Display/BrandLogo/index.d.ts +17 -0
  9. package/dist/Display/FileCard/FileCard.d.ts +102 -41
  10. package/dist/Display/FileCard/index.d.ts +31 -0
  11. package/dist/Display/ImageAttachment/ImageAttachment.d.ts +58 -0
  12. package/dist/Display/ImageAttachment/index.d.ts +2 -0
  13. package/dist/Display/LinkPreview/LinkPreview.d.ts +65 -0
  14. package/dist/Display/LinkPreview/index.d.ts +1 -0
  15. package/dist/Display/LocationAttachment/LocationAttachment.d.ts +71 -0
  16. package/dist/Display/LocationAttachment/index.d.ts +1 -0
  17. package/dist/Display/VideoAttachment/VideoAttachment.d.ts +78 -0
  18. package/dist/Display/VideoAttachment/index.d.ts +1 -0
  19. package/dist/Display/VoiceMessage/VoiceMessage.d.ts +103 -0
  20. package/dist/Display/VoiceMessage/index.d.ts +2 -0
  21. package/dist/Display/VoiceMessage/toWaveformPeaks.d.ts +58 -0
  22. package/dist/Display/VoiceMessage/useVoiceWaveform.d.ts +90 -0
  23. package/dist/Feedback/Dialog/Dialog.d.ts +13 -1
  24. package/dist/Feedback/Dialog/DialogOverlay.d.ts +12 -1
  25. package/dist/Feedback/Drawer/Drawer.d.ts +68 -0
  26. package/dist/Feedback/Drawer/DrawerBody.d.ts +18 -0
  27. package/dist/Feedback/Drawer/DrawerContent.d.ts +36 -0
  28. package/dist/Feedback/Drawer/DrawerDescription.d.ts +10 -0
  29. package/dist/Feedback/Drawer/DrawerExpand.d.ts +23 -0
  30. package/dist/Feedback/Drawer/DrawerTitle.d.ts +13 -0
  31. package/dist/Feedback/Drawer/index.d.ts +7 -0
  32. package/dist/Feedback/Drawer/useDrawer.d.ts +30 -0
  33. package/dist/Feedback/Progress/index.d.ts +1 -1
  34. package/dist/Forms/Inputs/AiSearch/AiSearch.d.ts +67 -0
  35. package/dist/Forms/Inputs/AiSearch/AiSearchAsk.d.ts +34 -0
  36. package/dist/Forms/Inputs/AiSearch/AiSearchPanel.d.ts +50 -0
  37. package/dist/Forms/Inputs/AiSearch/AiSearchSend.d.ts +30 -0
  38. package/dist/Forms/Inputs/AiSearch/AiSearchShortcut.d.ts +23 -0
  39. package/dist/Forms/Inputs/AiSearch/AiSearchSuggestion.d.ts +27 -0
  40. package/dist/Forms/Inputs/AiSearch/index.d.ts +6 -0
  41. package/dist/Forms/Inputs/AiSearch/useAiSearch.d.ts +37 -0
  42. package/dist/Forms/Inputs/TimePicker/TimePicker.d.ts +64 -0
  43. package/dist/Forms/Inputs/TimePicker/TimePickerField.d.ts +35 -0
  44. package/dist/Forms/Inputs/TimePicker/TimePickerList.d.ts +23 -0
  45. package/dist/Forms/Inputs/TimePicker/TimePickerWheel.d.ts +26 -0
  46. package/dist/Forms/Inputs/TimePicker/TimePickerWheelColumn.d.ts +23 -0
  47. package/dist/Forms/Inputs/TimePicker/index.d.ts +6 -0
  48. package/dist/Forms/Inputs/TimePicker/time.d.ts +41 -0
  49. package/dist/Forms/Inputs/TimePicker/useTimeField.d.ts +62 -0
  50. package/dist/Forms/Inputs/TimePicker/useTimeList.d.ts +53 -0
  51. package/dist/Forms/Inputs/TimePicker/useTimePicker.d.ts +35 -0
  52. package/dist/Forms/Inputs/TimePicker/useWheelColumn.d.ts +71 -0
  53. package/dist/Forms/Inputs/index.d.ts +1 -0
  54. package/dist/Navigation/Tabs/Tabs.d.ts +18 -0
  55. package/dist/Navigation/Tabs/TabsContent.d.ts +7 -0
  56. package/dist/Navigation/Tabs/TabsList.d.ts +10 -0
  57. package/dist/Navigation/Tabs/TabsTrigger.d.ts +20 -0
  58. package/dist/Navigation/Tabs/index.d.ts +4 -0
  59. package/dist/internal/AttachmentMedia/AttachmentMedia.d.ts +38 -0
  60. package/dist/internal/AttachmentMedia/index.d.ts +36 -0
  61. package/dist/internal/index.d.ts +1 -0
  62. package/dist/main.d.ts +12 -2
  63. package/dist/react-design-system.css +1 -1
  64. package/dist/react-design-system.js +12581 -10218
  65. package/dist/react-design-system.umd.cjs +108 -74
  66. package/package.json +3 -2
  67. package/src/assets/styles/theme-v3.css +99 -2
  68. package/src/assets/styles/theme-v3.test.ts +5 -5
@@ -0,0 +1,78 @@
1
+ import { ReactNode } from 'react';
2
+ import { AttachmentImage, AttachmentSurface } from '../../internal/AttachmentMedia/index.ts';
3
+ /**
4
+ * A video attached to a chat message: its poster frame, a play affordance and
5
+ * however long it runs.
6
+ * Mirrors `@sesame/orxata-core` VideoAttachment.
7
+ *
8
+ * No primitive underneath. shadcn has no video card and Radix has no primitive
9
+ * in this shape — there is no open state, no roving focus and no value to
10
+ * coordinate, only a single activation. What is left is one `<button>`, which
11
+ * is what Radix's `Primitive` would have rendered anyway, and
12
+ * `OxImageAttachment` reached the same place one cell at a time.
13
+ *
14
+ * The picture, its three states and the `Surface` tint are `AttachmentMedia`'s,
15
+ * shared with the other four attachments. This file adds the two things that
16
+ * are only a video's: the circle and the pill over it.
17
+ *
18
+ * ### The whole thumbnail is the button
19
+ *
20
+ * The behaviour board puts the full-screen player behind a click on the
21
+ * thumbnail, not on the circle, and nothing inside is interactive, so there is
22
+ * no control to nest — the opposite of `OxFileCard`, whose root refuses to be
23
+ * one for exactly that reason. The circle is therefore decoration and
24
+ * `aria-hidden`; the button is named by the poster's `alt` and the duration,
25
+ * both of which sit inside it. That is the accessibility board's "the duration
26
+ * is announced beside the button, not as loose text".
27
+ *
28
+ * The board also asks the button to be labelled "Play video, duration m:ss".
29
+ * This package ships no strings and has no catalogue to add one to, so the
30
+ * wording is the application's; `alt` plus the duration is the honest name a
31
+ * design system can assemble.
32
+ *
33
+ * ### `onPlay`, and why it displaces the DOM's own
34
+ *
35
+ * The Vue original emits `play`, so the port names the handler `onPlay` —
36
+ * which on a `<button>` collides with React's media event of the same name.
37
+ * The media one is omitted rather than renamed: a `<button>` never fires it, so
38
+ * nothing real is lost, and keeping the Vue name is what lets a consumer move
39
+ * between the two packages without relearning the API. A caller's own
40
+ * `onClick` still arrives and runs first.
41
+ *
42
+ * ### Why the duration is a node and not a string
43
+ *
44
+ * `OxLinkPreview` and `OxLocationAttachment` carry a title and a description,
45
+ * names the composition rule does not allow as props at all, so every sibling
46
+ * takes its text as a node and none of them takes it a second way. The pill's
47
+ * shape, position and colour stay the component's; only the string is the
48
+ * application's, and it has no opinion on whether that reads `0:42`,
49
+ * `1:03:20` or `12 min`.
50
+ *
51
+ * ### It has no size
52
+ *
53
+ * The width is the bubble's and the poster keeps its 3:2 shape at whatever
54
+ * width that turns out to be, which is `AttachmentMedia`'s aspect ratio rather
55
+ * than the 240×160 the board prints.
56
+ */
57
+ export interface VideoAttachmentProps extends Omit<React.ComponentPropsWithoutRef<'button'>, 'onPlay'> {
58
+ /**
59
+ * The poster frame, if there is one. Without it the box keeps its tint and
60
+ * shows a glyph, which is also what a poster that fails to load falls back
61
+ * to.
62
+ */
63
+ image?: AttachmentImage;
64
+ /**
65
+ * Which side of the bubble this sits in: `brand` inside the reader's own
66
+ * message, `default` inside anyone else's.
67
+ */
68
+ surface?: AttachmentSurface;
69
+ /**
70
+ * How long the video runs, drawn as the pill over the poster. Ports the Vue
71
+ * `#duration` slot; omitted, there is no pill at all.
72
+ */
73
+ duration?: ReactNode;
74
+ /** The thumbnail was activated. Open the player; nothing plays in place. */
75
+ onPlay?: () => void;
76
+ dataTestid: string;
77
+ }
78
+ export declare const VideoAttachment: import('react').ForwardRefExoticComponent<VideoAttachmentProps & import('react').RefAttributes<HTMLButtonElement>>;
@@ -0,0 +1 @@
1
+ export { VideoAttachment, type VideoAttachmentProps, } from './VideoAttachment.tsx';
@@ -0,0 +1,103 @@
1
+ import { ReactNode } from 'react';
2
+ import { AttachmentSurface } from '../../internal/AttachmentMedia/index.ts';
3
+ /**
4
+ * A voice note in a chat message: a play button, the recording's waveform and
5
+ * how long it runs.
6
+ * Mirrors `@sesame/orxata-core` VoiceMessage.
7
+ *
8
+ * No primitive underneath, and this one is a decision rather than an absence.
9
+ * shadcn has no audio player; Radix has `Slider`, which is the right behaviour
10
+ * but the wrong DOM — Radix puts `role="slider"` on a *thumb*, and the board
11
+ * draws no thumb at all. Introducing one would invent a control the design does
12
+ * not have and a focus state nobody drew, so the gesture and the ARIA are
13
+ * implemented here against Radix's own conventions: one `role="slider"`
14
+ * element, `aria-valuemin`/`max`/`now`, arrows and Home/End, pointer capture on
15
+ * drag. The bars inside it are presentational children of a slider, which is
16
+ * what keeps a 32-bar waveform from announcing itself 32 times.
17
+ *
18
+ * ### It does not decode audio, and it does not own an `<audio>` element
19
+ *
20
+ * `peaks` arrives as data. Real peaks are computed server-side at upload, which
21
+ * is what WhatsApp, Telegram and Slack do and why their waveforms are visible
22
+ * before playback starts — decoding in the browser means downloading and
23
+ * decoding the whole file before anything can be drawn. Whether the numbers are
24
+ * real or decorative is the application's business, exactly as `OxFileCard`
25
+ * does not upload files.
26
+ *
27
+ * `toWaveformPeaks` converts the shapes a backend is likely to emit into the
28
+ * contract. It is a separate exported function the application calls at its own
29
+ * boundary rather than a guess made inside this component: sniffing the shape
30
+ * here would type `peaks` as `unknown` and make every new upstream format a
31
+ * change to a component.
32
+ *
33
+ * ### Controlled, with no fallback
34
+ *
35
+ * `playing` and `progress` are plain props with plain handlers, the same shape
36
+ * as `OxSwitch` and for the same reason: there is one player per conversation
37
+ * and one `<audio>` element behind it, both of which live in the application.
38
+ * An uncontrolled path here would let the component seed itself from the first
39
+ * value and carry on playing a position the real audio had left behind — so
40
+ * there deliberately is not one, and the Vue original has no `defineModel`
41
+ * either.
42
+ *
43
+ * So nothing moves unless the parent moves it. A `useState` in the caller,
44
+ * wired to `onPlayingChange` and `onProgressChange`, is the ordinary way.
45
+ *
46
+ * ### How many bars, and why the component decides
47
+ *
48
+ * The length of `peaks` is unconstrained because the number of bars is a visual
49
+ * decision and a backend cannot make it. `audiowaveform` emits whatever its
50
+ * samples-per-pixel gives it; how many 3px bars fit is a function of the
51
+ * bubble's width. The track is measured and the peaks are bucketed down to fit
52
+ * — see `useVoiceWaveform` for the maximum-per-bucket argument. Fewer peaks
53
+ * than bars draws short and left-aligned, which is what the board shows.
54
+ *
55
+ * ### Two nodes, because the package ships no strings
56
+ *
57
+ * `label` is the accessible name of the pair of controls — visually hidden, and
58
+ * the one thing a design system cannot supply, since it has no catalogue to add
59
+ * "Voice message" to. The button then carries state through `aria-pressed`
60
+ * rather than a name that changes, which is the only version of play/pause that
61
+ * needs no second string.
62
+ *
63
+ * `duration` is the visible one, and a node for the same reason
64
+ * `OxVideoAttachment`'s is: the pill's shape is the component's and the string
65
+ * is the application's, which has no opinion on `0:18` against `18 s`. With no
66
+ * `label` the duration names the controls instead, so the component is never
67
+ * nameless.
68
+ */
69
+ export interface VoiceMessageProps extends React.ComponentPropsWithoutRef<'div'> {
70
+ /**
71
+ * The waveform, as magnitudes normalised to 0–1 — one per sample, any
72
+ * length. Run a backend's own shape through `toWaveformPeaks` first.
73
+ */
74
+ peaks: number[];
75
+ /**
76
+ * Whether the note is playing. Controlled: the glyph follows this and
77
+ * nothing else.
78
+ */
79
+ playing?: boolean;
80
+ /** How far through the note the playhead is, 0–1. Controlled. */
81
+ progress?: number;
82
+ /**
83
+ * Which side of the bubble this sits in: `brand` inside the reader's own
84
+ * message, `default` inside anyone else's.
85
+ */
86
+ surface?: AttachmentSurface;
87
+ /**
88
+ * The accessible name of both controls, read and not seen. Ports the Vue
89
+ * `#label` slot; without it the duration names them instead.
90
+ */
91
+ label?: ReactNode;
92
+ /** How long the note runs, drawn beside the waveform. Ports `#duration`. */
93
+ duration?: ReactNode;
94
+ /** The button was pressed. Start or stop the audio. */
95
+ onPlayingChange?: (playing: boolean) => void;
96
+ /**
97
+ * The listener scrubbed the waveform. Move the playhead to this 0–1
98
+ * position.
99
+ */
100
+ onProgressChange?: (progress: number) => void;
101
+ dataTestid: string;
102
+ }
103
+ export declare const VoiceMessage: import('react').ForwardRefExoticComponent<VoiceMessageProps & import('react').RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,2 @@
1
+ export { VoiceMessage, type VoiceMessageProps } from './VoiceMessage.tsx';
2
+ export { type AudiowaveformJson, type WaveformSource, toWaveformPeaks, } from './toWaveformPeaks.ts';
@@ -0,0 +1,58 @@
1
+ /**
2
+ * BBC `audiowaveform`'s JSON, as `audiowaveform -i in.mp3 -o out.json` writes
3
+ * it.
4
+ *
5
+ * Field names are the tool's own and are therefore `snake_case`, which is why
6
+ * this interface is the one place in the package that spells props that way:
7
+ * renaming them would mean the application had to translate before it could
8
+ * call the translator.
9
+ *
10
+ * `version` is 1 or 2 and `channels` exists only in 2. Everything else is
11
+ * present in both.
12
+ *
13
+ * @see https://github.com/bbc/audiowaveform/blob/master/doc/DataFormat.md
14
+ */
15
+ export interface AudiowaveformJson {
16
+ version?: number;
17
+ /** Version 2 only. Absent means one channel. */
18
+ channels?: number;
19
+ sample_rate?: number;
20
+ samples_per_pixel?: number;
21
+ /** 8 or 16. Decides the scale `data` is written at. */
22
+ bits: number;
23
+ /** Minimum/maximum pairs per channel, which is `data.length / (2 * channels)`. */
24
+ length?: number;
25
+ /** Minimum and maximum pairs, interleaved, and across channels too. */
26
+ data: readonly number[];
27
+ }
28
+ /** Anything `toWaveformPeaks` knows how to read. */
29
+ export type WaveformSource = AudiowaveformJson | readonly number[];
30
+ /**
31
+ * Turns what a backend has into what `OxVoiceMessage` draws: magnitudes
32
+ * normalised to 0…1, one per sample.
33
+ *
34
+ * **A function the application calls at its own boundary, not something the
35
+ * component does.** Sniffing the shape inside the component would mean typing
36
+ * its `peaks` prop as `unknown` and losing the only check there is that the
37
+ * numbers are numbers; it would also mean every new upstream format was a
38
+ * change to a component rather than a case in a function. Here, a new format is
39
+ * one more branch and `OxVoiceMessage` never moves.
40
+ *
41
+ * It reads three things:
42
+ *
43
+ * - **BBC `audiowaveform` JSON**, the tool that precomputes this server-side.
44
+ * Minimum/maximum pairs, interleaved across channels, at 8 or 16 bits.
45
+ * - **A flat array in an arbitrary range** — `−1…1`, `0…255`, or anything
46
+ * else: it is normalised by its own loudest value.
47
+ * - **A flat array already inside 0…1**, which comes back unchanged.
48
+ *
49
+ * Anything else **throws**, naming what arrived. A waveform is decoration that
50
+ * nobody checks against the audio, so a silently wrong one would survive every
51
+ * review; an exception at the boundary is the only place the mistake is still
52
+ * cheap.
53
+ *
54
+ * ```ts
55
+ * const peaks = toWaveformPeaks(await (await fetch(waveformUrl)).json());
56
+ * ```
57
+ */
58
+ export declare const toWaveformPeaks: (source: WaveformSource) => number[];
@@ -0,0 +1,90 @@
1
+ /**
2
+ * The bar and the gap the board draws, in pixels, and the only numbers in this
3
+ * file that also live in the stylesheet.
4
+ *
5
+ * They have to: how many bars fit is arithmetic, and CSS cannot bucket an
6
+ * array. `VoiceMessage.test.tsx` asserts the stylesheet still says `w-0.75` and
7
+ * `gap-0.5`, so changing one half fails there rather than quietly drawing a
8
+ * waveform at the wrong pitch.
9
+ */
10
+ export declare const VOICE_WAVEFORM_BAR_PX = 3;
11
+ export declare const VOICE_WAVEFORM_GAP_PX = 2;
12
+ /**
13
+ * How many bars to draw before the track has ever been measured — on the
14
+ * server, and on the first frame in a browser.
15
+ *
16
+ * It is the count that fits the board's own track: 240px wide, less the 36px
17
+ * button, the two 10px gaps and the duration, leaves 162px, and 162px holds 32
18
+ * bars at a 5px pitch. So an unmeasured waveform is drawn at the width the
19
+ * design was drawn at, which is the closest thing to a right answer available
20
+ * before layout.
21
+ */
22
+ export declare const VOICE_WAVEFORM_FALLBACK_BARS = 32;
23
+ /** One bar: how tall it is, and whether the playhead has passed it. */
24
+ export interface VoiceWaveformBar {
25
+ key: number;
26
+ peak: number;
27
+ played: boolean;
28
+ }
29
+ /**
30
+ * `peaks` reduced to exactly `count` values, or left as it is when there are
31
+ * fewer peaks than bars.
32
+ *
33
+ * **Maximum per bucket, not RMS.** Two reasons, and the first is the contract:
34
+ * what arrives is already magnitudes, so the sign is gone and a root mean
35
+ * square over them is no longer the signal's energy — it is just a smoother
36
+ * average wearing the name of a measurement. The second is what a waveform is
37
+ * for: it is read to find the loud bit, and a maximum is the only reduction
38
+ * that keeps a transient visible once 400 samples have to fit in 32 bars. RMS
39
+ * would flatten a clap between two silences into an unremarkable bar.
40
+ *
41
+ * Buckets are cut by proportion rather than by a fixed size, so a length that
42
+ * does not divide evenly spreads the remainder across the track instead of
43
+ * dropping the tail.
44
+ */
45
+ export declare const bucketPeaks: (peaks: readonly number[], count: number) => number[];
46
+ export interface UseVoiceWaveformOptions {
47
+ /**
48
+ * The waveform. A plain value where the Vue original takes a getter: a
49
+ * composable is set up once and has to defer reading its props, while a hook
50
+ * re-runs on every render and so is handed the current value directly.
51
+ */
52
+ peaks: readonly number[];
53
+ progress: number;
54
+ /** Called with a 0…1 position whenever the listener scrubs. */
55
+ seek: (fraction: number) => void;
56
+ }
57
+ /**
58
+ * The waveform's two jobs, which share one element and so share one hook:
59
+ * deciding how many bars to draw, and turning a gesture on them into a
60
+ * position. Port of `core/src/Display/VoiceMessage/useVoiceWaveform.ts`.
61
+ *
62
+ * **How many bars is a question only the element can answer.** The backend
63
+ * sends however many peaks it computed — `audiowaveform` at a given
64
+ * samples-per-pixel will happily produce hundreds — and how many bars fit is a
65
+ * function of the bubble's width, which nothing knows until layout has run. So
66
+ * the track is measured and the peaks are bucketed down to what fits, at the
67
+ * board's fixed 3px/2px pitch; drawing all of them instead would either overflow
68
+ * or squeeze the bars to hairlines, and clipping them would hide the end of the
69
+ * recording.
70
+ *
71
+ * Fewer peaks than bars is left alone rather than stretched: the board's own
72
+ * waveform is left-aligned in a track wider than its bars, so a short recording
73
+ * drawing short is what the design already shows.
74
+ *
75
+ * The handlers are rebuilt on every render rather than memoised. They close
76
+ * over `progress` and `seek`, both of which change as the note plays, so a
77
+ * `useCallback` keyed on them would rebuild anyway — and one keyed on less than
78
+ * them would scrub against a position the audio had already left behind.
79
+ * `dragging` is the one thing that must survive a render, which is why it is a
80
+ * ref: holding it in state would restart the gesture mid-drag.
81
+ */
82
+ export declare const useVoiceWaveform: (options: UseVoiceWaveformOptions) => {
83
+ track: import('react').RefObject<HTMLDivElement | null>;
84
+ bars: VoiceWaveformBar[];
85
+ percent: number;
86
+ onPointerDown: (event: React.PointerEvent<HTMLDivElement>) => void;
87
+ onPointerMove: (event: React.PointerEvent<HTMLDivElement>) => void;
88
+ onPointerUp: (event: React.PointerEvent<HTMLDivElement>) => void;
89
+ onKeyDown: (event: React.KeyboardEvent<HTMLDivElement>) => void;
90
+ };
@@ -5,7 +5,19 @@ export interface DialogProps {
5
5
  onOpenChange?: (open: boolean) => void;
6
6
  modal?: boolean;
7
7
  }
8
- /** Dialog root — built on `@radix-ui/react-dialog`. */
8
+ /**
9
+ * Dialog root — built on `@radix-ui/react-dialog`.
10
+ *
11
+ * `open` is destructured with **no default**, and that is deliberate. Radix
12
+ * decides whether the caller owns the state by testing `open === undefined`,
13
+ * so a default of `false` here would read as "the caller controls this and says
14
+ * closed" and `defaultOpen` would be ignored — the panel would render nothing
15
+ * on mount while still opening from its trigger. `OxDialog` on the Vue side has
16
+ * the same hazard through a different door, since Vue casts an absent `Boolean`
17
+ * prop to `false` unless the prop declares a default; `Dialog.vue` carries the
18
+ * matching note. Nothing casts anything here, so leaving `open` alone is the
19
+ * whole fix.
20
+ */
9
21
  export declare const Dialog: {
10
22
  ({ children, defaultOpen, open, onOpenChange, modal, }: DialogProps): import("react/jsx-runtime").JSX.Element;
11
23
  displayName: string;
@@ -1,4 +1,15 @@
1
1
  import * as DialogPrimitive from '@radix-ui/react-dialog';
2
2
  export type DialogOverlayProps = React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>;
3
- /** DialogOverlay — the dimmed backdrop behind dialog content. */
3
+ /**
4
+ * DialogOverlay — the glass behind dialog content.
5
+ *
6
+ * Radix's primitive owns the portal, the presence and the pointer handling; the
7
+ * look comes from `ox-overlay`, shared with whatever surface lands next — see
8
+ * `../overlay.css`.
9
+ *
10
+ * `data-slot="dialog-overlay"` is unchanged and remains the hook to target. The
11
+ * class was `styles.oxDialogOverlay` while the declarations lived in this
12
+ * component's module, which means it only ever reached the DOM hashed; the
13
+ * shared name is the first stable one, and it is the same string `core` applies.
14
+ */
4
15
  export declare const DialogOverlay: import('react').ForwardRefExoticComponent<Omit<DialogPrimitive.DialogOverlayProps & import('react').RefAttributes<HTMLDivElement>, "ref"> & import('react').RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,68 @@
1
+ export interface DrawerProps {
2
+ children?: React.ReactNode;
3
+ /** The controlled open state. Pair it with `onOpenChange`. */
4
+ open?: boolean;
5
+ defaultOpen?: boolean;
6
+ /**
7
+ * Whether the drawer blocks the page behind it. `true` draws the glass and
8
+ * locks body scroll; `false` leaves the list operable while the panel is open,
9
+ * which the board draws as `Show overlay: No`.
10
+ */
11
+ modal?: boolean;
12
+ /**
13
+ * Which edge the panel is anchored to.
14
+ *
15
+ * Two values, not four. The board draws the right edge only — the component
16
+ * set has a single `Size` axis and its description says "panel lateral
17
+ * derecho" — and a top or bottom panel is a Sheet, which is mobile and is not
18
+ * this component.
19
+ */
20
+ side?: 'left' | 'right';
21
+ onOpenChange?: (open: boolean) => void;
22
+ }
23
+ /**
24
+ * Drawer root — mirrors `@sesame/orxata-core` Drawer, built on
25
+ * `@radix-ui/react-dialog`.
26
+ *
27
+ * **A sibling of `OxDialog`, not a shape of it.** Both stand on the dialog
28
+ * primitive, which is where the focus trap, the body scroll lock, the Escape
29
+ * handling, the portal, `role="dialog"` and the `aria-labelledby` /
30
+ * `aria-describedby` wiring come from — hand-rolling any of that is how a
31
+ * surface ships unusable by keyboard. What makes a drawer its own component is
32
+ * everything above the primitive: it is anchored to an edge rather than centred,
33
+ * it is full height, it slides instead of scaling, it can be non-modal so the
34
+ * list behind stays operable, and it grows. None of that is a content shape.
35
+ *
36
+ * Note that modality arrives as `aria-hidden` on everything else rather than as
37
+ * `aria-modal` on the panel — Radix's choice, reka's too, and the current APG
38
+ * advice, since `aria-modal` leaves the rest of the page reachable by a screen
39
+ * reader's own navigation in several implementations. `Drawer.test.tsx` asserts
40
+ * the former, so nobody goes looking for the latter and concludes it is missing.
41
+ *
42
+ * **Why this root holds the open state rather than leaving it to the
43
+ * primitive.** `DrawerContent` has to know whether the panel is open: it drives
44
+ * the slide from `OxTransitionSlideX` rather than from the primitive's own
45
+ * presence, so it is what decides when the panel mounts. On the Vue side it asks
46
+ * reka, through `injectDialogRootContext()`. Radix exports no such hook, so the
47
+ * state lives here and `Dialog.Root` is always controlled from it. Everything
48
+ * else about the primitive is untouched.
49
+ *
50
+ * That is also why `defaultOpen` is not forwarded. Radix decides who owns the
51
+ * state by testing `open === undefined`, and this always passes it a boolean —
52
+ * so handing it `defaultOpen` as well would be giving it an initial value it is
53
+ * no longer in a position to use. It seeds the state below instead, which is the
54
+ * same thing one layer out. `core`'s `Drawer.vue` carries the matching note from
55
+ * the other direction: there, `open: undefined` in `withDefaults` is
56
+ * load-bearing, because Vue would otherwise cast an absent `Boolean` prop to
57
+ * `false` and reka would read that as "the caller says closed".
58
+ *
59
+ * `open` without `onOpenChange` pins the panel, exactly as it does on a bare
60
+ * `Dialog.Root` or a reka `DialogRoot`: a caller that takes the value over is
61
+ * expected to listen. This is not one of the `defineModel` ports that gate on
62
+ * the handler — `Drawer.vue` is a plain prop plus an emit, which is
63
+ * controlled-only, and so is this.
64
+ */
65
+ export declare const Drawer: {
66
+ ({ children, open, defaultOpen, modal, side, onOpenChange, }: DrawerProps): import("react/jsx-runtime").JSX.Element;
67
+ displayName: string;
68
+ };
@@ -0,0 +1,18 @@
1
+ export interface DrawerBodyProps extends React.HTMLAttributes<HTMLDivElement> {
2
+ dataTestid: string;
3
+ }
4
+ /**
5
+ * DrawerBody — mirrors `@sesame/orxata-core` DrawerBody. The scrolling region of
6
+ * the panel, the board's `Content`, "con Slot y scroll interno".
7
+ *
8
+ * The header and the footer stay put; this is the only part that moves once the
9
+ * content is taller than the panel. It carries no styling of its own: the
10
+ * declarations that make it the flexible, scrolling item live on `DrawerContent`
11
+ * next to the `top`/`bottom` pair they depend on, since a body is only a scroll
12
+ * region because the panel is pinned to both edges and splitting that pair
13
+ * across two files is how one of them gets changed alone.
14
+ *
15
+ * `data-slot` rather than position, so it can sit anywhere among the panel's
16
+ * children.
17
+ */
18
+ export declare const DrawerBody: import('react').ForwardRefExoticComponent<DrawerBodyProps & import('react').RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,36 @@
1
+ import * as DialogPrimitive from '@radix-ui/react-dialog';
2
+ export interface DrawerContentProps extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> {
3
+ showCloseButton?: boolean;
4
+ }
5
+ /**
6
+ * DrawerContent — mirrors `@sesame/orxata-core` DrawerContent. The panel itself:
7
+ * the portal, the glass, the slide and the dialog content in one place.
8
+ *
9
+ * **Why `OxTransitionSlideX` and not `OxTransitionReveal`.** The panel's whole
10
+ * movement is along the axis it is anchored to, and `SlideX` is the only one of
11
+ * the two that moves on X at all: `Reveal` rises 26px on Y, which would read as
12
+ * a card appearing rather than a panel entering. `SlideX` also takes
13
+ * `direction` / `leaveDirection` in the same `'left' | 'right'` vocabulary as
14
+ * our own `side`, so the two cannot drift.
15
+ *
16
+ * **One prop of `core`'s is missing, and it could not be kept.** `DrawerContent`
17
+ * on the Vue side takes `trapFocus` and hands it through, because reka's
18
+ * `DialogContent` spreads its `$attrs` into `DialogContentModal`, which declares
19
+ * `trapFocus` as a real prop — so the pass-through works even though reka omits
20
+ * it from `DialogContentProps`. Radix closes both doors: `DialogContentTypeProps`
21
+ * omits `trapFocus`, and `DialogContentModal` sets `trapFocus={context.open}`
22
+ * *after* spreading the caller's props, so anything passed is overwritten.
23
+ * Focus trapping here follows `modal` and nothing else. Do not add the prop
24
+ * back as a no-op to make the two prop lists match — a dead prop is worse than
25
+ * an absent one, and props are the part of the API the parity rules
26
+ * deliberately do not compare.
27
+ *
28
+ * **No portal-target prop.** `OxToaster` has a `to` because a toast viewport is
29
+ * genuinely a region of the application and tests want it somewhere else. A
30
+ * drawer is the opposite case: it is anchored to the viewport, and a portal
31
+ * anywhere but the document root puts it back inside the `overflow` and
32
+ * `transform` ancestors the portal exists to escape — so a `container` here
33
+ * would be an escape hatch whose main use is to break the component. Radix's
34
+ * `Dialog.Portal` is one import away for anybody who really needs it.
35
+ */
36
+ export declare const DrawerContent: import('react').ForwardRefExoticComponent<DrawerContentProps & import('react').RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,10 @@
1
+ import * as DialogPrimitive from '@radix-ui/react-dialog';
2
+ export type DrawerDescriptionProps = React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>;
3
+ /**
4
+ * DrawerDescription — mirrors `@sesame/orxata-core` DrawerDescription.
5
+ *
6
+ * The board's `Subtitle`, bound to `Text/sm` (14/20) and
7
+ * `Semantic-Colors/muted-foreground`. The primitive registers the id
8
+ * `aria-describedby` points at.
9
+ */
10
+ export declare const DrawerDescription: import('react').ForwardRefExoticComponent<Omit<DialogPrimitive.DialogDescriptionProps & import('react').RefAttributes<HTMLParagraphElement>, "ref"> & import('react').RefAttributes<HTMLParagraphElement>>;
@@ -0,0 +1,23 @@
1
+ export type DrawerExpandProps = React.ButtonHTMLAttributes<HTMLButtonElement>;
2
+ /**
3
+ * DrawerExpand — mirrors `@sesame/orxata-core` DrawerExpand.
4
+ *
5
+ * The header's grow/shrink toggle. The board draws it on every variant —
6
+ * `icon-maximize-diagonal` while the panel is at its resting width,
7
+ * `icon-minimize-diagonal` once it has grown (nodes I1463:199;1483:88 on
8
+ * `Size=md` and on `Size=xl`).
9
+ *
10
+ * A component rather than a prop on `DrawerContent`, because it is a control the
11
+ * consumer places: a drawer whose detail does not benefit from the extra width
12
+ * simply does not include it, and no `showExpandButton` has to exist.
13
+ *
14
+ * `aria-pressed` and not a second button: it is one control with two states,
15
+ * which is what a toggle button is. The accessible name changes with it, so a
16
+ * screen reader hears what pressing it will do rather than what it did.
17
+ *
18
+ * Positioned against the panel like the close glyph beside it, and for the same
19
+ * reason — see `DrawerContent`'s `.oxDrawerClose`. It therefore renders
20
+ * correctly wherever it sits among the panel's children; next to the title is
21
+ * where it reads.
22
+ */
23
+ export declare const DrawerExpand: import('react').ForwardRefExoticComponent<DrawerExpandProps & import('react').RefAttributes<HTMLButtonElement>>;
@@ -0,0 +1,13 @@
1
+ import * as DialogPrimitive from '@radix-ui/react-dialog';
2
+ export type DrawerTitleProps = React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>;
3
+ /**
4
+ * DrawerTitle — mirrors `@sesame/orxata-core` DrawerTitle.
5
+ *
6
+ * The panel's heading, and what `aria-labelledby` points at — the board's
7
+ * accessibility note asks for exactly that pairing, and the primitive is what
8
+ * registers the id, so this is not interchangeable with an `<h2>`.
9
+ *
10
+ * `text-xl` and not `OxDialogTitle`'s `text-lg`: the board's `Title` is bound to
11
+ * `Text/xl` (20/28) where the Modal's is `lg`.
12
+ */
13
+ export declare const DrawerTitle: import('react').ForwardRefExoticComponent<Omit<DialogPrimitive.DialogTitleProps & import('react').RefAttributes<HTMLHeadingElement>, "ref"> & import('react').RefAttributes<HTMLHeadingElement>>;
@@ -0,0 +1,7 @@
1
+ export { Drawer, type DrawerProps } from './Drawer.tsx';
2
+ export { DrawerContent, type DrawerContentProps } from './DrawerContent.tsx';
3
+ export { DrawerBody, type DrawerBodyProps } from './DrawerBody.tsx';
4
+ export { DrawerTitle, type DrawerTitleProps } from './DrawerTitle.tsx';
5
+ export { DrawerDescription, type DrawerDescriptionProps, } from './DrawerDescription.tsx';
6
+ export { DrawerExpand, type DrawerExpandProps } from './DrawerExpand.tsx';
7
+ export { Trigger as DrawerTrigger, Close as DrawerClose, } from '@radix-ui/react-dialog';
@@ -0,0 +1,30 @@
1
+ /**
2
+ * What the drawer's parts need from its root, and nothing else.
3
+ *
4
+ * **`open` is here, where `core`'s `DrawerContext` deliberately leaves it out.**
5
+ * That is not drift; it is the one thing the two primitives genuinely do
6
+ * differently. reka publishes its dialog root's state as
7
+ * `injectDialogRootContext()`, so `DrawerContent.vue` reads `open` from the
8
+ * primitive and a second copy in that context would be a second thing able to
9
+ * disagree with it. Radix publishes no equivalent — its `useDialogContext` is
10
+ * module-private and nothing exported reaches it — so the root here owns the
11
+ * state, hands it to `Dialog.Root` as a controlled value and publishes it
12
+ * through this context. Still one source of truth, just a different one.
13
+ */
14
+ export interface DrawerContextValue {
15
+ /** Whether the panel is open. The root owns this — see above. */
16
+ open: boolean;
17
+ /** Which edge the panel is anchored to. */
18
+ side: 'left' | 'right';
19
+ /** Whether the panel has grown to its inset limit. */
20
+ expanded: boolean;
21
+ toggleExpanded: () => void;
22
+ }
23
+ export declare const DrawerProvider: import('react').Provider<DrawerContextValue | null>;
24
+ /**
25
+ * Throws rather than returning a default: a part that cannot find its root is a
26
+ * composition mistake, and a silent fallback would let `OxDrawerExpand` render a
27
+ * button that moves nothing. The message is `core`'s word for word, so the two
28
+ * packages fail identically.
29
+ */
30
+ export declare const useDrawer: () => DrawerContextValue;
@@ -14,6 +14,6 @@ export type { ProgressProps } from './Progress.tsx';
14
14
  * The variant is the caller's to say.
15
15
  */
16
16
  export declare const progressVariants: (props?: ({
17
- variant?: "success" | "error" | "warning" | "brand" | null | undefined;
17
+ variant?: "success" | "brand" | "error" | "warning" | null | undefined;
18
18
  } & import('class-variance-authority/types').ClassProp) | undefined) => string;
19
19
  export type ProgressVariants = VariantProps<typeof progressVariants>;