@exsto/ds 0.0.1-rc.6 → 0.0.1-rc.7
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.
- package/README.md +33 -0
- package/dist/chunks/toggle-C2c5oDaE.js +629 -0
- package/dist/chunks/toggle-C2c5oDaE.js.map +1 -0
- package/dist/ds/src/rich-text-editor.d.ts +8 -0
- package/dist/index.js +3 -470
- package/dist/index.js.map +1 -1
- package/dist/rich-text-editor.js +846 -0
- package/dist/rich-text-editor.js.map +1 -0
- package/dist/styles.css +159 -0
- package/dist/ui/src/components/edyn/rich-text-content/allow-list.d.ts +11 -0
- package/dist/ui/src/components/edyn/rich-text-content/index.d.ts +2 -0
- package/dist/ui/src/components/edyn/rich-text-content/rich-text-content.d.ts +22 -0
- package/dist/ui/src/components/edyn/rich-text-content/rich-text-content.test.d.ts +8 -0
- package/dist/ui/src/components/edyn/rich-text-editor/extensions.d.ts +18 -0
- package/dist/ui/src/components/edyn/rich-text-editor/index.d.ts +3 -0
- package/dist/ui/src/components/edyn/rich-text-editor/mention-list.d.ts +17 -0
- package/dist/ui/src/components/edyn/rich-text-editor/rich-text-editor.d.ts +53 -0
- package/dist/ui/src/components/edyn/rich-text-editor/rich-text-editor.test.d.ts +6 -0
- package/dist/ui/src/components/edyn/rich-text-editor/serialize.d.ts +13 -0
- package/dist/ui/src/components/edyn/rich-text-editor/toolbar.d.ts +20 -0
- package/dist/ui/src/components/edyn/rich-text-editor/types.d.ts +68 -0
- package/dist/ui/src/components/ui/popover/popover.d.ts +1 -1
- package/dist/ui/src/index.d.ts +1 -0
- package/package.json +21 -1
package/README.md
CHANGED
|
@@ -80,9 +80,42 @@ query, so a subtree can be dark inside a light page.
|
|
|
80
80
|
| Import | What it is |
|
|
81
81
|
|---|---|
|
|
82
82
|
| `@exsto/ds` | React components |
|
|
83
|
+
| `@exsto/ds/rich-text-editor` | The rich-text editor, on its own entry so its engine loads only where it is used |
|
|
83
84
|
| `@exsto/ds/styles.css` | Precompiled tokens and component styles |
|
|
84
85
|
| `@exsto/ds/theme.css` | Uncompiled tokens, brand layer, and the Tailwind `@theme` mapping |
|
|
85
86
|
|
|
87
|
+
## Rich text
|
|
88
|
+
|
|
89
|
+
`RichTextEditor` is a small rich-text field: paragraphs, bold, italic, underline, bulleted and numbered lists, links and a horizontal line, with optional `@mentions` that your app searches. It lives on its own entry because it carries an editor engine (about 138 KB gzipped); an app that never imports it never pays for it.
|
|
90
|
+
|
|
91
|
+
```tsx
|
|
92
|
+
import { RichTextContent } from '@exsto/ds';
|
|
93
|
+
import { RichTextEditor } from '@exsto/ds/rich-text-editor';
|
|
94
|
+
|
|
95
|
+
<RichTextEditor
|
|
96
|
+
aria-label="Comment"
|
|
97
|
+
value={html}
|
|
98
|
+
onChange={setHtml} // "" when there is no text
|
|
99
|
+
minRows={3}
|
|
100
|
+
mention={{ search: async (query, { page, signal }) => fetchPeople(query, page, signal) }}
|
|
101
|
+
/>;
|
|
102
|
+
|
|
103
|
+
<RichTextContent html={savedHtml} />;
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
The value is HTML. **The editor emits only these elements and attributes**, whatever is typed or pasted (anything else is dropped on paste and on load, keeping its text), so a server allow-list can be exactly this; the same lists are exported as `RICH_TEXT_ELEMENTS`, `RICH_TEXT_ATTRIBUTES` and `RICH_TEXT_LINK_PROTOCOLS`:
|
|
107
|
+
|
|
108
|
+
| Element | Attributes |
|
|
109
|
+
|---|---|
|
|
110
|
+
| `p`, `br`, `hr`, `strong`, `em`, `u`, `ul`, `ol`, `li` | none (no `class`, no `style`) |
|
|
111
|
+
| `a` | `href`, which must be absolute `http`, `https` or `mailto` |
|
|
112
|
+
|
|
113
|
+
Strong and em, not b and i; no `target` or `rel` on links (add them when you render, as `RichTextContent` does). The editor is a convenience for the writer, not a security boundary: sanitise on the server as well.
|
|
114
|
+
|
|
115
|
+
**Mentions.** Typing `@` opens a list under the caret that calls `mention.search(query, { page, signal })` after a pause (250 ms by default), aborting the previous call. Return an array (one page) or `{ items, hasMore }` (the next page is asked for when the list is scrolled to its end). An item is `{ id, label, secondary? }`: the chip shows `label` while editing, and the HTML carries **plain text, `@${id}` followed by a space, and no element** (`mention.toText` changes that). A value loaded back into the editor shows that text as text, not as a chip. `RichTextContent` marks runs that look like `@person@example.test` and `@assignees` as pills.
|
|
116
|
+
|
|
117
|
+
Also: `ref` gives `insertText(text)` and `focus()` for buttons outside the editor; `fields` adds an "Insert a field" menu that writes `{{id}}`; `maxLength` refuses edits past a number of characters; `onPasteFiles` receives files pasted without any text; `invalid` and `aria-invalid`, `disabled`, `readOnly`, `placeholder`, `labels` (every string) and `toolbar={false}` do what they say. The box is `minRows` lines tall when empty and grows to `maxRows`, then scrolls inside.
|
|
118
|
+
|
|
86
119
|
## Status
|
|
87
120
|
|
|
88
121
|
Release candidates (`0.0.1-rc.N`). The API is not stable yet; treat minor versions
|
|
@@ -0,0 +1,629 @@
|
|
|
1
|
+
import { createElement, isValidElement, useCallback, useEffect, useMemo, useRef } from "react";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Button } from "@base-ui/react/button";
|
|
4
|
+
import { clsx } from "clsx";
|
|
5
|
+
import { twMerge } from "tailwind-merge";
|
|
6
|
+
import { cva } from "class-variance-authority";
|
|
7
|
+
import { IconCheck, IconChevronRight } from "@tabler/icons-react";
|
|
8
|
+
import { Popover } from "@base-ui/react/popover";
|
|
9
|
+
import { ScrollArea } from "@base-ui/react/scroll-area";
|
|
10
|
+
import { Input } from "@base-ui/react/input";
|
|
11
|
+
import { Menu } from "@base-ui/react/menu";
|
|
12
|
+
import { Toggle } from "@base-ui/react/toggle";
|
|
13
|
+
//#region ../ui/src/lib/utils.ts
|
|
14
|
+
var cn = (...inputs) => twMerge(clsx(inputs));
|
|
15
|
+
/**
|
|
16
|
+
* Normalise a size prop to a CSS length.
|
|
17
|
+
*
|
|
18
|
+
* Bare numbers become px, which is also what a bare numeric *string* means: an
|
|
19
|
+
* element attribute arrives as a string, so `width="480"` and `width={480}` have
|
|
20
|
+
* to land on the same value. Anything with a unit, a percentage or a keyword
|
|
21
|
+
* passes through untouched.
|
|
22
|
+
*/
|
|
23
|
+
var cssLength = (value) => {
|
|
24
|
+
if (value == null || value === "") return;
|
|
25
|
+
if (typeof value === "number") return Number.isFinite(value) ? `${value}px` : void 0;
|
|
26
|
+
return /^-?\d*\.?\d+$/.test(value.trim()) ? `${value.trim()}px` : value;
|
|
27
|
+
};
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region ../ui/src/components/ui/button/button.tsx
|
|
30
|
+
var buttonVariants = cva("group/button inline-flex shrink-0 cursor-pointer items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all motion-reduce:transition-none motion-reduce:transform-none outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", {
|
|
31
|
+
variants: {
|
|
32
|
+
variant: {
|
|
33
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/80",
|
|
34
|
+
outline: "border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
|
|
35
|
+
secondary: "bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
|
|
36
|
+
ghost: "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
|
|
37
|
+
destructive: "bg-destructive/10 text-destructive hover:bg-destructive/15 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/15 dark:hover:bg-destructive/20 dark:focus-visible:ring-destructive/40",
|
|
38
|
+
link: "text-primary underline-offset-4 hover:underline"
|
|
39
|
+
},
|
|
40
|
+
size: {
|
|
41
|
+
default: "h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
|
42
|
+
xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
|
|
43
|
+
sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
|
|
44
|
+
lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
|
45
|
+
icon: "size-8",
|
|
46
|
+
"icon-xs": "size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
|
|
47
|
+
"icon-sm": "size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg",
|
|
48
|
+
"icon-lg": "size-9"
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
defaultVariants: {
|
|
52
|
+
variant: "default",
|
|
53
|
+
size: "default"
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
/**
|
|
57
|
+
* Whether this Button is rendering a link rather than a button, so it can keep
|
|
58
|
+
* link semantics. Covers `render={<a href="..." />}` and a routing component
|
|
59
|
+
* that takes an `href` (Next's Link). A component with a different prop for its
|
|
60
|
+
* destination (react-router's `to`) cannot be recognised from here: give those
|
|
61
|
+
* `nativeButton={false} role={undefined}` directly.
|
|
62
|
+
*/
|
|
63
|
+
var rendersALink = (render) => isValidElement(render) && (render.type === "a" || typeof render.props.href === "string");
|
|
64
|
+
/**
|
|
65
|
+
* A link that looks like a button is still a link, and has to be announced as
|
|
66
|
+
* one: it opens in a new tab, it has an address, Enter follows it and Space
|
|
67
|
+
* scrolls the page.
|
|
68
|
+
*
|
|
69
|
+
* Base UI needs telling, in two steps. Rendering an anchor while `nativeButton`
|
|
70
|
+
* is true (its default) warns in development and puts `type="button"` on the
|
|
71
|
+
* anchor; following the warning and passing `nativeButton={false}` then adds
|
|
72
|
+
* `role="button"`, which is what takes the link semantics away. So this does
|
|
73
|
+
* both: non-native, with the role and the tabindex Base UI would add cleared
|
|
74
|
+
* again. An explicit prop from the caller still wins over either.
|
|
75
|
+
*/
|
|
76
|
+
var LINK_PROPS = {
|
|
77
|
+
nativeButton: false,
|
|
78
|
+
role: void 0,
|
|
79
|
+
tabIndex: void 0
|
|
80
|
+
};
|
|
81
|
+
var Button$1 = ({ className, variant = "default", size = "default", ...props }) => {
|
|
82
|
+
return /* @__PURE__ */ jsx(Button, {
|
|
83
|
+
"data-slot": "button",
|
|
84
|
+
...rendersALink(props.render) ? LINK_PROPS : {},
|
|
85
|
+
className: cn(buttonVariants({
|
|
86
|
+
variant,
|
|
87
|
+
size,
|
|
88
|
+
className
|
|
89
|
+
})),
|
|
90
|
+
...props
|
|
91
|
+
});
|
|
92
|
+
};
|
|
93
|
+
//#endregion
|
|
94
|
+
//#region ../ui/src/components/ui/popover/popover.tsx
|
|
95
|
+
var Popover$1 = ({ ...props }) => {
|
|
96
|
+
return /* @__PURE__ */ jsx(Popover.Root, {
|
|
97
|
+
"data-slot": "popover",
|
|
98
|
+
...props
|
|
99
|
+
});
|
|
100
|
+
};
|
|
101
|
+
var PopoverTrigger = ({ ...props }) => {
|
|
102
|
+
return /* @__PURE__ */ jsx(Popover.Trigger, {
|
|
103
|
+
"data-slot": "popover-trigger",
|
|
104
|
+
...props
|
|
105
|
+
});
|
|
106
|
+
};
|
|
107
|
+
var PopoverContent = ({ className, align = "center", alignOffset = 0, side = "bottom", sideOffset = 4, anchor, ...props }) => {
|
|
108
|
+
return /* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsx(Popover.Positioner, {
|
|
109
|
+
align,
|
|
110
|
+
alignOffset,
|
|
111
|
+
anchor,
|
|
112
|
+
side,
|
|
113
|
+
sideOffset,
|
|
114
|
+
className: "isolate z-50",
|
|
115
|
+
children: /* @__PURE__ */ jsx(Popover.Popup, {
|
|
116
|
+
"data-slot": "popover-content",
|
|
117
|
+
className: cn("z-50 flex w-72 origin-(--transform-origin) flex-col gap-2.5 rounded-lg bg-popover p-2.5 text-sm text-popover-foreground shadow-md ring-1 ring-foreground/10 outline-hidden duration-100 motion-reduce:animate-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className),
|
|
118
|
+
...props
|
|
119
|
+
})
|
|
120
|
+
}) });
|
|
121
|
+
};
|
|
122
|
+
var PopoverHeader = ({ className, ...props }) => {
|
|
123
|
+
return /* @__PURE__ */ jsx("div", {
|
|
124
|
+
"data-slot": "popover-header",
|
|
125
|
+
className: cn("flex flex-col gap-0.5 text-sm", className),
|
|
126
|
+
...props
|
|
127
|
+
});
|
|
128
|
+
};
|
|
129
|
+
var PopoverTitle = ({ className, ...props }) => {
|
|
130
|
+
return /* @__PURE__ */ jsx(Popover.Title, {
|
|
131
|
+
"data-slot": "popover-title",
|
|
132
|
+
className: cn("font-medium", className),
|
|
133
|
+
...props
|
|
134
|
+
});
|
|
135
|
+
};
|
|
136
|
+
var PopoverDescription = ({ className, ...props }) => {
|
|
137
|
+
return /* @__PURE__ */ jsx(Popover.Description, {
|
|
138
|
+
"data-slot": "popover-description",
|
|
139
|
+
className: cn("text-muted-foreground", className),
|
|
140
|
+
...props
|
|
141
|
+
});
|
|
142
|
+
};
|
|
143
|
+
//#endregion
|
|
144
|
+
//#region ../ui/src/hooks/use-merged-refs.ts
|
|
145
|
+
var assignRef = (ref, value) => {
|
|
146
|
+
if (typeof ref === "function") ref(value);
|
|
147
|
+
else if (ref !== null) ref.current = value;
|
|
148
|
+
};
|
|
149
|
+
var mergeRefs = (...refs) => {
|
|
150
|
+
return (node) => {
|
|
151
|
+
for (const ref of refs) if (ref) assignRef(ref, node);
|
|
152
|
+
};
|
|
153
|
+
};
|
|
154
|
+
var useMergedRefs = (...refs) => {
|
|
155
|
+
return useCallback(mergeRefs(...refs), [...refs]);
|
|
156
|
+
};
|
|
157
|
+
//#endregion
|
|
158
|
+
//#region ../ui/src/components/ui/scroll-area/scroll-area.tsx
|
|
159
|
+
var ScrollBar = ({ className, orientation = "vertical", visibility = "hover", ...props }) => {
|
|
160
|
+
return /* @__PURE__ */ jsx(ScrollArea.Scrollbar, {
|
|
161
|
+
"data-slot": "scroll-area-scrollbar",
|
|
162
|
+
orientation,
|
|
163
|
+
className: cn("peer/scrollbar z-100 flex touch-none select-none motion-safe:transition-opacity motion-safe:duration-150", orientation === "vertical" && "h-(--scroll-bar-length) w-(--scroll-bar-size) me-(--scroll-bar-gap) mt-(--scroll-bar-offset-top) border-l border-l-transparent p-px", orientation === "horizontal" && "h-(--scroll-bar-size) ms-(--scroll-bar-gap) me-(--scroll-bar-gap) mb-(--scroll-bar-gap) flex-col border-t border-t-transparent p-px", visibility !== "always" && "opacity-0 data-scrolling:opacity-100", visibility === "hover" && "data-hovering:opacity-100 group-hover/scroll-area:opacity-100", className),
|
|
164
|
+
...props,
|
|
165
|
+
children: /* @__PURE__ */ jsx(ScrollArea.Thumb, { className: "relative flex-1 rounded-full bg-muted-foreground/50 transition-colors hover:bg-muted-foreground/70 data-scrolling:bg-muted-foreground/70" })
|
|
166
|
+
});
|
|
167
|
+
};
|
|
168
|
+
/**
|
|
169
|
+
* Which axes fade, published for scroll-fade.css to key its mask off. The
|
|
170
|
+
* stylesheet reaches this element's own viewport through a child combinator, so a
|
|
171
|
+
* ScrollArea nested in another one cannot pick up its ancestor's edges.
|
|
172
|
+
*/
|
|
173
|
+
/**
|
|
174
|
+
* Base UI gives the root, the viewport and the content `role="presentation"`. On
|
|
175
|
+
* a div that changes nothing for a screen reader, but it is an EXPLICIT role, and
|
|
176
|
+
* axe treats explicit presentation children as foreign to a parent that owns
|
|
177
|
+
* required children. A ScrollArea inside `role="menu"` or `role="listbox"` then
|
|
178
|
+
* fails as a critical aria-required-children violation, while the same div with
|
|
179
|
+
* no role passes straight through. Clearing it is what makes a ScrollArea legal
|
|
180
|
+
* in a popup list. A spread because the literal `role={undefined}` is flagged by
|
|
181
|
+
* the a11y lint rule, which cannot tell it from an invalid role.
|
|
182
|
+
*/
|
|
183
|
+
var NO_ROLE = { role: void 0 };
|
|
184
|
+
/** Base UI makes the viewport a tab stop (0, or -1 with nothing to scroll); undefined takes that away. */
|
|
185
|
+
var NO_TAB_STOP = { tabIndex: void 0 };
|
|
186
|
+
var FADE_AXIS = {
|
|
187
|
+
vertical: "block",
|
|
188
|
+
horizontal: "inline",
|
|
189
|
+
both: "both"
|
|
190
|
+
};
|
|
191
|
+
/**
|
|
192
|
+
* Themed scroll container (Base UI): overflowing content scrolls INSIDE the component
|
|
193
|
+
* with a slim overlay scrollbar instead of exposing a native browser scrollbar. House
|
|
194
|
+
* rule: wide/tall content (tables, lists, code) always goes through this, never a bare
|
|
195
|
+
* overflow-* utility.
|
|
196
|
+
*
|
|
197
|
+
* Cap the height with a plain class on the component itself, e.g.
|
|
198
|
+
* `<ScrollArea className="max-h-88">`. The viewport picks the cap up through
|
|
199
|
+
* `max-h-inherit`, which matters because the viewport is the element that actually
|
|
200
|
+
* scrolls: a cap that lands only on the root leaves tall content spilling out
|
|
201
|
+
* underneath with no scrollbar, and that reads as a working height right up until the
|
|
202
|
+
* content outgrows it.
|
|
203
|
+
*
|
|
204
|
+
* `fade` fades out each edge that is currently hiding content, rather than always
|
|
205
|
+
* fading one: a permanent fade is a lie half the time, dimming the last row that fits
|
|
206
|
+
* and promising more below when the reader is already at the end. It is cut into the
|
|
207
|
+
* content by a mask (@edyn/theme/scroll-fade.css), so it needs no surface colour and
|
|
208
|
+
* reads correctly over a tint, a gradient or a glow. A host that keeps something
|
|
209
|
+
* unscrolled at the top, such as a sticky table header, exempts that band by setting
|
|
210
|
+
* --scroll-fade-offset on the ScrollArea. The static faded-* utilities in
|
|
211
|
+
* @edyn/theme/utilities.css are the other tool, for DECORATIVE edges that do not
|
|
212
|
+
* track scroll position.
|
|
213
|
+
*
|
|
214
|
+
* The viewport publishes its size as --scroll-area-width/--scroll-area-height CSS vars
|
|
215
|
+
* so children can size against the visible area.
|
|
216
|
+
*/
|
|
217
|
+
var ScrollArea$1 = ({ className, children, orientation = "vertical", type = "hover", fade = false, fadeFrom = "card", scrollbarClassName, viewportId, viewportRef, viewportClassName, scrollRestorationId, focusable = true, viewPortId, viewPortRef, viewPortClassName, scrollVerticalClassName, scrollHorizontalClassName, ...props }) => {
|
|
218
|
+
const refInner = useRef(null);
|
|
219
|
+
const refsViewport = useMergedRefs(refInner, viewportRef ?? viewPortRef);
|
|
220
|
+
useEffect(() => {
|
|
221
|
+
const el = refInner.current;
|
|
222
|
+
if (!el) return;
|
|
223
|
+
const observer = new ResizeObserver(() => {
|
|
224
|
+
el.style.setProperty("--scroll-area-width", `${el.clientWidth}px`);
|
|
225
|
+
el.style.setProperty("--scroll-area-height", `${el.clientHeight}px`);
|
|
226
|
+
});
|
|
227
|
+
observer.observe(el);
|
|
228
|
+
return () => {
|
|
229
|
+
observer.disconnect();
|
|
230
|
+
};
|
|
231
|
+
}, []);
|
|
232
|
+
return /* @__PURE__ */ jsxs(ScrollArea.Root, {
|
|
233
|
+
"data-slot": "scroll-area",
|
|
234
|
+
"data-focus": "inset",
|
|
235
|
+
className: cn("group/scroll-area relative grid min-w-0 grid-rows-1 overflow-hidden", className),
|
|
236
|
+
"data-fade": fade ? FADE_AXIS[orientation] : void 0,
|
|
237
|
+
...NO_ROLE,
|
|
238
|
+
...props,
|
|
239
|
+
children: [
|
|
240
|
+
(orientation === "vertical" || orientation === "both") && /* @__PURE__ */ jsx(ScrollBar, {
|
|
241
|
+
orientation: "vertical",
|
|
242
|
+
visibility: type,
|
|
243
|
+
className: cn(scrollbarClassName, scrollVerticalClassName)
|
|
244
|
+
}),
|
|
245
|
+
(orientation === "horizontal" || orientation === "both") && /* @__PURE__ */ jsx(ScrollBar, {
|
|
246
|
+
orientation: "horizontal",
|
|
247
|
+
visibility: type,
|
|
248
|
+
className: cn(scrollbarClassName, scrollHorizontalClassName)
|
|
249
|
+
}),
|
|
250
|
+
/* @__PURE__ */ jsx(ScrollArea.Corner, {}),
|
|
251
|
+
/* @__PURE__ */ jsx(ScrollArea.Viewport, {
|
|
252
|
+
"data-slot": "scroll-area-viewport",
|
|
253
|
+
id: viewportId ?? viewPortId,
|
|
254
|
+
ref: refsViewport,
|
|
255
|
+
"data-scroll-restoration-id": scrollRestorationId,
|
|
256
|
+
...NO_ROLE,
|
|
257
|
+
...focusable ? null : NO_TAB_STOP,
|
|
258
|
+
className: cn("max-h-inherit w-full rounded-[inherit]", orientation === "vertical" && "overflow-x-hidden!", orientation === "horizontal" && "overflow-y-hidden!", viewportClassName ?? viewPortClassName),
|
|
259
|
+
children: /* @__PURE__ */ jsx(ScrollArea.Content, {
|
|
260
|
+
...NO_ROLE,
|
|
261
|
+
style: orientation === "vertical" ? { minWidth: 0 } : void 0,
|
|
262
|
+
children
|
|
263
|
+
})
|
|
264
|
+
})
|
|
265
|
+
]
|
|
266
|
+
});
|
|
267
|
+
};
|
|
268
|
+
//#endregion
|
|
269
|
+
//#region ../ui/src/lib/popup-list.ts
|
|
270
|
+
var ITEM_BASE = "relative flex min-h-8 cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm text-foreground outline-none select-none [&>svg]:pointer-events-none [&>svg]:shrink-0 [&>svg]:text-muted-foreground [&>svg:not([class*='size-'])]:size-4";
|
|
271
|
+
var ITEM_STATES = {
|
|
272
|
+
"base-ui": "data-highlighted:bg-foreground/6 data-highlighted:[&>svg]:text-foreground data-[selected]:font-medium data-disabled:pointer-events-none data-disabled:opacity-50",
|
|
273
|
+
cmdk: "data-[selected=true]:bg-foreground/6 data-[selected=true]:[&>svg]:text-foreground data-[checked=true]:font-medium data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50"
|
|
274
|
+
};
|
|
275
|
+
/** The shared item look. `checkable` leaves room at the end for the check. */
|
|
276
|
+
var popupItemClass = (flavor = "base-ui", checkable = false) => cn(ITEM_BASE, ITEM_STATES[flavor], checkable && "pr-8");
|
|
277
|
+
/**
|
|
278
|
+
* The check's slot, at the end of the item and the same size whether or not the
|
|
279
|
+
* check is in it, so a list does not shift as the choice moves.
|
|
280
|
+
*/
|
|
281
|
+
var POPUP_ITEM_INDICATOR_CLASS = "pointer-events-none absolute right-2 flex size-4 items-center justify-center text-foreground";
|
|
282
|
+
/** A group's heading in any of these lists. */
|
|
283
|
+
var POPUP_GROUP_LABEL_CLASS = "px-2 py-1.5 text-xs font-medium text-muted-foreground";
|
|
284
|
+
//#endregion
|
|
285
|
+
//#region ../ui/src/components/edyn/rich-text-content/allow-list.ts
|
|
286
|
+
/**
|
|
287
|
+
* The HTML the rich-text editor emits, and the only HTML RichTextContent renders. An app that
|
|
288
|
+
* sanitises on a server takes its allow-list from here: nothing outside these elements and this
|
|
289
|
+
* attribute ever comes out of the editor, whatever was pasted into it.
|
|
290
|
+
*/
|
|
291
|
+
var RICH_TEXT_ELEMENTS = [
|
|
292
|
+
"p",
|
|
293
|
+
"br",
|
|
294
|
+
"hr",
|
|
295
|
+
"strong",
|
|
296
|
+
"em",
|
|
297
|
+
"u",
|
|
298
|
+
"ul",
|
|
299
|
+
"ol",
|
|
300
|
+
"li",
|
|
301
|
+
"a"
|
|
302
|
+
];
|
|
303
|
+
/** The attributes each element may carry. Every other element carries none (no class, no style). */
|
|
304
|
+
var RICH_TEXT_ATTRIBUTES = { a: ["href"] };
|
|
305
|
+
/** A link must be absolute and use one of these. `javascript:`, `data:` and relative links are refused. */
|
|
306
|
+
var RICH_TEXT_LINK_PROTOCOLS = [
|
|
307
|
+
"http",
|
|
308
|
+
"https",
|
|
309
|
+
"mailto"
|
|
310
|
+
];
|
|
311
|
+
var isAllowedRichTextHref = (href) => {
|
|
312
|
+
const cleaned = Array.from(href).filter((char) => char.charCodeAt(0) > 32).join("");
|
|
313
|
+
const colon = cleaned.indexOf(":");
|
|
314
|
+
if (colon < 1) return false;
|
|
315
|
+
const protocol = cleaned.slice(0, colon).toLowerCase();
|
|
316
|
+
return RICH_TEXT_LINK_PROTOCOLS.includes(protocol);
|
|
317
|
+
};
|
|
318
|
+
//#endregion
|
|
319
|
+
//#region ../ui/src/components/edyn/rich-text-content/rich-text-content.tsx
|
|
320
|
+
/**
|
|
321
|
+
* How rich text looks, shared by the editor and by RichTextContent so a comment reads the same while it is typed and
|
|
322
|
+
* after it is saved. Descendant selectors rather than classes on the elements, because the HTML that is stored carries
|
|
323
|
+
* no classes.
|
|
324
|
+
*/
|
|
325
|
+
var RICH_TEXT_BODY_CLASS = "text-sm leading-6 wrap-break-word [&_a]:text-primary [&_a]:underline [&_a]:underline-offset-2 [&_hr]:my-3 [&_hr]:border-0 [&_hr]:border-t [&_hr]:border-border [&_ol]:list-decimal [&_ol]:ps-5 [&_p+p]:mt-2 [&_ul]:list-disc [&_ul]:ps-5 [&_ul+p]:mt-2 [&_ol+p]:mt-2 [&_p+ul]:mt-2 [&_p+ol]:mt-2";
|
|
326
|
+
var MENTION_PATTERN = /(?<![\w.+-])@(?:assignees\b|[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+)/g;
|
|
327
|
+
var RICH_TEXT_MENTION_CLASS = "rounded-sm bg-primary/10 px-1 py-0.5 font-medium text-primary";
|
|
328
|
+
var TAG_FOR = {
|
|
329
|
+
b: "strong",
|
|
330
|
+
i: "em"
|
|
331
|
+
};
|
|
332
|
+
var KEPT = /* @__PURE__ */ new Set([
|
|
333
|
+
"p",
|
|
334
|
+
"strong",
|
|
335
|
+
"em",
|
|
336
|
+
"u",
|
|
337
|
+
"ul",
|
|
338
|
+
"ol",
|
|
339
|
+
"li",
|
|
340
|
+
"a",
|
|
341
|
+
"br",
|
|
342
|
+
"hr"
|
|
343
|
+
]);
|
|
344
|
+
var DROPPED_WITH_CONTENT = /* @__PURE__ */ new Set([
|
|
345
|
+
"script",
|
|
346
|
+
"style",
|
|
347
|
+
"template",
|
|
348
|
+
"iframe",
|
|
349
|
+
"object",
|
|
350
|
+
"embed",
|
|
351
|
+
"noscript",
|
|
352
|
+
"svg",
|
|
353
|
+
"math",
|
|
354
|
+
"head",
|
|
355
|
+
"title"
|
|
356
|
+
]);
|
|
357
|
+
var withMentions = (text, key) => {
|
|
358
|
+
const parts = [];
|
|
359
|
+
let last = 0;
|
|
360
|
+
for (const match of text.matchAll(MENTION_PATTERN)) {
|
|
361
|
+
const start = match.index ?? 0;
|
|
362
|
+
if (start > last) parts.push(text.slice(last, start));
|
|
363
|
+
parts.push(/* @__PURE__ */ jsx("span", {
|
|
364
|
+
"data-slot": "rich-text-mention",
|
|
365
|
+
className: RICH_TEXT_MENTION_CLASS,
|
|
366
|
+
children: match[0]
|
|
367
|
+
}, `${key}-${start}`));
|
|
368
|
+
last = start + match[0].length;
|
|
369
|
+
}
|
|
370
|
+
if (last < text.length) parts.push(text.slice(last));
|
|
371
|
+
return parts;
|
|
372
|
+
};
|
|
373
|
+
var toNodes = (parent, key, insideLink) => {
|
|
374
|
+
const out = [];
|
|
375
|
+
parent.childNodes.forEach((child, index) => {
|
|
376
|
+
const childKey = `${key}.${index}`;
|
|
377
|
+
if (child.nodeType === 3) {
|
|
378
|
+
const text = child.textContent ?? "";
|
|
379
|
+
out.push(...insideLink ? [text] : withMentions(text, childKey));
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
if (child.nodeType !== 1) return;
|
|
383
|
+
const element = child;
|
|
384
|
+
const name = element.tagName.toLowerCase();
|
|
385
|
+
if (DROPPED_WITH_CONTENT.has(name)) return;
|
|
386
|
+
const tag = TAG_FOR[name] ?? name;
|
|
387
|
+
if (!KEPT.has(tag)) {
|
|
388
|
+
out.push(...toNodes(element, childKey, insideLink));
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
if (tag === "br") {
|
|
392
|
+
out.push(/* @__PURE__ */ jsx("br", {}, childKey));
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
if (tag === "hr") {
|
|
396
|
+
out.push(/* @__PURE__ */ jsx("hr", {}, childKey));
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
if (tag === "a") {
|
|
400
|
+
const href = element.getAttribute("href") ?? "";
|
|
401
|
+
const inner = toNodes(element, childKey, true);
|
|
402
|
+
out.push(...isAllowedRichTextHref(href) ? [/* @__PURE__ */ jsx("a", {
|
|
403
|
+
href: href.trim(),
|
|
404
|
+
rel: "noopener noreferrer",
|
|
405
|
+
target: "_blank",
|
|
406
|
+
children: inner
|
|
407
|
+
}, childKey)] : inner);
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
out.push(createElement(tag, { key: childKey }, ...toNodes(element, childKey, insideLink)));
|
|
411
|
+
});
|
|
412
|
+
return out;
|
|
413
|
+
};
|
|
414
|
+
/** The HTML as React elements, keeping only what the editor can produce. Browser only (it needs a DOM parser). */
|
|
415
|
+
var richTextToNodes = (html) => {
|
|
416
|
+
if (typeof DOMParser === "undefined" || html === "") return [];
|
|
417
|
+
return toNodes(new DOMParser().parseFromString(`<body>${html}`, "text/html").body, "r", false);
|
|
418
|
+
};
|
|
419
|
+
/**
|
|
420
|
+
* Shows rich text the editor produced (or any HTML), as the same elements the editor emits and nothing else:
|
|
421
|
+
* `p`, `br`, `hr`, `strong`, `em`, `u`, `ul`, `ol`, `li` and `a` with an `href` (http, https or mailto; it opens in a new tab
|
|
422
|
+
* with `rel="noopener noreferrer"`). Everything else is dropped and its text kept. Built as React elements, not injected
|
|
423
|
+
* as markup, so it is safe on HTML that has not been cleaned. A run like `@name@example.test` or `@assignees` is shown
|
|
424
|
+
* as a chip.
|
|
425
|
+
*/
|
|
426
|
+
var RichTextContent = ({ html, className, ...props }) => {
|
|
427
|
+
const nodes = useMemo(() => richTextToNodes(html), [html]);
|
|
428
|
+
return /* @__PURE__ */ jsx("div", {
|
|
429
|
+
"data-slot": "rich-text-content",
|
|
430
|
+
className: cn(RICH_TEXT_BODY_CLASS, className),
|
|
431
|
+
...props,
|
|
432
|
+
children: nodes
|
|
433
|
+
});
|
|
434
|
+
};
|
|
435
|
+
//#endregion
|
|
436
|
+
//#region ../ui/src/components/ui/input/input.tsx
|
|
437
|
+
var Input$1 = ({ className, type, ...props }) => {
|
|
438
|
+
return /* @__PURE__ */ jsx(Input, {
|
|
439
|
+
type,
|
|
440
|
+
"data-slot": "input",
|
|
441
|
+
className: cn("h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base transition-colors outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40", className),
|
|
442
|
+
...props
|
|
443
|
+
});
|
|
444
|
+
};
|
|
445
|
+
//#endregion
|
|
446
|
+
//#region ../ui/src/components/ui/dropdown-menu/dropdown-menu.tsx
|
|
447
|
+
var DropdownMenu = ({ ...props }) => {
|
|
448
|
+
return /* @__PURE__ */ jsx(Menu.Root, {
|
|
449
|
+
"data-slot": "dropdown-menu",
|
|
450
|
+
...props
|
|
451
|
+
});
|
|
452
|
+
};
|
|
453
|
+
var DropdownMenuPortal = ({ ...props }) => {
|
|
454
|
+
return /* @__PURE__ */ jsx(Menu.Portal, {
|
|
455
|
+
"data-slot": "dropdown-menu-portal",
|
|
456
|
+
...props
|
|
457
|
+
});
|
|
458
|
+
};
|
|
459
|
+
var DropdownMenuTrigger = ({ ...props }) => {
|
|
460
|
+
return /* @__PURE__ */ jsx(Menu.Trigger, {
|
|
461
|
+
"data-slot": "dropdown-menu-trigger",
|
|
462
|
+
...props
|
|
463
|
+
});
|
|
464
|
+
};
|
|
465
|
+
/**
|
|
466
|
+
* Scrolls inside a DS ScrollArea like the other popup lists, with the bar shown
|
|
467
|
+
* for as long as the menu overflows and a fade on whichever end is cut off.
|
|
468
|
+
*
|
|
469
|
+
* Base UI puts `role="menu"` on this popup itself and offers no inner list part
|
|
470
|
+
* to move it onto, so the ScrollArea sits INSIDE the role. That is only legal
|
|
471
|
+
* because ScrollArea drops the `role="presentation"` Base UI gives its root,
|
|
472
|
+
* viewport and content: a menu may not own presentation wrappers (axe reports
|
|
473
|
+
* a critical aria-required-children violation), but it may own plain divs.
|
|
474
|
+
* e2e/a11y.spec.ts scans the open menu in both themes to keep it that way.
|
|
475
|
+
*/
|
|
476
|
+
var DropdownMenuContent = ({ align = "start", alignOffset = 0, side = "bottom", sideOffset = 4, className, children, ...props }) => {
|
|
477
|
+
return /* @__PURE__ */ jsx(Menu.Portal, { children: /* @__PURE__ */ jsx(Menu.Positioner, {
|
|
478
|
+
className: "isolate z-50 outline-none",
|
|
479
|
+
align,
|
|
480
|
+
alignOffset,
|
|
481
|
+
side,
|
|
482
|
+
sideOffset,
|
|
483
|
+
children: /* @__PURE__ */ jsx(Menu.Popup, {
|
|
484
|
+
"data-slot": "dropdown-menu-content",
|
|
485
|
+
className: cn("z-50 max-h-(--available-height) w-(--anchor-width) min-w-32 origin-(--transform-origin) overflow-hidden rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 outline-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:overflow-hidden data-closed:fade-out-0 data-closed:zoom-out-95", "motion-reduce:animate-none", className),
|
|
486
|
+
...props,
|
|
487
|
+
children: /* @__PURE__ */ jsx(ScrollArea$1, {
|
|
488
|
+
type: "always",
|
|
489
|
+
fade: true,
|
|
490
|
+
focusable: false,
|
|
491
|
+
className: "max-h-inherit",
|
|
492
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
493
|
+
className: "p-1",
|
|
494
|
+
children
|
|
495
|
+
})
|
|
496
|
+
})
|
|
497
|
+
})
|
|
498
|
+
}) });
|
|
499
|
+
};
|
|
500
|
+
var DropdownMenuGroup = ({ ...props }) => {
|
|
501
|
+
return /* @__PURE__ */ jsx(Menu.Group, {
|
|
502
|
+
"data-slot": "dropdown-menu-group",
|
|
503
|
+
...props
|
|
504
|
+
});
|
|
505
|
+
};
|
|
506
|
+
var DropdownMenuLabel = ({ className, inset, ...props }) => {
|
|
507
|
+
return /* @__PURE__ */ jsx(Menu.GroupLabel, {
|
|
508
|
+
"data-slot": "dropdown-menu-label",
|
|
509
|
+
"data-inset": inset,
|
|
510
|
+
className: cn(POPUP_GROUP_LABEL_CLASS, "data-[inset=true]:pl-7", className),
|
|
511
|
+
...props
|
|
512
|
+
});
|
|
513
|
+
};
|
|
514
|
+
var DropdownMenuItem = ({ className, inset, variant = "default", ...props }) => {
|
|
515
|
+
return /* @__PURE__ */ jsx(Menu.Item, {
|
|
516
|
+
"data-slot": "dropdown-menu-item",
|
|
517
|
+
"data-inset": inset,
|
|
518
|
+
"data-variant": variant,
|
|
519
|
+
className: cn("group/dropdown-menu-item", popupItemClass(), "data-[inset=true]:pl-7", "data-[variant=destructive]:text-destructive data-[variant=destructive]:[&>svg]:text-destructive", "data-[variant=destructive]:data-highlighted:bg-destructive/10 data-[variant=destructive]:data-highlighted:text-destructive", className),
|
|
520
|
+
...props
|
|
521
|
+
});
|
|
522
|
+
};
|
|
523
|
+
var DropdownMenuSub = ({ ...props }) => {
|
|
524
|
+
return /* @__PURE__ */ jsx(Menu.SubmenuRoot, {
|
|
525
|
+
"data-slot": "dropdown-menu-sub",
|
|
526
|
+
...props
|
|
527
|
+
});
|
|
528
|
+
};
|
|
529
|
+
var DropdownMenuSubTrigger = ({ className, inset, children, ...props }) => {
|
|
530
|
+
return /* @__PURE__ */ jsxs(Menu.SubmenuTrigger, {
|
|
531
|
+
"data-slot": "dropdown-menu-sub-trigger",
|
|
532
|
+
"data-inset": inset,
|
|
533
|
+
className: cn(popupItemClass(), "data-[inset=true]:pl-7 data-popup-open:bg-foreground/6 data-popup-open:[&>svg]:text-foreground", className),
|
|
534
|
+
...props,
|
|
535
|
+
children: [children, /* @__PURE__ */ jsx(IconChevronRight, { className: "ml-auto" })]
|
|
536
|
+
});
|
|
537
|
+
};
|
|
538
|
+
var DropdownMenuSubContent = ({ align = "start", alignOffset = -3, side = "right", sideOffset = 0, className, ...props }) => {
|
|
539
|
+
return /* @__PURE__ */ jsx(DropdownMenuContent, {
|
|
540
|
+
"data-slot": "dropdown-menu-sub-content",
|
|
541
|
+
className: cn("w-auto min-w-[96px] rounded-lg bg-popover p-1 text-popover-foreground shadow-lg ring-1 ring-foreground/10 duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", "motion-reduce:animate-none", className),
|
|
542
|
+
align,
|
|
543
|
+
alignOffset,
|
|
544
|
+
side,
|
|
545
|
+
sideOffset,
|
|
546
|
+
...props
|
|
547
|
+
});
|
|
548
|
+
};
|
|
549
|
+
var DropdownMenuCheckboxItem = ({ className, children, checked, inset, ...props }) => {
|
|
550
|
+
return /* @__PURE__ */ jsxs(Menu.CheckboxItem, {
|
|
551
|
+
"data-slot": "dropdown-menu-checkbox-item",
|
|
552
|
+
"data-inset": inset,
|
|
553
|
+
className: cn(popupItemClass("base-ui", true), "data-checked:font-medium data-[inset=true]:pl-7", className),
|
|
554
|
+
checked,
|
|
555
|
+
...props,
|
|
556
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
557
|
+
className: POPUP_ITEM_INDICATOR_CLASS,
|
|
558
|
+
"data-slot": "dropdown-menu-checkbox-item-indicator",
|
|
559
|
+
children: /* @__PURE__ */ jsx(Menu.CheckboxItemIndicator, { children: /* @__PURE__ */ jsx(IconCheck, {}) })
|
|
560
|
+
}), children]
|
|
561
|
+
});
|
|
562
|
+
};
|
|
563
|
+
var DropdownMenuRadioGroup = ({ ...props }) => {
|
|
564
|
+
return /* @__PURE__ */ jsx(Menu.RadioGroup, {
|
|
565
|
+
"data-slot": "dropdown-menu-radio-group",
|
|
566
|
+
...props
|
|
567
|
+
});
|
|
568
|
+
};
|
|
569
|
+
var DropdownMenuRadioItem = ({ className, children, inset, ...props }) => {
|
|
570
|
+
return /* @__PURE__ */ jsxs(Menu.RadioItem, {
|
|
571
|
+
"data-slot": "dropdown-menu-radio-item",
|
|
572
|
+
"data-inset": inset,
|
|
573
|
+
className: cn(popupItemClass("base-ui", true), "data-checked:font-medium data-[inset=true]:pl-7", className),
|
|
574
|
+
...props,
|
|
575
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
576
|
+
className: POPUP_ITEM_INDICATOR_CLASS,
|
|
577
|
+
"data-slot": "dropdown-menu-radio-item-indicator",
|
|
578
|
+
children: /* @__PURE__ */ jsx(Menu.RadioItemIndicator, { children: /* @__PURE__ */ jsx(IconCheck, {}) })
|
|
579
|
+
}), children]
|
|
580
|
+
});
|
|
581
|
+
};
|
|
582
|
+
var DropdownMenuSeparator = ({ className, ...props }) => {
|
|
583
|
+
return /* @__PURE__ */ jsx(Menu.Separator, {
|
|
584
|
+
"data-slot": "dropdown-menu-separator",
|
|
585
|
+
className: cn("-mx-1 my-1 h-px bg-border", className),
|
|
586
|
+
...props
|
|
587
|
+
});
|
|
588
|
+
};
|
|
589
|
+
var DropdownMenuShortcut = ({ className, ...props }) => {
|
|
590
|
+
return /* @__PURE__ */ jsx("span", {
|
|
591
|
+
"data-slot": "dropdown-menu-shortcut",
|
|
592
|
+
className: cn("ml-auto text-xs tracking-widest text-muted-foreground group-focus/dropdown-menu-item:text-accent-foreground", className),
|
|
593
|
+
...props
|
|
594
|
+
});
|
|
595
|
+
};
|
|
596
|
+
//#endregion
|
|
597
|
+
//#region ../ui/src/components/ui/toggle/toggle.tsx
|
|
598
|
+
var toggleVariants = cva("inline-flex shrink-0 cursor-pointer items-center justify-center gap-1.5 rounded-lg border border-transparent text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 data-pressed:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\"size-\"])]:size-4", {
|
|
599
|
+
variants: {
|
|
600
|
+
variant: {
|
|
601
|
+
default: "bg-transparent hover:bg-muted hover:text-foreground data-pressed:bg-muted",
|
|
602
|
+
outline: "border-border bg-background hover:bg-muted hover:text-foreground data-pressed:bg-muted dark:border-input dark:bg-input/30 dark:hover:bg-input/50"
|
|
603
|
+
},
|
|
604
|
+
size: {
|
|
605
|
+
sm: "h-7 px-2.5 text-[0.8rem]",
|
|
606
|
+
default: "h-8 px-2.5",
|
|
607
|
+
lg: "h-9 px-3"
|
|
608
|
+
}
|
|
609
|
+
},
|
|
610
|
+
defaultVariants: {
|
|
611
|
+
variant: "default",
|
|
612
|
+
size: "default"
|
|
613
|
+
}
|
|
614
|
+
});
|
|
615
|
+
var Toggle$1 = ({ className, variant = "default", size = "default", ...props }) => {
|
|
616
|
+
return /* @__PURE__ */ jsx(Toggle, {
|
|
617
|
+
"data-slot": "toggle",
|
|
618
|
+
className: cn(toggleVariants({
|
|
619
|
+
variant,
|
|
620
|
+
size,
|
|
621
|
+
className
|
|
622
|
+
})),
|
|
623
|
+
...props
|
|
624
|
+
});
|
|
625
|
+
};
|
|
626
|
+
//#endregion
|
|
627
|
+
export { ScrollBar as A, cn as B, RICH_TEXT_ATTRIBUTES as C, POPUP_ITEM_INDICATOR_CLASS as D, isAllowedRichTextHref as E, PopoverHeader as F, PopoverTitle as I, PopoverTrigger as L, Popover$1 as M, PopoverContent as N, popupItemClass as O, PopoverDescription as P, Button$1 as R, richTextToNodes as S, RICH_TEXT_LINK_PROTOCOLS as T, cssLength as V, DropdownMenuTrigger as _, DropdownMenuContent as a, RICH_TEXT_MENTION_CLASS as b, DropdownMenuLabel as c, DropdownMenuRadioItem as d, DropdownMenuSeparator as f, DropdownMenuSubTrigger as g, DropdownMenuSubContent as h, DropdownMenuCheckboxItem as i, useMergedRefs as j, ScrollArea$1 as k, DropdownMenuPortal as l, DropdownMenuSub as m, toggleVariants as n, DropdownMenuGroup as o, DropdownMenuShortcut as p, DropdownMenu as r, DropdownMenuItem as s, Toggle$1 as t, DropdownMenuRadioGroup as u, Input$1 as v, RICH_TEXT_ELEMENTS as w, RichTextContent as x, RICH_TEXT_BODY_CLASS as y, buttonVariants as z };
|
|
628
|
+
|
|
629
|
+
//# sourceMappingURL=toggle-C2c5oDaE.js.map
|