@modern-admin/ui 0.1.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.
- package/dist/components/accordion.d.ts +7 -0
- package/dist/components/accordion.d.ts.map +1 -0
- package/dist/components/accordion.jsx +19 -0
- package/dist/components/accordion.jsx.map +1 -0
- package/dist/components/alert-dialog.d.ts +22 -0
- package/dist/components/alert-dialog.d.ts.map +1 -0
- package/dist/components/alert-dialog.jsx +27 -0
- package/dist/components/alert-dialog.jsx.map +1 -0
- package/dist/components/audit-timeline.d.ts +24 -0
- package/dist/components/audit-timeline.d.ts.map +1 -0
- package/dist/components/audit-timeline.jsx +60 -0
- package/dist/components/audit-timeline.jsx.map +1 -0
- package/dist/components/avatar.d.ts +6 -0
- package/dist/components/avatar.d.ts.map +1 -0
- package/dist/components/avatar.jsx +10 -0
- package/dist/components/avatar.jsx.map +1 -0
- package/dist/components/badge.d.ts +10 -0
- package/dist/components/badge.d.ts.map +1 -0
- package/dist/components/badge.jsx +19 -0
- package/dist/components/badge.jsx.map +1 -0
- package/dist/components/breadcrumb.d.ts +17 -0
- package/dist/components/breadcrumb.d.ts.map +1 -0
- package/dist/components/breadcrumb.jsx +27 -0
- package/dist/components/breadcrumb.jsx.map +1 -0
- package/dist/components/button.d.ts +12 -0
- package/dist/components/button.d.ts.map +1 -0
- package/dist/components/button.jsx +37 -0
- package/dist/components/button.jsx.map +1 -0
- package/dist/components/calendar.d.ts +9 -0
- package/dist/components/calendar.d.ts.map +1 -0
- package/dist/components/calendar.jsx +102 -0
- package/dist/components/calendar.jsx.map +1 -0
- package/dist/components/card.d.ts +8 -0
- package/dist/components/card.d.ts.map +1 -0
- package/dist/components/card.jsx +18 -0
- package/dist/components/card.jsx.map +1 -0
- package/dist/components/chart.d.ts +97 -0
- package/dist/components/chart.d.ts.map +1 -0
- package/dist/components/chart.jsx +233 -0
- package/dist/components/chart.jsx.map +1 -0
- package/dist/components/checkbox.d.ts +4 -0
- package/dist/components/checkbox.d.ts.map +1 -0
- package/dist/components/checkbox.jsx +11 -0
- package/dist/components/checkbox.jsx.map +1 -0
- package/dist/components/combobox.d.ts +46 -0
- package/dist/components/combobox.d.ts.map +1 -0
- package/dist/components/combobox.jsx +145 -0
- package/dist/components/combobox.jsx.map +1 -0
- package/dist/components/command.d.ts +80 -0
- package/dist/components/command.d.ts.map +1 -0
- package/dist/components/command.jsx +32 -0
- package/dist/components/command.jsx.map +1 -0
- package/dist/components/date-picker.d.ts +24 -0
- package/dist/components/date-picker.d.ts.map +1 -0
- package/dist/components/date-picker.jsx +149 -0
- package/dist/components/date-picker.jsx.map +1 -0
- package/dist/components/date-range-input.d.ts +22 -0
- package/dist/components/date-range-input.d.ts.map +1 -0
- package/dist/components/date-range-input.jsx +202 -0
- package/dist/components/date-range-input.jsx.map +1 -0
- package/dist/components/dialog.d.ts +19 -0
- package/dist/components/dialog.d.ts.map +1 -0
- package/dist/components/dialog.jsx +30 -0
- package/dist/components/dialog.jsx.map +1 -0
- package/dist/components/diff-view.d.ts +24 -0
- package/dist/components/diff-view.d.ts.map +1 -0
- package/dist/components/diff-view.jsx +69 -0
- package/dist/components/diff-view.jsx.map +1 -0
- package/dist/components/dropdown-menu.d.ts +27 -0
- package/dist/components/dropdown-menu.d.ts.map +1 -0
- package/dist/components/dropdown-menu.jsx +48 -0
- package/dist/components/dropdown-menu.jsx.map +1 -0
- package/dist/components/empty.d.ts +15 -0
- package/dist/components/empty.d.ts.map +1 -0
- package/dist/components/empty.jsx +27 -0
- package/dist/components/empty.jsx.map +1 -0
- package/dist/components/field.d.ts +23 -0
- package/dist/components/field.d.ts.map +1 -0
- package/dist/components/field.jsx +60 -0
- package/dist/components/field.jsx.map +1 -0
- package/dist/components/file-input.d.ts +50 -0
- package/dist/components/file-input.d.ts.map +1 -0
- package/dist/components/file-input.jsx +104 -0
- package/dist/components/file-input.jsx.map +1 -0
- package/dist/components/form.d.ts +20 -0
- package/dist/components/form.d.ts.map +1 -0
- package/dist/components/form.jsx +66 -0
- package/dist/components/form.jsx.map +1 -0
- package/dist/components/info-tooltip.d.ts +11 -0
- package/dist/components/info-tooltip.d.ts.map +1 -0
- package/dist/components/info-tooltip.jsx +17 -0
- package/dist/components/info-tooltip.jsx.map +1 -0
- package/dist/components/input.d.ts +13 -0
- package/dist/components/input.d.ts.map +1 -0
- package/dist/components/input.jsx +19 -0
- package/dist/components/input.jsx.map +1 -0
- package/dist/components/json-editor.d.ts +23 -0
- package/dist/components/json-editor.d.ts.map +1 -0
- package/dist/components/json-editor.jsx +143 -0
- package/dist/components/json-editor.jsx.map +1 -0
- package/dist/components/kbd.d.ts +15 -0
- package/dist/components/kbd.d.ts.map +1 -0
- package/dist/components/kbd.jsx +23 -0
- package/dist/components/kbd.jsx.map +1 -0
- package/dist/components/key-value-editor.d.ts +92 -0
- package/dist/components/key-value-editor.d.ts.map +1 -0
- package/dist/components/key-value-editor.jsx +187 -0
- package/dist/components/key-value-editor.jsx.map +1 -0
- package/dist/components/keyboard-shortcuts-help.d.ts +17 -0
- package/dist/components/keyboard-shortcuts-help.d.ts.map +1 -0
- package/dist/components/keyboard-shortcuts-help.jsx +97 -0
- package/dist/components/keyboard-shortcuts-help.jsx.map +1 -0
- package/dist/components/label.d.ts +5 -0
- package/dist/components/label.d.ts.map +1 -0
- package/dist/components/label.jsx +8 -0
- package/dist/components/label.jsx.map +1 -0
- package/dist/components/media-preview.d.ts +30 -0
- package/dist/components/media-preview.d.ts.map +1 -0
- package/dist/components/media-preview.jsx +189 -0
- package/dist/components/media-preview.jsx.map +1 -0
- package/dist/components/multi-file-input.d.ts +76 -0
- package/dist/components/multi-file-input.d.ts.map +1 -0
- package/dist/components/multi-file-input.jsx +131 -0
- package/dist/components/multi-file-input.jsx.map +1 -0
- package/dist/components/password-input.d.ts +10 -0
- package/dist/components/password-input.d.ts.map +1 -0
- package/dist/components/password-input.jsx +18 -0
- package/dist/components/password-input.jsx.map +1 -0
- package/dist/components/popover.d.ts +7 -0
- package/dist/components/popover.d.ts.map +1 -0
- package/dist/components/popover.jsx +11 -0
- package/dist/components/popover.jsx.map +1 -0
- package/dist/components/revision-timeline.d.ts +30 -0
- package/dist/components/revision-timeline.d.ts.map +1 -0
- package/dist/components/revision-timeline.jsx +42 -0
- package/dist/components/revision-timeline.jsx.map +1 -0
- package/dist/components/richtext-editor.d.ts +43 -0
- package/dist/components/richtext-editor.d.ts.map +1 -0
- package/dist/components/richtext-editor.jsx +319 -0
- package/dist/components/richtext-editor.jsx.map +1 -0
- package/dist/components/richtext-mode.d.ts +23 -0
- package/dist/components/richtext-mode.d.ts.map +1 -0
- package/dist/components/richtext-mode.js +36 -0
- package/dist/components/richtext-mode.js.map +1 -0
- package/dist/components/richtext-render.d.ts +8 -0
- package/dist/components/richtext-render.d.ts.map +1 -0
- package/dist/components/richtext-render.jsx +33 -0
- package/dist/components/richtext-render.jsx.map +1 -0
- package/dist/components/richtext-sync.d.ts +37 -0
- package/dist/components/richtext-sync.d.ts.map +1 -0
- package/dist/components/richtext-sync.js +46 -0
- package/dist/components/richtext-sync.js.map +1 -0
- package/dist/components/scroll-area.d.ts +5 -0
- package/dist/components/scroll-area.d.ts.map +1 -0
- package/dist/components/scroll-area.jsx +16 -0
- package/dist/components/scroll-area.jsx.map +1 -0
- package/dist/components/select.d.ts +36 -0
- package/dist/components/select.d.ts.map +1 -0
- package/dist/components/select.jsx +87 -0
- package/dist/components/select.jsx.map +1 -0
- package/dist/components/separator.d.ts +4 -0
- package/dist/components/separator.d.ts.map +1 -0
- package/dist/components/separator.jsx +6 -0
- package/dist/components/separator.jsx.map +1 -0
- package/dist/components/sheet.d.ts +29 -0
- package/dist/components/sheet.d.ts.map +1 -0
- package/dist/components/sheet.jsx +44 -0
- package/dist/components/sheet.jsx.map +1 -0
- package/dist/components/sidebar.d.ts +70 -0
- package/dist/components/sidebar.d.ts.map +1 -0
- package/dist/components/sidebar.jsx +245 -0
- package/dist/components/sidebar.jsx.map +1 -0
- package/dist/components/skeleton.d.ts +3 -0
- package/dist/components/skeleton.d.ts.map +1 -0
- package/dist/components/skeleton.jsx +6 -0
- package/dist/components/skeleton.jsx.map +1 -0
- package/dist/components/sonner.d.ts +6 -0
- package/dist/components/sonner.d.ts.map +1 -0
- package/dist/components/sonner.jsx +29 -0
- package/dist/components/sonner.jsx.map +1 -0
- package/dist/components/switch.d.ts +4 -0
- package/dist/components/switch.d.ts.map +1 -0
- package/dist/components/switch.jsx +8 -0
- package/dist/components/switch.jsx.map +1 -0
- package/dist/components/table.d.ts +10 -0
- package/dist/components/table.d.ts.map +1 -0
- package/dist/components/table.jsx +21 -0
- package/dist/components/table.jsx.map +1 -0
- package/dist/components/tabs.d.ts +7 -0
- package/dist/components/tabs.d.ts.map +1 -0
- package/dist/components/tabs.jsx +14 -0
- package/dist/components/tabs.jsx.map +1 -0
- package/dist/components/textarea.d.ts +4 -0
- package/dist/components/textarea.d.ts.map +1 -0
- package/dist/components/textarea.jsx +5 -0
- package/dist/components/textarea.jsx.map +1 -0
- package/dist/components/tooltip.d.ts +7 -0
- package/dist/components/tooltip.d.ts.map +1 -0
- package/dist/components/tooltip.jsx +11 -0
- package/dist/components/tooltip.jsx.map +1 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +72 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/theme.d.ts +11 -0
- package/dist/lib/theme.d.ts.map +1 -0
- package/dist/lib/theme.js +44 -0
- package/dist/lib/theme.js.map +1 -0
- package/dist/lib/utils.d.ts +3 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +6 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/styles.css +242 -0
- package/package.json +85 -0
- package/src/components/accordion.tsx +48 -0
- package/src/components/alert-dialog.tsx +113 -0
- package/src/components/audit-timeline.tsx +102 -0
- package/src/components/avatar.tsx +42 -0
- package/src/components/badge.tsx +34 -0
- package/src/components/breadcrumb.tsx +99 -0
- package/src/components/button.tsx +58 -0
- package/src/components/calendar.tsx +176 -0
- package/src/components/card.tsx +60 -0
- package/src/components/chart.tsx +558 -0
- package/src/components/checkbox.tsx +23 -0
- package/src/components/combobox.tsx +264 -0
- package/src/components/command.tsx +120 -0
- package/src/components/date-picker.tsx +221 -0
- package/src/components/date-range-input.tsx +295 -0
- package/src/components/dialog.tsx +94 -0
- package/src/components/diff-view.tsx +182 -0
- package/src/components/dropdown-menu.tsx +165 -0
- package/src/components/empty.tsx +100 -0
- package/src/components/field.tsx +168 -0
- package/src/components/file-input.tsx +233 -0
- package/src/components/form.tsx +152 -0
- package/src/components/info-tooltip.tsx +40 -0
- package/src/components/input.tsx +55 -0
- package/src/components/json-editor.tsx +210 -0
- package/src/components/kbd.tsx +35 -0
- package/src/components/key-value-editor.tsx +423 -0
- package/src/components/keyboard-shortcuts-help.tsx +136 -0
- package/src/components/label.tsx +16 -0
- package/src/components/media-preview.tsx +278 -0
- package/src/components/multi-file-input.tsx +315 -0
- package/src/components/password-input.tsx +50 -0
- package/src/components/popover.tsx +26 -0
- package/src/components/revision-timeline.tsx +93 -0
- package/src/components/richtext-editor.tsx +624 -0
- package/src/components/richtext-mode.ts +39 -0
- package/src/components/richtext-render.tsx +51 -0
- package/src/components/richtext-sync.ts +57 -0
- package/src/components/scroll-area.tsx +41 -0
- package/src/components/select.tsx +200 -0
- package/src/components/separator.tsx +21 -0
- package/src/components/sheet.tsx +109 -0
- package/src/components/sidebar.tsx +660 -0
- package/src/components/skeleton.tsx +9 -0
- package/src/components/sonner.tsx +45 -0
- package/src/components/switch.tsx +24 -0
- package/src/components/table.tsx +93 -0
- package/src/components/tabs.tsx +57 -0
- package/src/components/textarea.tsx +18 -0
- package/src/components/tooltip.tsx +25 -0
- package/src/index.ts +342 -0
- package/src/lib/theme.ts +45 -0
- package/src/lib/utils.ts +6 -0
- package/src/styles.css +242 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Read-only renderer for HTML/Markdown produced by <RichtextEditor>.
|
|
2
|
+
// Sanitizes HTML through DOMPurify before injection. Markdown is parsed
|
|
3
|
+
// with `marked`, then sanitized. Output is wrapped in Tailwind `prose`
|
|
4
|
+
// utilities so headings/lists/code blocks/etc. get long-form typography.
|
|
5
|
+
|
|
6
|
+
import * as React from 'react'
|
|
7
|
+
import DOMPurify from 'dompurify'
|
|
8
|
+
import { marked } from 'marked'
|
|
9
|
+
import { cn } from '../lib/utils.js'
|
|
10
|
+
|
|
11
|
+
const proseRenderClass =
|
|
12
|
+
'prose prose-sm max-w-none text-foreground prose-headings:text-foreground ' +
|
|
13
|
+
'prose-p:text-foreground prose-strong:text-foreground prose-li:text-foreground ' +
|
|
14
|
+
'prose-code:text-foreground prose-pre:text-foreground prose-pre:bg-muted ' +
|
|
15
|
+
'prose-blockquote:text-muted-foreground prose-blockquote:border-border ' +
|
|
16
|
+
'prose-a:text-foreground dark:prose-invert'
|
|
17
|
+
|
|
18
|
+
export interface RichtextRenderProps {
|
|
19
|
+
value: string
|
|
20
|
+
format?: 'html' | 'markdown'
|
|
21
|
+
className?: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function renderHtml(html: string): string {
|
|
25
|
+
// DOMPurify is browser-only; on SSR fall back to escaping.
|
|
26
|
+
if (typeof window === 'undefined') return html
|
|
27
|
+
return DOMPurify.sanitize(html, { USE_PROFILES: { html: true } })
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function RichtextRender({
|
|
31
|
+
value,
|
|
32
|
+
format = 'html',
|
|
33
|
+
className,
|
|
34
|
+
}: RichtextRenderProps): React.ReactElement {
|
|
35
|
+
const html = React.useMemo(() => {
|
|
36
|
+
if (!value) return ''
|
|
37
|
+
if (format === 'markdown') {
|
|
38
|
+
// marked.parse is sync when no async extensions are configured.
|
|
39
|
+
const out = marked.parse(value, { async: false }) as string
|
|
40
|
+
return renderHtml(out)
|
|
41
|
+
}
|
|
42
|
+
return renderHtml(value)
|
|
43
|
+
}, [value, format])
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<div
|
|
47
|
+
className={cn(proseRenderClass, className)}
|
|
48
|
+
dangerouslySetInnerHTML={{ __html: html }}
|
|
49
|
+
/>
|
|
50
|
+
)
|
|
51
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Pure helpers for RichtextEditor content sync logic.
|
|
2
|
+
// Extracted so they can be unit-tested without a DOM/React environment.
|
|
3
|
+
|
|
4
|
+
import type { RichtextFormat } from './richtext-editor.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Reads the current content from a Tiptap editor instance.
|
|
8
|
+
* Returns the HTML or Markdown string depending on `format`.
|
|
9
|
+
*/
|
|
10
|
+
export function readEditorContent(
|
|
11
|
+
ed: { getHTML(): string; storage: unknown },
|
|
12
|
+
format: RichtextFormat,
|
|
13
|
+
): string {
|
|
14
|
+
if (format === 'markdown') {
|
|
15
|
+
return (
|
|
16
|
+
(ed.storage as { markdown?: { getMarkdown?(): string } }).markdown?.getMarkdown?.() ?? ''
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
return ed.getHTML()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Returns `true` if the editor content should be replaced with the external
|
|
24
|
+
* `value` (i.e. `editor.commands.setContent(value)` should be called).
|
|
25
|
+
*
|
|
26
|
+
* Returns `false` when:
|
|
27
|
+
*
|
|
28
|
+
* 1. `pendingFormatChange === true` — a format switch was just initiated.
|
|
29
|
+
* In this intermediate React render, `activeFormat` has already been
|
|
30
|
+
* updated to the new format while the external `value` still holds the
|
|
31
|
+
* previous format's string (the parent hasn't re-rendered yet).
|
|
32
|
+
* Calling `setContent` here would write the stale, wrong-format string
|
|
33
|
+
* into the editor and corrupt the content. The flag is cleared after the
|
|
34
|
+
* first effect run, so once `value` catches up the normal diff logic
|
|
35
|
+
* takes over again.
|
|
36
|
+
*
|
|
37
|
+
* 2. `value === lastEmitted` — the parent is just echoing back a value we
|
|
38
|
+
* ourselves emitted via onChange. This is the common race during fast
|
|
39
|
+
* typing: the user keeps typing while React is still committing the
|
|
40
|
+
* previous onChange, so by the time the effect runs the editor is
|
|
41
|
+
* *already* ahead of value. Calling setContent in that frame would
|
|
42
|
+
* rewind the editor to a stale state, dropping the most recent
|
|
43
|
+
* keystrokes and resetting the cursor. When value matches lastEmitted
|
|
44
|
+
* we trust the editor and skip the sync.
|
|
45
|
+
*
|
|
46
|
+
* 3. `editorContent === value` — the editor already has the correct content.
|
|
47
|
+
*/
|
|
48
|
+
export function shouldSyncToEditor(
|
|
49
|
+
editorContent: string,
|
|
50
|
+
value: string,
|
|
51
|
+
pendingFormatChange: boolean,
|
|
52
|
+
lastEmitted?: string,
|
|
53
|
+
): boolean {
|
|
54
|
+
if (pendingFormatChange) return false
|
|
55
|
+
if (lastEmitted !== undefined && value === lastEmitted) return false
|
|
56
|
+
return editorContent !== value
|
|
57
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area'
|
|
3
|
+
import { cn } from '../lib/utils.js'
|
|
4
|
+
|
|
5
|
+
export const ScrollArea = React.forwardRef<
|
|
6
|
+
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
|
|
7
|
+
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
|
|
8
|
+
>(({ className, children, ...props }, ref) => (
|
|
9
|
+
<ScrollAreaPrimitive.Root
|
|
10
|
+
ref={ref}
|
|
11
|
+
className={cn('relative overflow-hidden', className)}
|
|
12
|
+
{...props}
|
|
13
|
+
>
|
|
14
|
+
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
|
|
15
|
+
{children}
|
|
16
|
+
</ScrollAreaPrimitive.Viewport>
|
|
17
|
+
<ScrollBar />
|
|
18
|
+
<ScrollAreaPrimitive.Corner />
|
|
19
|
+
</ScrollAreaPrimitive.Root>
|
|
20
|
+
))
|
|
21
|
+
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
|
|
22
|
+
|
|
23
|
+
export const ScrollBar = React.forwardRef<
|
|
24
|
+
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
|
|
25
|
+
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
|
|
26
|
+
>(({ className, orientation = 'vertical', ...props }, ref) => (
|
|
27
|
+
<ScrollAreaPrimitive.ScrollAreaScrollbar
|
|
28
|
+
ref={ref}
|
|
29
|
+
orientation={orientation}
|
|
30
|
+
className={cn(
|
|
31
|
+
'flex touch-none select-none transition-colors',
|
|
32
|
+
orientation === 'vertical' && 'h-full w-2.5 border-l border-l-transparent p-[1px]',
|
|
33
|
+
orientation === 'horizontal' && 'h-2.5 flex-col border-t border-t-transparent p-[1px]',
|
|
34
|
+
className,
|
|
35
|
+
)}
|
|
36
|
+
{...props}
|
|
37
|
+
>
|
|
38
|
+
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
|
|
39
|
+
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
|
40
|
+
))
|
|
41
|
+
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import * as SelectPrimitive from '@radix-ui/react-select'
|
|
3
|
+
import { Check, ChevronDown, ChevronUp } from 'lucide-react'
|
|
4
|
+
import { cn } from '../lib/utils.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Thin wrapper around `SelectPrimitive.Root` that suppresses the spurious
|
|
8
|
+
* empty-string callback Radix fires through its hidden bubble-input while
|
|
9
|
+
* `SelectItem` nodes register lazily via Portal after an external `value`
|
|
10
|
+
* change (e.g. `form.reset()` after an async data load). Without this guard
|
|
11
|
+
* the externally-set value is immediately overwritten with `''`.
|
|
12
|
+
*
|
|
13
|
+
* The empty string is never a legitimate choice from a rendered `SelectItem`,
|
|
14
|
+
* so dropping it is always safe. Call sites should NOT add their own
|
|
15
|
+
* `if (v === '') return` guards — this component handles it centrally.
|
|
16
|
+
*/
|
|
17
|
+
function SelectRoot({
|
|
18
|
+
onValueChange,
|
|
19
|
+
...props
|
|
20
|
+
}: React.ComponentPropsWithoutRef<typeof SelectPrimitive.Root>): React.ReactElement {
|
|
21
|
+
const handleValueChange = React.useCallback(
|
|
22
|
+
(value: string) => {
|
|
23
|
+
if (value !== '') onValueChange?.(value)
|
|
24
|
+
},
|
|
25
|
+
[onValueChange],
|
|
26
|
+
)
|
|
27
|
+
return <SelectPrimitive.Root onValueChange={handleValueChange} {...props} />
|
|
28
|
+
}
|
|
29
|
+
SelectRoot.displayName = 'Select'
|
|
30
|
+
|
|
31
|
+
export const Select = SelectRoot
|
|
32
|
+
export const SelectGroup = SelectPrimitive.Group
|
|
33
|
+
export const SelectValue = SelectPrimitive.Value
|
|
34
|
+
|
|
35
|
+
export const SelectTrigger = React.forwardRef<
|
|
36
|
+
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
|
37
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
|
|
38
|
+
>(({ className, children, onMouseEnter, ...props }, ref) => {
|
|
39
|
+
const handleMouseEnter = (e: React.MouseEvent<HTMLButtonElement>): void => {
|
|
40
|
+
// Set native title only when the value span is actually clipped so the
|
|
41
|
+
// tooltip doesn't appear redundantly on short values.
|
|
42
|
+
const valueSpan = e.currentTarget.querySelector<HTMLElement>('span')
|
|
43
|
+
if (valueSpan && valueSpan.scrollWidth > valueSpan.offsetWidth) {
|
|
44
|
+
e.currentTarget.title = valueSpan.textContent ?? ''
|
|
45
|
+
} else {
|
|
46
|
+
e.currentTarget.removeAttribute('title')
|
|
47
|
+
}
|
|
48
|
+
onMouseEnter?.(e)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<SelectPrimitive.Trigger
|
|
53
|
+
ref={ref}
|
|
54
|
+
className={cn(
|
|
55
|
+
'flex h-9 w-full cursor-pointer items-center justify-between rounded-md border border-input bg-background px-3 py-1 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1',
|
|
56
|
+
className,
|
|
57
|
+
)}
|
|
58
|
+
onMouseEnter={handleMouseEnter}
|
|
59
|
+
{...props}
|
|
60
|
+
>
|
|
61
|
+
{children}
|
|
62
|
+
<SelectPrimitive.Icon asChild>
|
|
63
|
+
<ChevronDown className="h-4 w-4 opacity-50" />
|
|
64
|
+
</SelectPrimitive.Icon>
|
|
65
|
+
</SelectPrimitive.Trigger>
|
|
66
|
+
)
|
|
67
|
+
})
|
|
68
|
+
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
|
|
69
|
+
|
|
70
|
+
export const SelectScrollUpButton = React.forwardRef<
|
|
71
|
+
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
|
|
72
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
|
|
73
|
+
>(({ className, ...props }, ref) => (
|
|
74
|
+
<SelectPrimitive.ScrollUpButton
|
|
75
|
+
ref={ref}
|
|
76
|
+
className={cn('flex cursor-default items-center justify-center py-1', className)}
|
|
77
|
+
{...props}
|
|
78
|
+
>
|
|
79
|
+
<ChevronUp className="h-4 w-4" />
|
|
80
|
+
</SelectPrimitive.ScrollUpButton>
|
|
81
|
+
))
|
|
82
|
+
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
|
|
83
|
+
|
|
84
|
+
export const SelectScrollDownButton = React.forwardRef<
|
|
85
|
+
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
|
|
86
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
|
|
87
|
+
>(({ className, ...props }, ref) => (
|
|
88
|
+
<SelectPrimitive.ScrollDownButton
|
|
89
|
+
ref={ref}
|
|
90
|
+
className={cn('flex cursor-default items-center justify-center py-1', className)}
|
|
91
|
+
{...props}
|
|
92
|
+
>
|
|
93
|
+
<ChevronDown className="h-4 w-4" />
|
|
94
|
+
</SelectPrimitive.ScrollDownButton>
|
|
95
|
+
))
|
|
96
|
+
SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName
|
|
97
|
+
|
|
98
|
+
export const SelectContent = React.forwardRef<
|
|
99
|
+
React.ElementRef<typeof SelectPrimitive.Content>,
|
|
100
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
|
101
|
+
>(({ className, children, position = 'popper', ...props }, ref) => (
|
|
102
|
+
<SelectPrimitive.Portal>
|
|
103
|
+
<SelectPrimitive.Content
|
|
104
|
+
ref={ref}
|
|
105
|
+
className={cn(
|
|
106
|
+
'relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border border-border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2',
|
|
107
|
+
position === 'popper' &&
|
|
108
|
+
'data-[side=bottom]:translate-y-1 data-[side=top]:-translate-y-1',
|
|
109
|
+
className,
|
|
110
|
+
)}
|
|
111
|
+
position={position}
|
|
112
|
+
{...props}
|
|
113
|
+
>
|
|
114
|
+
<SelectScrollUpButton />
|
|
115
|
+
<SelectPrimitive.Viewport
|
|
116
|
+
className={cn(
|
|
117
|
+
'p-1',
|
|
118
|
+
position === 'popper' &&
|
|
119
|
+
'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]',
|
|
120
|
+
)}
|
|
121
|
+
>
|
|
122
|
+
{children}
|
|
123
|
+
</SelectPrimitive.Viewport>
|
|
124
|
+
<SelectScrollDownButton />
|
|
125
|
+
</SelectPrimitive.Content>
|
|
126
|
+
</SelectPrimitive.Portal>
|
|
127
|
+
))
|
|
128
|
+
SelectContent.displayName = SelectPrimitive.Content.displayName
|
|
129
|
+
|
|
130
|
+
export const SelectLabel = React.forwardRef<
|
|
131
|
+
React.ElementRef<typeof SelectPrimitive.Label>,
|
|
132
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
|
133
|
+
>(({ className, ...props }, ref) => (
|
|
134
|
+
<SelectPrimitive.Label
|
|
135
|
+
ref={ref}
|
|
136
|
+
className={cn('px-2 py-1.5 text-sm font-semibold', className)}
|
|
137
|
+
{...props}
|
|
138
|
+
/>
|
|
139
|
+
))
|
|
140
|
+
SelectLabel.displayName = SelectPrimitive.Label.displayName
|
|
141
|
+
|
|
142
|
+
export const SelectItem = React.forwardRef<
|
|
143
|
+
React.ElementRef<typeof SelectPrimitive.Item>,
|
|
144
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
|
|
145
|
+
>(({ className, children, ...props }, ref) => (
|
|
146
|
+
<SelectPrimitive.Item
|
|
147
|
+
ref={ref}
|
|
148
|
+
className={cn(
|
|
149
|
+
'relative flex w-full cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
|
150
|
+
className,
|
|
151
|
+
)}
|
|
152
|
+
title={typeof children === 'string' ? children : undefined}
|
|
153
|
+
{...props}
|
|
154
|
+
>
|
|
155
|
+
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
156
|
+
<SelectPrimitive.ItemIndicator>
|
|
157
|
+
<Check className="h-4 w-4" />
|
|
158
|
+
</SelectPrimitive.ItemIndicator>
|
|
159
|
+
</span>
|
|
160
|
+
<SelectPrimitive.ItemText>
|
|
161
|
+
<span className="block truncate">{children}</span>
|
|
162
|
+
</SelectPrimitive.ItemText>
|
|
163
|
+
</SelectPrimitive.Item>
|
|
164
|
+
))
|
|
165
|
+
SelectItem.displayName = SelectPrimitive.Item.displayName
|
|
166
|
+
|
|
167
|
+
export const SelectSeparator = React.forwardRef<
|
|
168
|
+
React.ElementRef<typeof SelectPrimitive.Separator>,
|
|
169
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
|
170
|
+
>(({ className, ...props }, ref) => (
|
|
171
|
+
<SelectPrimitive.Separator
|
|
172
|
+
ref={ref}
|
|
173
|
+
className={cn('-mx-1 my-1 h-px bg-muted', className)}
|
|
174
|
+
{...props}
|
|
175
|
+
/>
|
|
176
|
+
))
|
|
177
|
+
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Plain native `<select>` styled to match the rest of the kit. Useful when
|
|
181
|
+
* you need a no-JS, SSR-friendly fallback or simple form posts. Most call
|
|
182
|
+
* sites should prefer the Radix-based `Select` family above.
|
|
183
|
+
*/
|
|
184
|
+
export type NativeSelectProps = React.SelectHTMLAttributes<HTMLSelectElement>
|
|
185
|
+
|
|
186
|
+
export const NativeSelect = React.forwardRef<HTMLSelectElement, NativeSelectProps>(
|
|
187
|
+
({ className, children, ...props }, ref) => (
|
|
188
|
+
<select
|
|
189
|
+
ref={ref}
|
|
190
|
+
className={cn(
|
|
191
|
+
'flex h-9 w-full rounded-md border border-input bg-background px-3 py-1 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
|
|
192
|
+
className,
|
|
193
|
+
)}
|
|
194
|
+
{...props}
|
|
195
|
+
>
|
|
196
|
+
{children}
|
|
197
|
+
</select>
|
|
198
|
+
),
|
|
199
|
+
)
|
|
200
|
+
NativeSelect.displayName = 'NativeSelect'
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import * as SeparatorPrimitive from '@radix-ui/react-separator'
|
|
3
|
+
import { cn } from '../lib/utils.js'
|
|
4
|
+
|
|
5
|
+
export const Separator = React.forwardRef<
|
|
6
|
+
React.ElementRef<typeof SeparatorPrimitive.Root>,
|
|
7
|
+
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
|
|
8
|
+
>(({ className, orientation = 'horizontal', decorative = true, ...props }, ref) => (
|
|
9
|
+
<SeparatorPrimitive.Root
|
|
10
|
+
ref={ref}
|
|
11
|
+
decorative={decorative}
|
|
12
|
+
orientation={orientation}
|
|
13
|
+
className={cn(
|
|
14
|
+
'shrink-0 bg-border',
|
|
15
|
+
orientation === 'horizontal' ? 'h-px w-full' : 'h-full w-px',
|
|
16
|
+
className,
|
|
17
|
+
)}
|
|
18
|
+
{...props}
|
|
19
|
+
/>
|
|
20
|
+
))
|
|
21
|
+
Separator.displayName = SeparatorPrimitive.Root.displayName
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// shadcn-style Sheet — side-anchored Dialog. Built on @radix-ui/react-dialog,
|
|
2
|
+
// shares the same primitive as <Dialog>. Variants: top / bottom / left / right.
|
|
3
|
+
|
|
4
|
+
import * as React from 'react'
|
|
5
|
+
import * as SheetPrimitive from '@radix-ui/react-dialog'
|
|
6
|
+
import { cva, type VariantProps } from 'class-variance-authority'
|
|
7
|
+
import { X } from 'lucide-react'
|
|
8
|
+
import { cn } from '../lib/utils.js'
|
|
9
|
+
|
|
10
|
+
export const Sheet = SheetPrimitive.Root
|
|
11
|
+
export const SheetTrigger = SheetPrimitive.Trigger
|
|
12
|
+
export const SheetClose = SheetPrimitive.Close
|
|
13
|
+
export const SheetPortal = SheetPrimitive.Portal
|
|
14
|
+
|
|
15
|
+
export const SheetOverlay = React.forwardRef<
|
|
16
|
+
React.ElementRef<typeof SheetPrimitive.Overlay>,
|
|
17
|
+
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
|
|
18
|
+
>(({ className, ...props }, ref) => (
|
|
19
|
+
<SheetPrimitive.Overlay
|
|
20
|
+
ref={ref}
|
|
21
|
+
className={cn(
|
|
22
|
+
'fixed inset-0 z-50 bg-black/50 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
|
|
23
|
+
className,
|
|
24
|
+
)}
|
|
25
|
+
{...props}
|
|
26
|
+
/>
|
|
27
|
+
))
|
|
28
|
+
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
|
|
29
|
+
|
|
30
|
+
const sheetVariants = cva(
|
|
31
|
+
'fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500',
|
|
32
|
+
{
|
|
33
|
+
variants: {
|
|
34
|
+
side: {
|
|
35
|
+
top: 'inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top',
|
|
36
|
+
bottom:
|
|
37
|
+
'inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom',
|
|
38
|
+
left: 'inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm',
|
|
39
|
+
right:
|
|
40
|
+
'inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm',
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
defaultVariants: { side: 'right' },
|
|
44
|
+
},
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
export interface SheetContentProps
|
|
48
|
+
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
|
|
49
|
+
VariantProps<typeof sheetVariants> {
|
|
50
|
+
/** Set to true to suppress the built-in absolute close button (e.g. when
|
|
51
|
+
* the consumer renders its own close control inside the header). */
|
|
52
|
+
hideCloseButton?: boolean
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const SheetContent = React.forwardRef<
|
|
56
|
+
React.ElementRef<typeof SheetPrimitive.Content>,
|
|
57
|
+
SheetContentProps
|
|
58
|
+
>(({ side = 'right', className, children, hideCloseButton, ...props }, ref) => (
|
|
59
|
+
<SheetPortal>
|
|
60
|
+
<SheetOverlay />
|
|
61
|
+
<SheetPrimitive.Content ref={ref} className={cn(sheetVariants({ side }), className)} {...props}>
|
|
62
|
+
{children}
|
|
63
|
+
{!hideCloseButton && (
|
|
64
|
+
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
|
|
65
|
+
<X className="size-4" />
|
|
66
|
+
<span className="sr-only">Close</span>
|
|
67
|
+
</SheetPrimitive.Close>
|
|
68
|
+
)}
|
|
69
|
+
</SheetPrimitive.Content>
|
|
70
|
+
</SheetPortal>
|
|
71
|
+
))
|
|
72
|
+
SheetContent.displayName = SheetPrimitive.Content.displayName
|
|
73
|
+
|
|
74
|
+
export const SheetHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>): React.ReactElement => (
|
|
75
|
+
<div className={cn('flex flex-col space-y-2 text-center sm:text-left', className)} {...props} />
|
|
76
|
+
)
|
|
77
|
+
SheetHeader.displayName = 'SheetHeader'
|
|
78
|
+
|
|
79
|
+
export const SheetFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>): React.ReactElement => (
|
|
80
|
+
<div
|
|
81
|
+
className={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className)}
|
|
82
|
+
{...props}
|
|
83
|
+
/>
|
|
84
|
+
)
|
|
85
|
+
SheetFooter.displayName = 'SheetFooter'
|
|
86
|
+
|
|
87
|
+
export const SheetTitle = React.forwardRef<
|
|
88
|
+
React.ElementRef<typeof SheetPrimitive.Title>,
|
|
89
|
+
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
|
|
90
|
+
>(({ className, ...props }, ref) => (
|
|
91
|
+
<SheetPrimitive.Title
|
|
92
|
+
ref={ref}
|
|
93
|
+
className={cn('text-lg font-semibold text-foreground', className)}
|
|
94
|
+
{...props}
|
|
95
|
+
/>
|
|
96
|
+
))
|
|
97
|
+
SheetTitle.displayName = SheetPrimitive.Title.displayName
|
|
98
|
+
|
|
99
|
+
export const SheetDescription = React.forwardRef<
|
|
100
|
+
React.ElementRef<typeof SheetPrimitive.Description>,
|
|
101
|
+
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
|
|
102
|
+
>(({ className, ...props }, ref) => (
|
|
103
|
+
<SheetPrimitive.Description
|
|
104
|
+
ref={ref}
|
|
105
|
+
className={cn('text-sm text-muted-foreground', className)}
|
|
106
|
+
{...props}
|
|
107
|
+
/>
|
|
108
|
+
))
|
|
109
|
+
SheetDescription.displayName = SheetPrimitive.Description.displayName
|