@alfakit/react 0.1.12 → 0.2.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/CHANGELOG.md +12 -0
- package/dist/components/cms/blocks.d.ts.map +1 -1
- package/dist/components/cms/blocks.js +2 -2
- package/dist/components/cms/blocks.js.map +1 -1
- package/dist/components/cms/head.d.ts +6 -4
- package/dist/components/cms/head.d.ts.map +1 -1
- package/dist/components/cms/head.js +10 -4
- package/dist/components/cms/head.js.map +1 -1
- package/dist/components/cms/page.d.ts.map +1 -1
- package/dist/components/cms/page.js +16 -3
- package/dist/components/cms/page.js.map +1 -1
- package/dist/components/fields/file-upload.js +4 -4
- package/dist/components/fields/file-upload.js.map +1 -1
- package/dist/components/fields/rich-editor.js +4 -2
- package/dist/components/fields/rich-editor.js.map +1 -1
- package/dist/components/fields/slider.js +1 -1
- package/dist/components/fields/slider.js.map +1 -1
- package/dist/components/helpdesk/chat-widget.js +1 -1
- package/dist/components/layout/section.js +1 -1
- package/dist/components/layout/tabs.js +1 -1
- package/dist/components/panel/app-sidebar.d.ts.map +1 -1
- package/dist/components/panel/app-sidebar.js +27 -5
- package/dist/components/panel/app-sidebar.js.map +1 -1
- package/dist/components/panel/global-search.js +2 -2
- package/dist/components/panel/global-search.js.map +1 -1
- package/dist/components/panel/shortcut-keys.js +1 -1
- package/dist/components/panel/shortcut-keys.js.map +1 -1
- package/dist/components/table/list-tabs.js +1 -1
- package/dist/components/table/list-tabs.js.map +1 -1
- package/dist/components/table/table-views.js +1 -1
- package/dist/components/table/table-views.js.map +1 -1
- package/dist/components/versions/record-versions.js +1 -1
- package/dist/context.d.ts +8 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +27 -2
- package/dist/context.js.map +1 -1
- package/dist/create-alfakit.d.ts.map +1 -1
- package/dist/create-alfakit.js +6 -3
- package/dist/create-alfakit.js.map +1 -1
- package/dist/eject-manifest.json +2 -2
- package/dist/index.js +1 -1
- package/dist/layouts/app-layout.d.ts.map +1 -1
- package/dist/layouts/app-layout.js +13 -3
- package/dist/layouts/app-layout.js.map +1 -1
- package/dist/lib/discovery.d.ts +4 -0
- package/dist/lib/discovery.d.ts.map +1 -1
- package/dist/lib/discovery.js +17 -7
- package/dist/lib/discovery.js.map +1 -1
- package/dist/lib/icons.d.ts +2 -1
- package/dist/lib/icons.d.ts.map +1 -1
- package/dist/lib/icons.js +55 -19
- package/dist/lib/icons.js.map +1 -1
- package/dist/pages/page.d.ts +1 -1
- package/dist/pages/page.d.ts.map +1 -1
- package/dist/pages/page.js +47 -10
- package/dist/pages/page.js.map +1 -1
- package/dist/standalone/alfakit-component.js +2 -1
- package/dist/standalone/alfakit-component.js.map +1 -1
- package/dist/standalone/alfakit-toaster.js +2 -1
- package/dist/standalone/alfakit-toaster.js.map +1 -1
- package/dist/standalone/runtime.js +1 -11
- package/dist/standalone/runtime.js.map +1 -1
- package/dist/standalone/toaster-claims.js +16 -0
- package/dist/standalone/toaster-claims.js.map +1 -0
- package/dist/starter-kit/widgets-loaded.js +63 -6
- package/dist/starter-kit/widgets-loaded.js.map +1 -1
- package/dist/starter-kit/widgets-triggers.js +1 -1
- package/dist/starter-kit/widgets-triggers.js.map +1 -1
- package/dist/themes.css +92 -0
- package/package.json +2 -2
- package/src/components/cms/blocks.tsx +7 -1
- package/src/components/cms/head.tsx +19 -4
- package/src/components/cms/page.tsx +17 -2
- package/src/components/fields/file-upload.tsx +5 -4
- package/src/components/fields/rich-editor.tsx +7 -2
- package/src/components/fields/slider.tsx +3 -1
- package/src/components/panel/app-sidebar.tsx +28 -7
- package/src/components/panel/global-search.tsx +4 -1
- package/src/components/panel/shortcut-keys.tsx +1 -1
- package/src/components/table/list-tabs.tsx +3 -1
- package/src/components/table/table-views.tsx +3 -1
- package/src/context.tsx +46 -1
- package/src/create-alfakit.tsx +13 -2
- package/src/layouts/app-layout.tsx +16 -3
- package/src/lib/discovery.tsx +23 -8
- package/src/lib/icons.tsx +90 -24
- package/src/pages/page.tsx +85 -27
- package/src/standalone/runtime.ts +2 -14
- package/src/standalone/toaster-claims.ts +16 -0
- package/src/starter-kit/widgets-loaded.tsx +84 -1
- package/src/starter-kit/widgets-triggers.tsx +4 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rich-editor.js","names":[],"sources":["../../../src/components/fields/rich-editor.tsx"],"sourcesContent":["import type {\n LoadedRichEditorExtensions,\n RichEditorPanel,\n RichEditorSchema,\n ToolCommandContext,\n} from '@alfakit/core'\nimport {\n allToolbarButtons,\n attachToolbarKeyboard,\n commandLabel,\n detailsNodeConfigs,\n documentNonce,\n EditorAttachments,\n fieldIds,\n fileAttachmentError,\n floatingToolbarStyle,\n focusToolbar,\n gridNodeConfigs,\n highlightMarkConfig,\n htmlToMarkdown,\n isToolbarGroup,\n isToolbarShortcut,\n leadExtensionConfig,\n linkExtensionOptions,\n loadRichEditorExtensions,\n panelUploadOptions,\n resizableImageAttributes,\n resizableImageOptions,\n resolveFloatingToolbar,\n resolveStickyOffset,\n setTextColor,\n showsTableToolbar,\n smallMarkConfig,\n subscriptMarkConfig,\n superscriptMarkConfig,\n TABLE_TOOLBAR_BUTTONS,\n textAlignExtensionConfig,\n textColorMarkConfig,\n toEditorDocument,\n toolbarButtonLabel,\n toolbarCommands,\n toolbarPanel,\n uploadTemporaryFile,\n} from '@alfakit/core'\nimport Image from '@tiptap/extension-image'\nimport Link from '@tiptap/extension-link'\nimport Placeholder from '@tiptap/extension-placeholder'\nimport { TableKit } from '@tiptap/extension-table'\nimport {\n EditorContent,\n Extension,\n Mark,\n Node,\n useEditor,\n type Editor,\n type Extensions,\n} from '@tiptap/react'\nimport StarterKit from '@tiptap/starter-kit'\nimport {\n ALargeSmallIcon,\n BaselineIcon,\n BetweenHorizontalEndIcon,\n BetweenHorizontalStartIcon,\n BetweenVerticalEndIcon,\n BetweenVerticalStartIcon,\n BoldIcon,\n CodeIcon,\n Columns2Icon,\n Columns3Icon,\n EraserIcon,\n Grid2x2XIcon,\n Heading1Icon,\n Heading2Icon,\n Heading3Icon,\n Heading4Icon,\n Heading5Icon,\n Heading6Icon,\n HighlighterIcon,\n ImageIcon,\n ItalicIcon,\n LinkIcon,\n ListCollapseIcon,\n ListIcon,\n ListOrderedIcon,\n Minus,\n PanelTopIcon,\n PilcrowIcon,\n QuoteIcon,\n RedoIcon,\n RemoveFormattingIcon,\n Rows3Icon,\n SquareCodeIcon,\n SquareXIcon,\n StrikethroughIcon,\n SubscriptIcon,\n SuperscriptIcon,\n TableCellsMergeIcon,\n TableCellsSplitIcon,\n TableIcon,\n TablePropertiesIcon,\n TextAlignCenterIcon,\n TextAlignEndIcon,\n TextAlignJustifyIcon,\n TextAlignStartIcon,\n TypeIcon,\n UnderlineIcon,\n UndoIcon,\n} from 'lucide-react'\nimport { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from 'react'\nimport { useActionRunner, usePanel, useT } from '../../context'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport { useField } from '../form/form-context'\nimport { FieldWrapper } from '../schema/field-wrapper'\nimport { Button } from '../ui/button'\nimport { Separator } from '../ui/separator'\nimport { Skeleton } from '../ui/skeleton'\nimport {\n ExtensionToolbarButton,\n RichEditorSidePanel,\n stickyStyle,\n SuggestionPopup,\n ToolbarGroupButton,\n useRichEditorExtensions,\n} from './rich-editor-extensions'\n\ntype MarkConfig = Parameters<typeof Mark.create>[0]\ntype NodeConfig = Parameters<typeof Node.create>[0]\ntype ExtensionConfig = Parameters<typeof Extension.create>[0]\n\n/** Toolbar button icons; what each button does lives in `@alfakit/core` (`TOOLBAR_COMMANDS`). */\nconst ICONS: Record<string, ReactNode> = {\n bold: <BoldIcon />,\n italic: <ItalicIcon />,\n underline: <UnderlineIcon />,\n strike: <StrikethroughIcon />,\n subscript: <SubscriptIcon />,\n superscript: <SuperscriptIcon />,\n code: <CodeIcon />,\n highlight: <HighlighterIcon />,\n textColor: <BaselineIcon />,\n link: <LinkIcon />,\n h1: <Heading1Icon />,\n h2: <Heading2Icon />,\n h3: <Heading3Icon />,\n h4: <Heading4Icon />,\n h5: <Heading5Icon />,\n h6: <Heading6Icon />,\n paragraph: <TypeIcon />,\n heading: <Heading2Icon />,\n // Alignment and column icons point at a side: mirror them right to left.\n alignStart: <TextAlignStartIcon className=\"rtl:-scale-x-100\" />,\n alignCenter: <TextAlignCenterIcon />,\n alignEnd: <TextAlignEndIcon className=\"rtl:-scale-x-100\" />,\n alignJustify: <TextAlignJustifyIcon />,\n blockquote: <QuoteIcon />,\n codeBlock: <SquareCodeIcon />,\n bulletList: <ListIcon />,\n orderedList: <ListOrderedIcon />,\n horizontalRule: <Minus />,\n details: <ListCollapseIcon />,\n lead: <PilcrowIcon />,\n small: <ALargeSmallIcon />,\n grid: <Columns2Icon />,\n gridAddColumnBefore: <BetweenVerticalStartIcon className=\"rtl:-scale-x-100\" />,\n gridAddColumnAfter: <BetweenVerticalEndIcon className=\"rtl:-scale-x-100\" />,\n gridDeleteColumn: <Columns3Icon />,\n gridDelete: <SquareXIcon />,\n table: <TableIcon />,\n tableAddColumnBefore: <BetweenVerticalStartIcon className=\"rtl:-scale-x-100\" />,\n tableAddColumnAfter: <BetweenVerticalEndIcon className=\"rtl:-scale-x-100\" />,\n tableDeleteColumn: <Columns3Icon />,\n tableAddRowBefore: <BetweenHorizontalStartIcon />,\n tableAddRowAfter: <BetweenHorizontalEndIcon />,\n tableDeleteRow: <Rows3Icon />,\n tableMergeCells: <TableCellsMergeIcon />,\n tableSplitCell: <TableCellsSplitIcon />,\n tableToggleHeaderRow: <PanelTopIcon />,\n tableToggleHeaderCell: <TablePropertiesIcon />,\n tableDelete: <Grid2x2XIcon />,\n clearFormatting: <RemoveFormattingIcon />,\n attachFiles: <ImageIcon />,\n undo: <UndoIcon />,\n redo: <RedoIcon />,\n}\n\n/**\n * Resize handles of `resizableImages()` (Tiptap's node view adds bare `data-resize-handle`\n * elements): round dots on the corners, shown on hover and while the image is selected.\n */\nconst RESIZE_HANDLE_CLASSES =\n '[&_[data-resize-handle]]:z-10 [&_[data-resize-handle]]:size-3 [&_[data-resize-handle]]:rounded-full [&_[data-resize-handle]]:border-2 [&_[data-resize-handle]]:border-background [&_[data-resize-handle]]:bg-primary [&_[data-resize-handle]]:opacity-0 [&_[data-resize-container]:hover_[data-resize-handle]]:opacity-100 [&_.ProseMirror-selectednode_[data-resize-handle]]:opacity-100 [&_[data-resize-handle=bottom-right]]:cursor-nwse-resize [&_[data-resize-handle=top-left]]:cursor-nwse-resize [&_[data-resize-handle=bottom-left]]:cursor-nesw-resize [&_[data-resize-handle=top-right]]:cursor-nesw-resize'\n\n/** The image extension, with drag handles for `resizableImages()`. */\nexport function imageExtension(resizable: boolean) {\n if (!resizable) return Image\n\n return Image.extend({\n addAttributes() {\n return resizableImageAttributes(this.parent?.() ?? {})\n },\n }).configure(resizableImageOptions())\n}\n\n/** Extensions for the extra toolbar buttons (only those the toolbar uses are loaded). */\nexport function toolbarExtensions(buttons: string[], markdown: boolean): Extensions {\n const has = (...names: string[]) => names.some((name) => buttons.includes(name))\n const extensions: Extensions = []\n\n if (has('table')) extensions.push(TableKit.configure({ table: { resizable: false } }))\n // Markdown has no syntax for these.\n if (markdown) return extensions\n\n if (has('highlight')) extensions.push(Mark.create(highlightMarkConfig() as MarkConfig))\n if (has('subscript')) extensions.push(Mark.create(subscriptMarkConfig() as MarkConfig))\n if (has('superscript')) extensions.push(Mark.create(superscriptMarkConfig() as MarkConfig))\n if (has('textColor')) extensions.push(Mark.create(textColorMarkConfig() as MarkConfig))\n if (has('alignStart', 'alignCenter', 'alignEnd', 'alignJustify')) {\n extensions.push(Extension.create(textAlignExtensionConfig() as ExtensionConfig))\n }\n if (has('details')) {\n extensions.push(...detailsNodeConfigs().map((config) => Node.create(config as NodeConfig)))\n }\n if (has('small')) extensions.push(Mark.create(smallMarkConfig() as MarkConfig))\n if (has('lead')) extensions.push(Extension.create(leadExtensionConfig() as ExtensionConfig))\n if (has('grid', 'gridDelete', 'gridAddColumnBefore', 'gridAddColumnAfter', 'gridDeleteColumn')) {\n extensions.push(...gridNodeConfigs().map((config) => Node.create(config as NodeConfig)))\n }\n\n return extensions\n}\n\nconst NO_EXTENSIONS: LoadedRichEditorExtensions = { extensions: [], commands: {} }\n\n/**\n * The rich and Markdown editors. Plugin extensions (`RichEditor::plugins()`) load before\n * the editor is created, since Tiptap extensions can't be added afterwards.\n */\nexport function RichEditorField({ component }: { component: RichEditorSchema }) {\n const keys = component.type === 'MarkdownEditor' ? [] : (component.extensions ?? [])\n const signature = keys.join('|')\n const [loaded, setLoaded] = useState<{\n signature: string\n value: LoadedRichEditorExtensions\n } | null>(() => (keys.length === 0 ? { signature, value: NO_EXTENSIONS } : null))\n\n useEffect(() => {\n if (loaded?.signature === signature) return\n\n let cancelled = false\n\n void loadRichEditorExtensions(signature === '' ? [] : signature.split('|')).then((value) => {\n if (!cancelled) setLoaded({ signature, value })\n })\n\n return () => {\n cancelled = true\n }\n }, [signature, loaded?.signature])\n\n if (loaded?.signature !== signature) {\n return <RichEditorLoading component={component} />\n }\n\n return <RichEditorInput key={signature} component={component} plugins={loaded.value} />\n}\n\nfunction RichEditorLoading({ component }: { component: RichEditorSchema }) {\n const { id, error } = useField<unknown>(component)\n\n return (\n <FieldWrapper field={component} htmlFor={id} error={error}>\n <Skeleton className=\"h-40 w-full\" aria-hidden=\"true\" />\n </FieldWrapper>\n )\n}\n\nfunction RichEditorInput({\n component,\n plugins,\n}: {\n component: RichEditorSchema\n plugins: LoadedRichEditorExtensions\n}) {\n const t = useT()\n const panel = usePanel()\n const runner = useActionRunner()\n const { value, setValue, blur, error, id, disabled, readOnly, aria } =\n useField<unknown>(component)\n const fileInput = useRef<HTMLInputElement>(null)\n // ProseMirror reads its DOM attributes on every view update; the ref keeps them current.\n const ariaRef = useRef(aria)\n const isMarkdown = component.type === 'MarkdownEditor'\n // Uploaded images preview from a local URL but are stored by their `tmp:` key.\n const [attachments] = useState(() => new EditorAttachments())\n const editable = !disabled && !readOnly\n const isJson = Boolean(component.json) && !isMarkdown\n // The editor's live instance, for the custom block node views and suggestions.\n const editorRef = useRef<Editor | null>(null)\n const richExtensions = useRichEditorExtensions(component, editorRef)\n // The open toolbar panel (the text colour palette).\n const [openPanel, setOpenPanel] = useState<'textColor' | null>(null)\n // The open side panel (custom blocks or merge tags).\n const [sidePanel, setSidePanel] = useState<RichEditorPanel | null>(() => {\n const initial = component.activePanel ?? null\n\n if (initial === 'customBlocks') return component.customBlocks?.length ? initial : null\n if (initial === 'mergeTags') return component.mergeTags?.length ? initial : null\n\n return null\n })\n const [attachmentError, setAttachmentError] = useState<string | null>(null)\n // Re-render on selection changes so active states and the table row follow the cursor.\n const [, setSelectionTick] = useState(0)\n const sticky = Boolean(component.stickyToolbar || component.stickyPanels)\n const contextRef = useRef(richExtensions.context)\n // Keyboard access to the toolbars (roving tabindex, arrows, Escape, Alt+F10).\n const toolbarElements = useRef<Record<'main' | 'table' | 'floating', HTMLElement | null>>({\n main: null,\n table: null,\n floating: null,\n })\n const toolbars = useCallback(\n () => [\n toolbarElements.current.floating,\n toolbarElements.current.main,\n toolbarElements.current.table,\n ],\n [],\n )\n const attachKeyboard = useCallback(\n (slot: 'main' | 'table' | 'floating', element: HTMLElement | null) => {\n toolbarElements.current[slot] = element\n\n if (!element) return\n\n const detach = attachToolbarKeyboard(element, {\n onEscape: () => editorRef.current?.commands.focus(),\n toolbars,\n })\n\n return () => {\n detach()\n if (toolbarElements.current[slot] === element) toolbarElements.current[slot] = null\n }\n },\n [toolbars],\n )\n const mainToolbarRef = useCallback(\n (element: HTMLElement | null) => attachKeyboard('main', element),\n [attachKeyboard],\n )\n const tableToolbarRef = useCallback(\n (element: HTMLElement | null) => attachKeyboard('table', element),\n [attachKeyboard],\n )\n const floatingToolbarRef = useCallback(\n (element: HTMLElement | null) => attachKeyboard('floating', element),\n [attachKeyboard],\n )\n\n useEffect(() => {\n contextRef.current = richExtensions.context\n })\n\n // How plugin tools open their editor action. The refs are read when a tool runs (on\n // click), never during render.\n const [toolContext] = useState<ToolCommandContext>(() => ({\n runAction: (tool, args) =>\n tool.action\n ? runner.runField(tool.action, { ...contextRef.current, arguments: args })\n : Promise.resolve(undefined),\n editor: () => editorRef.current,\n }))\n\n // Built-in buttons, plugin tools and commands of plugin JavaScript extensions.\n const commands = useMemo(\n () => toolbarCommands(component.editorTools, toolContext, plugins.commands),\n [component.editorTools, plugins.commands, toolContext],\n )\n\n // Field state ↔ editor content: HTML, Markdown (loaded from the server's HTML) or a\n // Tiptap JSON document. `tmp:` upload keys become local preview URLs and back.\n const stateToContent = (): string | Record<string, unknown> => {\n if (isJson) {\n const document = toEditorDocument(value)\n\n return document ? JSON.parse(attachments.toEditor(JSON.stringify(document))) : ''\n }\n\n const markdownHtml = typeof component.html === 'string' ? component.html : null\n\n return attachments.toEditor(((isMarkdown ? markdownHtml : value) as string | null) ?? '')\n }\n const editorToState = (instance: Editor): unknown => {\n if (instance.isEmpty) return null\n if (isJson) return JSON.parse(attachments.toState(JSON.stringify(instance.getJSON())))\n\n const html = instance.getHTML()\n\n return attachments.toState(isMarkdown ? htmlToMarkdown(html) : html)\n }\n\n useEffect(() => () => attachments.destroy(), [attachments])\n\n const editor = useEditor({\n // TipTap injects its base CSS; with the page's CSP nonce a nonce-based policy allows it.\n injectNonce: documentNonce(),\n extensions: [\n StarterKit.configure({ link: false }),\n Link.configure(linkExtensionOptions(component.linkProtocols)),\n imageExtension(Boolean(component.resizableImages) && !isMarkdown),\n Placeholder.configure({ placeholder: component.placeholder ?? '' }),\n ...toolbarExtensions(allToolbarButtons(component), isMarkdown),\n ...(isMarkdown ? [] : richExtensions.extensions),\n ...(isMarkdown ? [] : (plugins.extensions as Extensions)),\n ],\n // Markdown is loaded from the server's HTML rendering of it, never parsed as HTML.\n content: stateToContent(),\n editable,\n editorProps: {\n // The mention / merge tag popup takes the arrow, Enter, Tab and Escape keys.\n handleKeyDown: (_view, event) => {\n // Alt+F10: into the floating toolbar, else the main one.\n if (isToolbarShortcut(event)) {\n return focusToolbar(toolbars())\n }\n\n return editorRef.current\n ? richExtensions.suggestions.handleKeyDown(event, editorRef.current)\n : false\n },\n attributes: () => ({\n class: cn(\n 'alfakit-prose min-h-32 px-3 py-2 text-sm outline-none',\n component.resizableImages && RESIZE_HANDLE_CLASSES,\n ),\n // Runtime sizes cannot be Tailwind classes (they would never be generated).\n ...(component.minHeight ? { style: `min-height: ${component.minHeight}px` } : {}),\n id,\n // Each paragraph follows its own script, so Arabic and English mix correctly.\n dir: 'auto',\n role: 'textbox',\n 'aria-multiline': 'true',\n ...(component.label ? { 'aria-labelledby': fieldIds(id).label } : {}),\n ...Object.fromEntries(\n Object.entries(ariaRef.current).map(([name, attribute]) => [name, String(attribute)]),\n ),\n }),\n },\n onUpdate: ({ editor: instance }) => {\n setValue(editorToState(instance))\n },\n onTransaction: ({ editor: instance }) => {\n richExtensions.suggestions.update(instance)\n setSelectionTick((tick) => tick + 1)\n },\n onBlur: () => {\n richExtensions.suggestions.close()\n blur()\n },\n })\n\n useEffect(() => {\n editorRef.current = editor\n }, [editor])\n\n // Keep editor in sync when the server refreshes the state.\n useEffect(() => {\n // Tiptap may replace the instance on mount (e.g. when the editor loads lazily); the\n // replaced one has no schema left, and the new one starts from the current value.\n if (!editor?.schema) return\n\n const current = editorToState(editor)\n const same = isJson\n ? JSON.stringify(toEditorDocument(value)) === JSON.stringify(current)\n : (value ?? '') === (current ?? '')\n\n if (!same) {\n editor.commands.setContent(stateToContent(), { emitUpdate: false })\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [value])\n\n useEffect(() => {\n editor?.setEditable(editable)\n }, [editor, editable])\n\n // Re-apply the editable element's attributes when the error or description changes.\n useEffect(() => {\n ariaRef.current = aria\n\n if (editor && !editor.isDestroyed) {\n editor.view.setProps({})\n }\n }, [editor, aria])\n\n // Measured in the browser once the editor exists (the toolbar only renders then).\n const stickyTop = useMemo(\n () => (sticky && editor ? resolveStickyOffset(component.stickyOffset) : null),\n [sticky, editor, component.stickyOffset],\n )\n\n // A floating toolbar follows its selection while the page scrolls.\n const hasFloatingToolbars = Boolean(component.floatingToolbars)\n\n useEffect(() => {\n if (!hasFloatingToolbars) return\n\n const onScroll = () => setSelectionTick((tick) => tick + 1)\n\n window.addEventListener('scroll', onScroll, { capture: true, passive: true })\n\n return () => window.removeEventListener('scroll', onScroll, { capture: true })\n }, [hasFloatingToolbars])\n\n const pickImage = () => fileInput.current?.click()\n\n const uploadImage = async (file: File) => {\n if (!panel.endpoints.upload || !editor) return\n\n // The server checks again when the form is saved.\n const problem = fileAttachmentError(file, component.fileAttachments)\n\n if (problem) {\n setAttachmentError(\n problem === 'too_large'\n ? t('form.file_too_large', {}, 'This file is too large.')\n : t('form.file_invalid_type', {}, 'This file type is not allowed.'),\n )\n\n return\n }\n\n setAttachmentError(null)\n\n let response: { key: string }\n\n try {\n // Large attachments go in resumable chunks.\n response = await uploadTemporaryFile(\n panel.endpoints.upload,\n file,\n panelUploadOptions(panel, component.fileAttachments.uploadPolicy),\n )\n } catch (error) {\n setAttachmentError(\n (error as { errors?: Record<string, string[]> }).errors?.file?.[0] ??\n t('form.upload_failed', {}, 'The file could not be uploaded.'),\n )\n\n return\n }\n\n const src = attachments.add(response.key, file)\n\n editor.chain().focus().setImage({ src, alt: file.name }).run()\n }\n\n const translate = (key: string, fallback: string) => t(key, {}, fallback)\n\n const iconFor = (name: string): ReactNode => {\n if (ICONS[name]) return ICONS[name]\n\n const tool = component.editorTools?.find((candidate) => candidate.name === name)\n\n return tool?.icon ? (\n <Icon name={tool.icon} />\n ) : (\n <span className=\"text-xs\">{commandLabel(name, commands, translate)}</span>\n )\n }\n\n const runCommand = (name: string) => {\n const button = commands[name]\n\n if (!button || !editor) return\n\n button.run(editor, {\n // Messages arrive translated (`form.link_prompt`, `editor.grid_columns`).\n prompt: (message) => window.prompt(message),\n pickImage,\n togglePanel: (target) => setOpenPanel((current) => (current === target ? null : target)),\n t: translate,\n })\n }\n\n const renderButton = (name: string) => {\n const button = commands[name]\n\n if (!button || !editor) return null\n\n const label = commandLabel(name, commands, translate)\n const active = button.active?.(editor) || (name === 'textColor' && openPanel === name)\n\n return (\n <Button\n key={name}\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={label}\n title={label}\n aria-pressed={button.active || name === 'textColor' ? Boolean(active) : undefined}\n disabled={!editable}\n className={cn(active && 'bg-accent text-accent-foreground')}\n onMouseDown={(event) => event.preventDefault()}\n onClick={() => runCommand(name)}\n >\n {iconFor(name)}\n </Button>\n )\n }\n\n /** A toolbar entry: separator, group dropdown, panel toggle or button. */\n const renderEntry = (name: string, index: number) => {\n if (!editor) return null\n\n if (name === '|') {\n return <Separator key={`sep-${index}`} orientation=\"vertical\" className=\"mx-1 h-5\" />\n }\n\n if (isToolbarGroup(name)) {\n const group = component.toolbarButtonGroups?.[name]\n\n return group ? (\n <ToolbarGroupButton\n key={name}\n group={group}\n editor={editor}\n editable={editable}\n commands={commands}\n icons={iconFor}\n translate={translate}\n onRun={runCommand}\n />\n ) : null\n }\n\n const target = toolbarPanel(name)\n\n if (target) {\n return (\n <ExtensionToolbarButton\n key={name}\n name={target}\n component={component}\n editable={editable}\n open={sidePanel === target}\n onToggle={() => setSidePanel((current) => (current === target ? null : target))}\n />\n )\n }\n\n return renderButton(name)\n }\n\n const floating = editable ? resolveFloatingToolbar(editor, component.floatingToolbars) : null\n // A `table` floating toolbar replaces the table row of buttons.\n const tableRow =\n editor &&\n editable &&\n !component.floatingToolbars?.table &&\n showsTableToolbar(allToolbarButtons(component), editor)\n\n return (\n <FieldWrapper field={component} htmlFor={id} error={error}>\n <div\n className={cn(\n 'border-input dark:bg-input/30 focus-within:border-ring focus-within:ring-ring/50 rounded-md border shadow-xs focus-within:ring-[3px]',\n error && 'border-destructive',\n disabled && 'opacity-60',\n )}\n >\n {component.toolbarButtons.length > 0 && editor && (\n <div\n ref={mainToolbarRef}\n role=\"toolbar\"\n aria-label={t('editor.toolbar', {}, 'Formatting')}\n aria-keyshortcuts=\"Alt+F10\"\n data-slot=\"rich-editor-toolbar\"\n className={cn(\n 'flex flex-wrap items-center gap-0.5 border-b p-1',\n component.stickyToolbar && 'bg-background sticky z-10 rounded-t-md',\n )}\n // The offset is a runtime value: no Tailwind class can hold it.\n style={component.stickyToolbar ? stickyStyle(stickyTop ?? '0px') : undefined}\n >\n {component.toolbarButtons.map(renderEntry)}\n </div>\n )}\n {editor && openPanel === 'textColor' && (\n <div\n role=\"group\"\n aria-label={toolbarButtonLabel('textColor', translate)}\n className=\"flex flex-wrap items-center gap-1 border-b p-1\"\n data-slot=\"rich-editor-text-colors\"\n >\n {(component.textColors ?? []).map((color) => (\n <button\n key={color.value}\n type=\"button\"\n title={color.label}\n aria-label={color.label}\n aria-pressed={editor.getAttributes('textColor').color === color.value}\n className=\"focus-visible:ring-ring/50 size-6 rounded-full border shadow-xs outline-none focus-visible:ring-[3px] aria-pressed:ring-2\"\n // Palette colours are runtime values: no Tailwind class can hold them.\n style={{ backgroundColor: color.value }}\n onMouseDown={(event) => event.preventDefault()}\n onClick={() => {\n setTextColor(editor, color.value)\n setOpenPanel(null)\n }}\n />\n ))}\n {component.customTextColors && (\n <input\n type=\"color\"\n aria-label={t('editor.custom_color', {}, 'Custom color')}\n title={t('editor.custom_color', {}, 'Custom color')}\n className=\"size-6 cursor-pointer rounded border bg-transparent p-0\"\n onChange={(event) => setTextColor(editor, event.target.value)}\n />\n )}\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={t('editor.remove_color', {}, 'Remove color')}\n title={t('editor.remove_color', {}, 'Remove color')}\n onMouseDown={(event) => event.preventDefault()}\n onClick={() => {\n setTextColor(editor, null)\n setOpenPanel(null)\n }}\n >\n <EraserIcon />\n </Button>\n </div>\n )}\n {tableRow && (\n <div\n ref={tableToolbarRef}\n role=\"toolbar\"\n aria-label={toolbarButtonLabel('table', translate)}\n className=\"flex flex-wrap items-center gap-0.5 border-b p-1\"\n data-slot=\"rich-editor-table-toolbar\"\n >\n {TABLE_TOOLBAR_BUTTONS.map((name) => renderButton(name))}\n </div>\n )}\n <div className=\"@container\">\n <div className=\"flex flex-col-reverse @2xl:flex-row\">\n <EditorContent\n editor={editor}\n className=\"min-w-0 flex-1\"\n style={\n component.maxHeight\n ? { maxHeight: component.maxHeight, overflowY: 'auto' }\n : undefined\n }\n />\n {editor && sidePanel && (\n <RichEditorSidePanel\n panel={sidePanel}\n component={component}\n editable={editable}\n sticky={component.stickyPanels ? (stickyTop ?? '0px') : null}\n onInsertBlock={(blockId) => void richExtensions.insertBlock(blockId)}\n onInsertMergeTag={(tagId) =>\n editor\n .chain()\n .focus()\n .insertContent({ type: 'mergeTag', attrs: { id: tagId } })\n .run()\n }\n onClose={() => setSidePanel(null)}\n />\n )}\n </div>\n </div>\n {editor && floating && floating.buttons.length > 0 && (\n <div\n ref={floatingToolbarRef}\n role=\"toolbar\"\n aria-label={t('editor.floating_toolbar', {}, 'Selection formatting')}\n data-slot=\"rich-editor-floating-toolbar\"\n data-node={floating.node}\n className=\"bg-popover text-popover-foreground fixed z-50 flex items-center gap-0.5 rounded-md border p-1 shadow-md\"\n // Runtime coordinates cannot be Tailwind classes.\n style={floatingToolbarStyle(floating.coords)}\n >\n {floating.buttons.map(renderEntry)}\n </div>\n )}\n <SuggestionPopup\n suggestions={richExtensions.suggestions}\n editor={editor}\n id={`${id}-suggestions`}\n />\n {attachmentError && (\n <p role=\"alert\" className=\"text-destructive px-3 pb-2 text-xs\">\n {attachmentError}\n </p>\n )}\n <input\n ref={fileInput}\n type=\"file\"\n accept={(component.fileAttachments.acceptedFileTypes ?? ['image/*']).join(',')}\n className=\"sr-only\"\n tabIndex={-1}\n aria-hidden=\"true\"\n onChange={(event) => event.target.files?.[0] && void uploadImage(event.target.files[0])}\n />\n </div>\n </FieldWrapper>\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"rich-editor.js","names":[],"sources":["../../../src/components/fields/rich-editor.tsx"],"sourcesContent":["import type {\n LoadedRichEditorExtensions,\n RichEditorPanel,\n RichEditorSchema,\n ToolCommandContext,\n} from '@alfakit/core'\nimport {\n allToolbarButtons,\n attachToolbarKeyboard,\n commandLabel,\n detailsNodeConfigs,\n documentNonce,\n EditorAttachments,\n fieldIds,\n fileAttachmentError,\n floatingToolbarStyle,\n focusToolbar,\n gridNodeConfigs,\n highlightMarkConfig,\n htmlToMarkdown,\n isToolbarGroup,\n isToolbarShortcut,\n leadExtensionConfig,\n linkExtensionOptions,\n loadRichEditorExtensions,\n panelUploadOptions,\n resizableImageAttributes,\n resizableImageOptions,\n resolveFloatingToolbar,\n resolveStickyOffset,\n setTextColor,\n showsTableToolbar,\n smallMarkConfig,\n subscriptMarkConfig,\n superscriptMarkConfig,\n TABLE_TOOLBAR_BUTTONS,\n textAlignExtensionConfig,\n textColorMarkConfig,\n toEditorDocument,\n toolbarButtonLabel,\n toolbarCommands,\n toolbarPanel,\n uploadTemporaryFile,\n} from '@alfakit/core'\nimport Image from '@tiptap/extension-image'\nimport Link from '@tiptap/extension-link'\nimport Placeholder from '@tiptap/extension-placeholder'\nimport { TableKit } from '@tiptap/extension-table'\nimport {\n EditorContent,\n Extension,\n Mark,\n Node,\n useEditor,\n type Editor,\n type Extensions,\n} from '@tiptap/react'\nimport StarterKit from '@tiptap/starter-kit'\nimport {\n ALargeSmallIcon,\n BaselineIcon,\n BetweenHorizontalEndIcon,\n BetweenHorizontalStartIcon,\n BetweenVerticalEndIcon,\n BetweenVerticalStartIcon,\n BoldIcon,\n CodeIcon,\n Columns2Icon,\n Columns3Icon,\n EraserIcon,\n Grid2x2XIcon,\n Heading1Icon,\n Heading2Icon,\n Heading3Icon,\n Heading4Icon,\n Heading5Icon,\n Heading6Icon,\n HighlighterIcon,\n ImageIcon,\n ItalicIcon,\n LinkIcon,\n ListCollapseIcon,\n ListIcon,\n ListOrderedIcon,\n Minus,\n PanelTopIcon,\n PilcrowIcon,\n QuoteIcon,\n RedoIcon,\n RemoveFormattingIcon,\n Rows3Icon,\n SquareCodeIcon,\n SquareXIcon,\n StrikethroughIcon,\n SubscriptIcon,\n SuperscriptIcon,\n TableCellsMergeIcon,\n TableCellsSplitIcon,\n TableIcon,\n TablePropertiesIcon,\n TextAlignCenterIcon,\n TextAlignEndIcon,\n TextAlignJustifyIcon,\n TextAlignStartIcon,\n TypeIcon,\n UnderlineIcon,\n UndoIcon,\n} from 'lucide-react'\nimport { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from 'react'\nimport { useActionRunner, usePanel, useT } from '../../context'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport { useField, useOptionalFormStore } from '../form/form-context'\nimport { FieldWrapper } from '../schema/field-wrapper'\nimport { Button } from '../ui/button'\nimport { Separator } from '../ui/separator'\nimport { Skeleton } from '../ui/skeleton'\nimport {\n ExtensionToolbarButton,\n RichEditorSidePanel,\n stickyStyle,\n SuggestionPopup,\n ToolbarGroupButton,\n useRichEditorExtensions,\n} from './rich-editor-extensions'\n\ntype MarkConfig = Parameters<typeof Mark.create>[0]\ntype NodeConfig = Parameters<typeof Node.create>[0]\ntype ExtensionConfig = Parameters<typeof Extension.create>[0]\n\n/** Toolbar button icons; what each button does lives in `@alfakit/core` (`TOOLBAR_COMMANDS`). */\nconst ICONS: Record<string, ReactNode> = {\n bold: <BoldIcon />,\n italic: <ItalicIcon />,\n underline: <UnderlineIcon />,\n strike: <StrikethroughIcon />,\n subscript: <SubscriptIcon />,\n superscript: <SuperscriptIcon />,\n code: <CodeIcon />,\n highlight: <HighlighterIcon />,\n textColor: <BaselineIcon />,\n link: <LinkIcon />,\n h1: <Heading1Icon />,\n h2: <Heading2Icon />,\n h3: <Heading3Icon />,\n h4: <Heading4Icon />,\n h5: <Heading5Icon />,\n h6: <Heading6Icon />,\n paragraph: <TypeIcon />,\n heading: <Heading2Icon />,\n // Alignment and column icons point at a side: mirror them right to left.\n alignStart: <TextAlignStartIcon className=\"rtl:-scale-x-100\" />,\n alignCenter: <TextAlignCenterIcon />,\n alignEnd: <TextAlignEndIcon className=\"rtl:-scale-x-100\" />,\n alignJustify: <TextAlignJustifyIcon />,\n blockquote: <QuoteIcon />,\n codeBlock: <SquareCodeIcon />,\n bulletList: <ListIcon />,\n orderedList: <ListOrderedIcon />,\n horizontalRule: <Minus />,\n details: <ListCollapseIcon />,\n lead: <PilcrowIcon />,\n small: <ALargeSmallIcon />,\n grid: <Columns2Icon />,\n gridAddColumnBefore: <BetweenVerticalStartIcon className=\"rtl:-scale-x-100\" />,\n gridAddColumnAfter: <BetweenVerticalEndIcon className=\"rtl:-scale-x-100\" />,\n gridDeleteColumn: <Columns3Icon />,\n gridDelete: <SquareXIcon />,\n table: <TableIcon />,\n tableAddColumnBefore: <BetweenVerticalStartIcon className=\"rtl:-scale-x-100\" />,\n tableAddColumnAfter: <BetweenVerticalEndIcon className=\"rtl:-scale-x-100\" />,\n tableDeleteColumn: <Columns3Icon />,\n tableAddRowBefore: <BetweenHorizontalStartIcon />,\n tableAddRowAfter: <BetweenHorizontalEndIcon />,\n tableDeleteRow: <Rows3Icon />,\n tableMergeCells: <TableCellsMergeIcon />,\n tableSplitCell: <TableCellsSplitIcon />,\n tableToggleHeaderRow: <PanelTopIcon />,\n tableToggleHeaderCell: <TablePropertiesIcon />,\n tableDelete: <Grid2x2XIcon />,\n clearFormatting: <RemoveFormattingIcon />,\n attachFiles: <ImageIcon />,\n undo: <UndoIcon />,\n redo: <RedoIcon />,\n}\n\n/**\n * Resize handles of `resizableImages()` (Tiptap's node view adds bare `data-resize-handle`\n * elements): round dots on the corners, shown on hover and while the image is selected.\n */\nconst RESIZE_HANDLE_CLASSES =\n '[&_[data-resize-handle]]:z-10 [&_[data-resize-handle]]:size-3 [&_[data-resize-handle]]:rounded-full [&_[data-resize-handle]]:border-2 [&_[data-resize-handle]]:border-background [&_[data-resize-handle]]:bg-primary [&_[data-resize-handle]]:opacity-0 [&_[data-resize-container]:hover_[data-resize-handle]]:opacity-100 [&_.ProseMirror-selectednode_[data-resize-handle]]:opacity-100 [&_[data-resize-handle=bottom-right]]:cursor-nwse-resize [&_[data-resize-handle=top-left]]:cursor-nwse-resize [&_[data-resize-handle=bottom-left]]:cursor-nesw-resize [&_[data-resize-handle=top-right]]:cursor-nesw-resize'\n\n/** The image extension, with drag handles for `resizableImages()`. */\nexport function imageExtension(resizable: boolean) {\n if (!resizable) return Image\n\n return Image.extend({\n addAttributes() {\n return resizableImageAttributes(this.parent?.() ?? {})\n },\n }).configure(resizableImageOptions())\n}\n\n/** Extensions for the extra toolbar buttons (only those the toolbar uses are loaded). */\nexport function toolbarExtensions(buttons: string[], markdown: boolean): Extensions {\n const has = (...names: string[]) => names.some((name) => buttons.includes(name))\n const extensions: Extensions = []\n\n if (has('table')) extensions.push(TableKit.configure({ table: { resizable: false } }))\n // Markdown has no syntax for these.\n if (markdown) return extensions\n\n if (has('highlight')) extensions.push(Mark.create(highlightMarkConfig() as MarkConfig))\n if (has('subscript')) extensions.push(Mark.create(subscriptMarkConfig() as MarkConfig))\n if (has('superscript')) extensions.push(Mark.create(superscriptMarkConfig() as MarkConfig))\n if (has('textColor')) extensions.push(Mark.create(textColorMarkConfig() as MarkConfig))\n if (has('alignStart', 'alignCenter', 'alignEnd', 'alignJustify')) {\n extensions.push(Extension.create(textAlignExtensionConfig() as ExtensionConfig))\n }\n if (has('details')) {\n extensions.push(...detailsNodeConfigs().map((config) => Node.create(config as NodeConfig)))\n }\n if (has('small')) extensions.push(Mark.create(smallMarkConfig() as MarkConfig))\n if (has('lead')) extensions.push(Extension.create(leadExtensionConfig() as ExtensionConfig))\n if (has('grid', 'gridDelete', 'gridAddColumnBefore', 'gridAddColumnAfter', 'gridDeleteColumn')) {\n extensions.push(...gridNodeConfigs().map((config) => Node.create(config as NodeConfig)))\n }\n\n return extensions\n}\n\nconst NO_EXTENSIONS: LoadedRichEditorExtensions = { extensions: [], commands: {} }\n\n/**\n * The rich and Markdown editors. Plugin extensions (`RichEditor::plugins()`) load before\n * the editor is created, since Tiptap extensions can't be added afterwards.\n */\nexport function RichEditorField({ component }: { component: RichEditorSchema }) {\n const keys = component.type === 'MarkdownEditor' ? [] : (component.extensions ?? [])\n const signature = keys.join('|')\n const [loaded, setLoaded] = useState<{\n signature: string\n value: LoadedRichEditorExtensions\n } | null>(() => (keys.length === 0 ? { signature, value: NO_EXTENSIONS } : null))\n\n useEffect(() => {\n if (loaded?.signature === signature) return\n\n let cancelled = false\n\n void loadRichEditorExtensions(signature === '' ? [] : signature.split('|')).then((value) => {\n if (!cancelled) setLoaded({ signature, value })\n })\n\n return () => {\n cancelled = true\n }\n }, [signature, loaded?.signature])\n\n if (loaded?.signature !== signature) {\n return <RichEditorLoading component={component} />\n }\n\n return <RichEditorInput key={signature} component={component} plugins={loaded.value} />\n}\n\nfunction RichEditorLoading({ component }: { component: RichEditorSchema }) {\n const { id, error } = useField<unknown>(component)\n\n return (\n <FieldWrapper field={component} htmlFor={id} error={error}>\n <Skeleton className=\"h-40 w-full\" aria-hidden=\"true\" />\n </FieldWrapper>\n )\n}\n\nfunction RichEditorInput({\n component,\n plugins,\n}: {\n component: RichEditorSchema\n plugins: LoadedRichEditorExtensions\n}) {\n const t = useT()\n const panel = usePanel()\n const formStore = useOptionalFormStore()\n const runner = useActionRunner()\n const { value, setValue, blur, error, id, disabled, readOnly, aria } =\n useField<unknown>(component)\n const fileInput = useRef<HTMLInputElement>(null)\n // ProseMirror reads its DOM attributes on every view update; the ref keeps them current.\n const ariaRef = useRef(aria)\n const isMarkdown = component.type === 'MarkdownEditor'\n // Uploaded images preview from a local URL but are stored by their `tmp:` key.\n const [attachments] = useState(() => new EditorAttachments())\n const editable = !disabled && !readOnly\n const isJson = Boolean(component.json) && !isMarkdown\n // The editor's live instance, for the custom block node views and suggestions.\n const editorRef = useRef<Editor | null>(null)\n const richExtensions = useRichEditorExtensions(component, editorRef)\n // The open toolbar panel (the text colour palette).\n const [openPanel, setOpenPanel] = useState<'textColor' | null>(null)\n // The open side panel (custom blocks or merge tags).\n const [sidePanel, setSidePanel] = useState<RichEditorPanel | null>(() => {\n const initial = component.activePanel ?? null\n\n if (initial === 'customBlocks') return component.customBlocks?.length ? initial : null\n if (initial === 'mergeTags') return component.mergeTags?.length ? initial : null\n\n return null\n })\n const [attachmentError, setAttachmentError] = useState<string | null>(null)\n // Re-render on selection changes so active states and the table row follow the cursor.\n const [, setSelectionTick] = useState(0)\n const sticky = Boolean(component.stickyToolbar || component.stickyPanels)\n const contextRef = useRef(richExtensions.context)\n // Keyboard access to the toolbars (roving tabindex, arrows, Escape, Alt+F10).\n const toolbarElements = useRef<Record<'main' | 'table' | 'floating', HTMLElement | null>>({\n main: null,\n table: null,\n floating: null,\n })\n const toolbars = useCallback(\n () => [\n toolbarElements.current.floating,\n toolbarElements.current.main,\n toolbarElements.current.table,\n ],\n [],\n )\n const attachKeyboard = useCallback(\n (slot: 'main' | 'table' | 'floating', element: HTMLElement | null) => {\n toolbarElements.current[slot] = element\n\n if (!element) return\n\n const detach = attachToolbarKeyboard(element, {\n onEscape: () => editorRef.current?.commands.focus(),\n toolbars,\n })\n\n return () => {\n detach()\n if (toolbarElements.current[slot] === element) toolbarElements.current[slot] = null\n }\n },\n [toolbars],\n )\n const mainToolbarRef = useCallback(\n (element: HTMLElement | null) => attachKeyboard('main', element),\n [attachKeyboard],\n )\n const tableToolbarRef = useCallback(\n (element: HTMLElement | null) => attachKeyboard('table', element),\n [attachKeyboard],\n )\n const floatingToolbarRef = useCallback(\n (element: HTMLElement | null) => attachKeyboard('floating', element),\n [attachKeyboard],\n )\n\n useEffect(() => {\n contextRef.current = richExtensions.context\n })\n\n // How plugin tools open their editor action. The refs are read when a tool runs (on\n // click), never during render.\n const [toolContext] = useState<ToolCommandContext>(() => ({\n runAction: (tool, args) =>\n tool.action\n ? runner.runField(tool.action, { ...contextRef.current, arguments: args })\n : Promise.resolve(undefined),\n editor: () => editorRef.current,\n }))\n\n // Built-in buttons, plugin tools and commands of plugin JavaScript extensions.\n const commands = useMemo(\n () => toolbarCommands(component.editorTools, toolContext, plugins.commands),\n [component.editorTools, plugins.commands, toolContext],\n )\n\n // Field state ↔ editor content: HTML, Markdown (loaded from the server's HTML) or a\n // Tiptap JSON document. `tmp:` upload keys become local preview URLs and back.\n const stateToContent = (): string | Record<string, unknown> => {\n if (isJson) {\n const document = toEditorDocument(value)\n\n return document ? JSON.parse(attachments.toEditor(JSON.stringify(document))) : ''\n }\n\n const markdownHtml = typeof component.html === 'string' ? component.html : null\n\n return attachments.toEditor(((isMarkdown ? markdownHtml : value) as string | null) ?? '')\n }\n const editorToState = (instance: Editor): unknown => {\n if (instance.isEmpty) return null\n if (isJson) return JSON.parse(attachments.toState(JSON.stringify(instance.getJSON())))\n\n const html = instance.getHTML()\n\n return attachments.toState(isMarkdown ? htmlToMarkdown(html) : html)\n }\n\n useEffect(() => () => attachments.destroy(), [attachments])\n\n const editor = useEditor({\n // TipTap injects its base CSS; with the page's CSP nonce a nonce-based policy allows it.\n injectNonce: documentNonce(),\n extensions: [\n StarterKit.configure({ link: false }),\n Link.configure(linkExtensionOptions(component.linkProtocols)),\n imageExtension(Boolean(component.resizableImages) && !isMarkdown),\n Placeholder.configure({ placeholder: component.placeholder ?? '' }),\n ...toolbarExtensions(allToolbarButtons(component), isMarkdown),\n ...(isMarkdown ? [] : richExtensions.extensions),\n ...(isMarkdown ? [] : (plugins.extensions as Extensions)),\n ],\n // Markdown is loaded from the server's HTML rendering of it, never parsed as HTML.\n content: stateToContent(),\n editable,\n editorProps: {\n // The mention / merge tag popup takes the arrow, Enter, Tab and Escape keys.\n handleKeyDown: (_view, event) => {\n // Alt+F10: into the floating toolbar, else the main one.\n if (isToolbarShortcut(event)) {\n return focusToolbar(toolbars())\n }\n\n return editorRef.current\n ? richExtensions.suggestions.handleKeyDown(event, editorRef.current)\n : false\n },\n attributes: () => ({\n class: cn(\n 'alfakit-prose min-h-32 px-3 py-2 text-sm outline-none',\n component.resizableImages && RESIZE_HANDLE_CLASSES,\n ),\n // Runtime sizes cannot be Tailwind classes (they would never be generated).\n ...(component.minHeight ? { style: `min-height: ${component.minHeight}px` } : {}),\n id,\n // Each paragraph follows its own script, so Arabic and English mix correctly.\n dir: 'auto',\n role: 'textbox',\n 'aria-multiline': 'true',\n ...(component.label ? { 'aria-labelledby': fieldIds(id).label } : {}),\n ...Object.fromEntries(\n Object.entries(ariaRef.current).map(([name, attribute]) => [name, String(attribute)]),\n ),\n }),\n },\n onUpdate: ({ editor: instance }) => {\n setValue(editorToState(instance))\n },\n onTransaction: ({ editor: instance }) => {\n richExtensions.suggestions.update(instance)\n setSelectionTick((tick) => tick + 1)\n },\n onBlur: () => {\n richExtensions.suggestions.close()\n blur()\n },\n })\n\n useEffect(() => {\n editorRef.current = editor\n }, [editor])\n\n // Keep editor in sync when the server refreshes the state.\n useEffect(() => {\n // Tiptap may replace the instance on mount (e.g. when the editor loads lazily); the\n // replaced one has no schema left, and the new one starts from the current value.\n if (!editor?.schema) return\n\n const current = editorToState(editor)\n const same = isJson\n ? JSON.stringify(toEditorDocument(value)) === JSON.stringify(current)\n : (value ?? '') === (current ?? '')\n\n if (!same) {\n editor.commands.setContent(stateToContent(), { emitUpdate: false })\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [value])\n\n useEffect(() => {\n editor?.setEditable(editable)\n }, [editor, editable])\n\n // Re-apply the editable element's attributes when the error or description changes.\n useEffect(() => {\n ariaRef.current = aria\n\n if (editor && !editor.isDestroyed) {\n editor.view.setProps({})\n }\n }, [editor, aria])\n\n // Measured in the browser once the editor exists (the toolbar only renders then).\n const stickyTop = useMemo(\n () => (sticky && editor ? resolveStickyOffset(component.stickyOffset) : null),\n [sticky, editor, component.stickyOffset],\n )\n\n // A floating toolbar follows its selection while the page scrolls.\n const hasFloatingToolbars = Boolean(component.floatingToolbars)\n\n useEffect(() => {\n if (!hasFloatingToolbars) return\n\n const onScroll = () => setSelectionTick((tick) => tick + 1)\n\n window.addEventListener('scroll', onScroll, { capture: true, passive: true })\n\n return () => window.removeEventListener('scroll', onScroll, { capture: true })\n }, [hasFloatingToolbars])\n\n const pickImage = () => fileInput.current?.click()\n\n // Submits wait for the attachment's upload, so saving now doesn't drop the image.\n const uploadImage = (file: File) =>\n formStore ? formStore.trackUpload(sendImage(file)) : sendImage(file)\n\n const sendImage = async (file: File) => {\n if (!panel.endpoints.upload || !editor) return\n\n // The server checks again when the form is saved.\n const problem = fileAttachmentError(file, component.fileAttachments)\n\n if (problem) {\n setAttachmentError(\n problem === 'too_large'\n ? t('form.file_too_large', {}, 'This file is too large.')\n : t('form.file_invalid_type', {}, 'This file type is not allowed.'),\n )\n\n return\n }\n\n setAttachmentError(null)\n\n let response: { key: string }\n\n try {\n // Large attachments go in resumable chunks.\n response = await uploadTemporaryFile(\n panel.endpoints.upload,\n file,\n panelUploadOptions(panel, component.fileAttachments.uploadPolicy),\n )\n } catch (error) {\n setAttachmentError(\n (error as { errors?: Record<string, string[]> }).errors?.file?.[0] ??\n t('form.upload_failed', {}, 'The file could not be uploaded.'),\n )\n\n return\n }\n\n const src = attachments.add(response.key, file)\n\n editor.chain().focus().setImage({ src, alt: file.name }).run()\n }\n\n const translate = (key: string, fallback: string) => t(key, {}, fallback)\n\n const iconFor = (name: string): ReactNode => {\n if (ICONS[name]) return ICONS[name]\n\n const tool = component.editorTools?.find((candidate) => candidate.name === name)\n\n return tool?.icon ? (\n <Icon name={tool.icon} />\n ) : (\n <span className=\"text-xs\">{commandLabel(name, commands, translate)}</span>\n )\n }\n\n const runCommand = (name: string) => {\n const button = commands[name]\n\n if (!button || !editor) return\n\n button.run(editor, {\n // Messages arrive translated (`form.link_prompt`, `editor.grid_columns`).\n prompt: (message) => window.prompt(message),\n pickImage,\n togglePanel: (target) => setOpenPanel((current) => (current === target ? null : target)),\n t: translate,\n })\n }\n\n const renderButton = (name: string) => {\n const button = commands[name]\n\n if (!button || !editor) return null\n\n const label = commandLabel(name, commands, translate)\n const active = button.active?.(editor) || (name === 'textColor' && openPanel === name)\n\n return (\n <Button\n key={name}\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={label}\n title={label}\n aria-pressed={button.active || name === 'textColor' ? Boolean(active) : undefined}\n disabled={!editable}\n className={cn(active && 'bg-accent text-accent-foreground')}\n onMouseDown={(event) => event.preventDefault()}\n onClick={() => runCommand(name)}\n >\n {iconFor(name)}\n </Button>\n )\n }\n\n /** A toolbar entry: separator, group dropdown, panel toggle or button. */\n const renderEntry = (name: string, index: number) => {\n if (!editor) return null\n\n if (name === '|') {\n return <Separator key={`sep-${index}`} orientation=\"vertical\" className=\"mx-1 h-5\" />\n }\n\n if (isToolbarGroup(name)) {\n const group = component.toolbarButtonGroups?.[name]\n\n return group ? (\n <ToolbarGroupButton\n key={name}\n group={group}\n editor={editor}\n editable={editable}\n commands={commands}\n icons={iconFor}\n translate={translate}\n onRun={runCommand}\n />\n ) : null\n }\n\n const target = toolbarPanel(name)\n\n if (target) {\n return (\n <ExtensionToolbarButton\n key={name}\n name={target}\n component={component}\n editable={editable}\n open={sidePanel === target}\n onToggle={() => setSidePanel((current) => (current === target ? null : target))}\n />\n )\n }\n\n return renderButton(name)\n }\n\n const floating = editable ? resolveFloatingToolbar(editor, component.floatingToolbars) : null\n // A `table` floating toolbar replaces the table row of buttons.\n const tableRow =\n editor &&\n editable &&\n !component.floatingToolbars?.table &&\n showsTableToolbar(allToolbarButtons(component), editor)\n\n return (\n <FieldWrapper field={component} htmlFor={id} error={error}>\n <div\n className={cn(\n 'border-input dark:bg-input/30 focus-within:border-ring focus-within:ring-ring/50 rounded-md border shadow-xs focus-within:ring-[3px]',\n error && 'border-destructive',\n disabled && 'opacity-60',\n )}\n >\n {component.toolbarButtons.length > 0 && editor && (\n <div\n ref={mainToolbarRef}\n role=\"toolbar\"\n aria-label={t('editor.toolbar', {}, 'Formatting')}\n aria-keyshortcuts=\"Alt+F10\"\n data-slot=\"rich-editor-toolbar\"\n className={cn(\n 'flex flex-wrap items-center gap-0.5 border-b p-1',\n component.stickyToolbar && 'bg-background sticky z-10 rounded-t-md',\n )}\n // The offset is a runtime value: no Tailwind class can hold it.\n style={component.stickyToolbar ? stickyStyle(stickyTop ?? '0px') : undefined}\n >\n {component.toolbarButtons.map(renderEntry)}\n </div>\n )}\n {editor && openPanel === 'textColor' && (\n <div\n role=\"group\"\n aria-label={toolbarButtonLabel('textColor', translate)}\n className=\"flex flex-wrap items-center gap-1 border-b p-1\"\n data-slot=\"rich-editor-text-colors\"\n >\n {(component.textColors ?? []).map((color) => (\n <button\n key={color.value}\n type=\"button\"\n title={color.label}\n aria-label={color.label}\n aria-pressed={editor.getAttributes('textColor').color === color.value}\n className=\"focus-visible:ring-ring/50 size-6 rounded-full border shadow-xs outline-none focus-visible:ring-[3px] aria-pressed:ring-2\"\n // Palette colours are runtime values: no Tailwind class can hold them.\n style={{ backgroundColor: color.value }}\n onMouseDown={(event) => event.preventDefault()}\n onClick={() => {\n setTextColor(editor, color.value)\n setOpenPanel(null)\n }}\n />\n ))}\n {component.customTextColors && (\n <input\n type=\"color\"\n aria-label={t('editor.custom_color', {}, 'Custom color')}\n title={t('editor.custom_color', {}, 'Custom color')}\n className=\"size-6 cursor-pointer rounded border bg-transparent p-0\"\n onChange={(event) => setTextColor(editor, event.target.value)}\n />\n )}\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={t('editor.remove_color', {}, 'Remove color')}\n title={t('editor.remove_color', {}, 'Remove color')}\n onMouseDown={(event) => event.preventDefault()}\n onClick={() => {\n setTextColor(editor, null)\n setOpenPanel(null)\n }}\n >\n <EraserIcon />\n </Button>\n </div>\n )}\n {tableRow && (\n <div\n ref={tableToolbarRef}\n role=\"toolbar\"\n aria-label={toolbarButtonLabel('table', translate)}\n className=\"flex flex-wrap items-center gap-0.5 border-b p-1\"\n data-slot=\"rich-editor-table-toolbar\"\n >\n {TABLE_TOOLBAR_BUTTONS.map((name) => renderButton(name))}\n </div>\n )}\n <div className=\"@container\">\n <div className=\"flex flex-col-reverse @2xl:flex-row\">\n <EditorContent\n editor={editor}\n className=\"min-w-0 flex-1\"\n style={\n component.maxHeight\n ? { maxHeight: component.maxHeight, overflowY: 'auto' }\n : undefined\n }\n />\n {editor && sidePanel && (\n <RichEditorSidePanel\n panel={sidePanel}\n component={component}\n editable={editable}\n sticky={component.stickyPanels ? (stickyTop ?? '0px') : null}\n onInsertBlock={(blockId) => void richExtensions.insertBlock(blockId)}\n onInsertMergeTag={(tagId) =>\n editor\n .chain()\n .focus()\n .insertContent({ type: 'mergeTag', attrs: { id: tagId } })\n .run()\n }\n onClose={() => setSidePanel(null)}\n />\n )}\n </div>\n </div>\n {editor && floating && floating.buttons.length > 0 && (\n <div\n ref={floatingToolbarRef}\n role=\"toolbar\"\n aria-label={t('editor.floating_toolbar', {}, 'Selection formatting')}\n data-slot=\"rich-editor-floating-toolbar\"\n data-node={floating.node}\n className=\"bg-popover text-popover-foreground fixed z-50 flex items-center gap-0.5 rounded-md border p-1 shadow-md\"\n // Runtime coordinates cannot be Tailwind classes.\n style={floatingToolbarStyle(floating.coords)}\n >\n {floating.buttons.map(renderEntry)}\n </div>\n )}\n <SuggestionPopup\n suggestions={richExtensions.suggestions}\n editor={editor}\n id={`${id}-suggestions`}\n />\n {attachmentError && (\n <p role=\"alert\" className=\"text-destructive px-3 pb-2 text-xs\">\n {attachmentError}\n </p>\n )}\n <input\n ref={fileInput}\n type=\"file\"\n accept={(component.fileAttachments.acceptedFileTypes ?? ['image/*']).join(',')}\n className=\"sr-only\"\n tabIndex={-1}\n aria-hidden=\"true\"\n onChange={(event) => event.target.files?.[0] && void uploadImage(event.target.files[0])}\n />\n </div>\n </FieldWrapper>\n )\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slider.js","names":["SliderPrimitive"],"sources":["../../../src/components/fields/slider.tsx"],"sourcesContent":["import type { SliderSchema } from '@alfakit/core'\nimport {\n fieldIds,\n sliderBehavior,\n sliderBlocksTap,\n sliderDirection,\n sliderFillSections,\n sliderFillStyle,\n sliderFromPrimitive,\n sliderHasTooltips,\n sliderNextValues,\n sliderPips,\n sliderPrimitive,\n sliderShowsTooltip,\n sliderSnap,\n sliderState,\n sliderToPrimitive,\n sliderValues,\n startSliderRangeDrag,\n} from '@alfakit/core'\nimport { Slider as SliderPrimitive } from 'radix-ui'\nimport { useMemo, useRef, useState, type PointerEvent } from 'react'\nimport { useDirection, useFormatter, useT } from '../../context'\nimport { cn } from '../../lib/utils'\nimport { useField } from '../form/form-context'\nimport { FieldWrapper } from '../schema/field-wrapper'\n\n/**\n * A slider with one thumb, or several for a range. Built on Radix Slider, which handles\n * pointer and keyboard input and mirrors itself in right-to-left layouts (`dir`). Values,\n * tooltips and pips are formatted with the panel's locale.\n */\nexport function SliderField({ component }: { component: SliderSchema }) {\n const t = useT()\n // `rtl()` overrides the panel's direction.\n const dir = sliderDirection(component, useDirection())\n const formatter = useFormatter()\n const { value, setValue, blur, error, id, disabled, readOnly, aria } =\n useField<unknown>(component)\n const values = sliderValues(value, component)\n const pips = useMemo(() => (component.pips ? sliderPips(component) : []), [component])\n const vertical = Boolean(component.vertical)\n // `vertical(isTopToBottom: true)`: the minimum at the top.\n const topToBottom = vertical && Boolean(component.topToBottom)\n const behavior = sliderBehavior(component)\n const trackRef = useRef<HTMLSpanElement>(null)\n // `Behavior::SmoothSteps`: no snapping while a pointer drags a thumb.\n const [smooth, setSmooth] = useState(false)\n const primitive = sliderPrimitive(component, smooth)\n const locked = disabled || readOnly\n\n const change = (next: number[]) =>\n setValue(sliderState(sliderNextValues(values, next, component), component))\n\n // Before the primitive sees the press: start a range drag, or swallow taps on the track.\n const onPointerDownCapture = (event: PointerEvent<HTMLDivElement>) => {\n if (locked) return\n\n const target = event.target as Element\n\n if (behavior.has('drag') && target.closest('[data-slot=\"slider-range\"]') && trackRef.current) {\n startSliderRangeDrag(event.nativeEvent, {\n track: trackRef.current,\n values,\n schema: component,\n direction: dir,\n vertical,\n topToBottom,\n onChange: (next) => setValue(sliderState(next, component)),\n onEnd: () => blur(),\n })\n\n return\n }\n\n if (sliderBlocksTap(target, component)) {\n event.stopPropagation()\n\n return\n }\n\n if (behavior.has('smooth-steps') && target.closest('[role=\"slider\"]')) setSmooth(true)\n }\n const format = (number: number) =>\n formatter.number(number, {\n maximumFractionDigits: component.decimalPlaces ?? 10,\n minimumFractionDigits: component.decimalPlaces ?? 0,\n })\n const range = component.max - component.min\n // `fillTrack([…])`: chosen sections are drawn here; the primitive's range stays clear.\n const fillSections = sliderFillSections(values, component)\n const fill = fillSections === null && (component.fillTrack === true || values.length > 1)\n\n const thumbLabel = (index: number) => {\n if (values.length === 1) return undefined\n if (values.length === 2) {\n return index === 0 ? t('slider.minimum', {}, 'Minimum') : t('slider.maximum', {}, 'Maximum')\n }\n\n return t('slider.thumb', { number: index + 1 }, `Value ${index + 1}`)\n }\n\n return (\n <FieldWrapper field={component} htmlFor={`${id}-thumb-0`} error={error}>\n <div\n data-slot=\"slider\"\n className={cn(\n 'px-2',\n sliderHasTooltips(component) && !vertical && 'pt-7',\n pips.length > 0 && 'pb-6',\n )}\n onPointerDownCapture={onPointerDownCapture}\n >\n <SliderPrimitive.Root\n id={id}\n dir={dir}\n min={primitive.min}\n max={primitive.max}\n step={primitive.step}\n minStepsBetweenThumbs={\n component.minDifference && !primitive.nonLinear\n ? Math.round(component.minDifference / component.step)\n : 0\n }\n orientation={vertical ? 'vertical' : 'horizontal'}\n inverted={topToBottom}\n disabled={locked}\n value={sliderToPrimitive(values, component)}\n onValueChange={(next) => change(sliderFromPrimitive(next, component, !smooth))}\n onValueCommit={() => {\n if (smooth) {\n setSmooth(false)\n change(values.map((thumb) => sliderSnap(thumb, component)))\n }\n\n blur()\n }}\n className={cn(\n 'relative flex touch-none items-center select-none data-[disabled]:opacity-50',\n vertical ? 'h-48 w-5 flex-col' : 'w-full',\n readOnly && 'data-[disabled]:opacity-100',\n )}\n >\n <SliderPrimitive.Track\n ref={trackRef}\n className={cn(\n 'bg-muted relative grow overflow-hidden rounded-full',\n vertical ? 'h-full w-1.5' : 'h-1.5 w-full',\n )}\n >\n {fillSections?.map((section) => (\n <span\n key={`${section.start}-${section.end}`}\n data-slot=\"slider-fill\"\n aria-hidden=\"true\"\n className={cn('bg-primary absolute', vertical ? 'w-full' : 'h-full')}\n // Runtime geometry: no Tailwind class can hold these values.\n style={sliderFillStyle(section, { vertical, topToBottom, direction: dir })}\n />\n ))}\n <SliderPrimitive.Range\n data-slot=\"slider-range\"\n className={cn(\n 'absolute',\n fill ? 'bg-primary' : 'bg-transparent',\n behavior.has('drag') && values.length > 1 && !locked && 'cursor-grab',\n vertical ? 'w-full' : 'h-full',\n )}\n />\n </SliderPrimitive.Track>\n {values.map((thumb, index) => (\n <SliderPrimitive.Thumb\n key={index}\n id={`${id}-thumb-${index}`}\n aria-label={thumbLabel(index)}\n aria-labelledby={\n values.length === 1 && component.label ? fieldIds(id).label : undefined\n }\n aria-describedby={aria['aria-describedby']}\n aria-valuetext={format(thumb)}\n className=\"border-primary bg-background ring-ring/50 relative block size-4 shrink-0 rounded-full border shadow-sm transition-[color,box-shadow] hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden disabled:pointer-events-none\"\n >\n {sliderShowsTooltip(component, index) && (\n <span\n aria-hidden=\"true\"\n className={cn(\n 'bg-foreground text-background pointer-events-none absolute rounded px-1.5 py-0.5 text-xs whitespace-nowrap tabular-nums',\n vertical\n ? 'start-6 top-1/2 -translate-y-1/2'\n : 'bottom-6 left-1/2 -translate-x-1/2',\n )}\n >\n {format(thumb)}\n </span>\n )}\n </SliderPrimitive.Thumb>\n ))}\n {pips.length > 0 && range > 0 && (\n <div\n aria-hidden=\"true\"\n className={cn(\n 'pointer-events-none absolute',\n vertical ? 'inset-y-0 start-6' : 'inset-x-0 top-4',\n )}\n >\n {pips.map((pip) => {\n // Radix puts the minimum at the bottom of a vertical slider and at the\n // inline start of a horizontal one (the right in RTL).\n const offset = `${pip.percent}%`\n const style = vertical\n ? topToBottom\n ? { top: offset }\n : { bottom: offset }\n : dir === 'rtl'\n ? { right: offset }\n : { left: offset }\n\n return (\n <span\n key={pip.value}\n style={style}\n className={cn(\n 'absolute flex items-center gap-1',\n vertical\n ? cn('flex-row', topToBottom ? '-translate-y-1/2' : 'translate-y-1/2')\n : 'flex-col',\n !vertical && (dir === 'rtl' ? 'translate-x-1/2' : '-translate-x-1/2'),\n )}\n >\n <span\n className={cn(\n 'bg-border block',\n vertical ? 'h-px' : 'w-px',\n pip.labelled && !pip.small\n ? vertical\n ? 'w-2'\n : 'h-2'\n : vertical\n ? 'w-1'\n : 'h-1',\n )}\n />\n {pip.labelled && (\n <span\n className={cn(\n 'text-muted-foreground tabular-nums',\n pip.small ? 'text-[0.625rem]' : 'text-xs',\n )}\n >\n {format(pip.value)}\n </span>\n )}\n </span>\n )\n })}\n </div>\n )}\n </SliderPrimitive.Root>\n </div>\n </FieldWrapper>\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"slider.js","names":["SliderPrimitive"],"sources":["../../../src/components/fields/slider.tsx"],"sourcesContent":["import type { SliderSchema } from '@alfakit/core'\nimport {\n fieldIds,\n sliderBehavior,\n sliderBlocksTap,\n sliderDirection,\n sliderFillSections,\n sliderFillStyle,\n sliderFromPrimitive,\n sliderHasTooltips,\n sliderNextValues,\n sliderPips,\n sliderPrimitive,\n sliderShowsTooltip,\n sliderSnap,\n sliderState,\n sliderToPrimitive,\n sliderValues,\n startSliderRangeDrag,\n} from '@alfakit/core'\nimport { Slider as SliderPrimitive } from 'radix-ui'\nimport { useMemo, useRef, useState, type PointerEvent } from 'react'\nimport { useDirection, useFormatter, useT } from '../../context'\nimport { cn } from '../../lib/utils'\nimport { useField } from '../form/form-context'\nimport { FieldWrapper } from '../schema/field-wrapper'\n\n/**\n * A slider with one thumb, or several for a range. Built on Radix Slider, which handles\n * pointer and keyboard input and mirrors itself in right-to-left layouts (`dir`). Values,\n * tooltips and pips are formatted with the panel's locale.\n */\nexport function SliderField({ component }: { component: SliderSchema }) {\n const t = useT()\n // `rtl()` overrides the panel's direction.\n const dir = sliderDirection(component, useDirection())\n const formatter = useFormatter()\n const { value, setValue, blur, error, id, disabled, readOnly, aria } =\n useField<unknown>(component)\n const values = sliderValues(value, component)\n const pips = useMemo(() => (component.pips ? sliderPips(component) : []), [component])\n const vertical = Boolean(component.vertical)\n // `vertical(isTopToBottom: true)`: the minimum at the top.\n const topToBottom = vertical && Boolean(component.topToBottom)\n const behavior = sliderBehavior(component)\n const trackRef = useRef<HTMLSpanElement>(null)\n // `Behavior::SmoothSteps`: no snapping while a pointer drags a thumb.\n const [smooth, setSmooth] = useState(false)\n const primitive = sliderPrimitive(component, smooth)\n const locked = disabled || readOnly\n\n const change = (next: number[]) =>\n setValue(sliderState(sliderNextValues(values, next, component), component))\n\n // Before the primitive sees the press: start a range drag, or swallow taps on the track.\n const onPointerDownCapture = (event: PointerEvent<HTMLDivElement>) => {\n if (locked) return\n\n const target = event.target as Element\n\n if (behavior.has('drag') && target.closest('[data-slot=\"slider-range\"]') && trackRef.current) {\n startSliderRangeDrag(event.nativeEvent, {\n track: trackRef.current,\n values,\n schema: component,\n direction: dir,\n vertical,\n topToBottom,\n onChange: (next) => setValue(sliderState(next, component)),\n onEnd: () => blur(),\n })\n\n return\n }\n\n if (sliderBlocksTap(target, component)) {\n event.stopPropagation()\n\n return\n }\n\n if (behavior.has('smooth-steps') && target.closest('[role=\"slider\"]')) setSmooth(true)\n }\n const format = (number: number) =>\n formatter.number(number, {\n maximumFractionDigits: component.decimalPlaces ?? 10,\n minimumFractionDigits: component.decimalPlaces ?? 0,\n })\n const range = component.max - component.min\n // `fillTrack([…])`: chosen sections are drawn here; the primitive's range stays clear.\n const fillSections = sliderFillSections(values, component)\n const fill = fillSections === null && (component.fillTrack === true || values.length > 1)\n\n const thumbLabel = (index: number) => {\n if (values.length === 1) return undefined\n if (values.length === 2) {\n return index === 0 ? t('slider.minimum', {}, 'Minimum') : t('slider.maximum', {}, 'Maximum')\n }\n\n return t('slider.thumb', { number: index + 1 }, `Value ${index + 1}`)\n }\n\n return (\n // The wrapper's label, hint and error ids derive from `id`, as `aria` does: a thumb is not\n // labelable, so one thumb names itself through `aria-labelledby`.\n <FieldWrapper field={component} htmlFor={id} error={error}>\n <div\n data-slot=\"slider\"\n className={cn(\n 'px-2',\n sliderHasTooltips(component) && !vertical && 'pt-7',\n pips.length > 0 && 'pb-6',\n )}\n onPointerDownCapture={onPointerDownCapture}\n >\n <SliderPrimitive.Root\n id={id}\n dir={dir}\n min={primitive.min}\n max={primitive.max}\n step={primitive.step}\n minStepsBetweenThumbs={\n component.minDifference && !primitive.nonLinear\n ? Math.round(component.minDifference / component.step)\n : 0\n }\n orientation={vertical ? 'vertical' : 'horizontal'}\n inverted={topToBottom}\n disabled={locked}\n value={sliderToPrimitive(values, component)}\n onValueChange={(next) => change(sliderFromPrimitive(next, component, !smooth))}\n onValueCommit={() => {\n if (smooth) {\n setSmooth(false)\n change(values.map((thumb) => sliderSnap(thumb, component)))\n }\n\n blur()\n }}\n className={cn(\n 'relative flex touch-none items-center select-none data-[disabled]:opacity-50',\n vertical ? 'h-48 w-5 flex-col' : 'w-full',\n readOnly && 'data-[disabled]:opacity-100',\n )}\n >\n <SliderPrimitive.Track\n ref={trackRef}\n className={cn(\n 'bg-muted relative grow overflow-hidden rounded-full',\n vertical ? 'h-full w-1.5' : 'h-1.5 w-full',\n )}\n >\n {fillSections?.map((section) => (\n <span\n key={`${section.start}-${section.end}`}\n data-slot=\"slider-fill\"\n aria-hidden=\"true\"\n className={cn('bg-primary absolute', vertical ? 'w-full' : 'h-full')}\n // Runtime geometry: no Tailwind class can hold these values.\n style={sliderFillStyle(section, { vertical, topToBottom, direction: dir })}\n />\n ))}\n <SliderPrimitive.Range\n data-slot=\"slider-range\"\n className={cn(\n 'absolute',\n fill ? 'bg-primary' : 'bg-transparent',\n behavior.has('drag') && values.length > 1 && !locked && 'cursor-grab',\n vertical ? 'w-full' : 'h-full',\n )}\n />\n </SliderPrimitive.Track>\n {values.map((thumb, index) => (\n <SliderPrimitive.Thumb\n key={index}\n id={`${id}-thumb-${index}`}\n aria-label={thumbLabel(index)}\n aria-labelledby={\n values.length === 1 && component.label ? fieldIds(id).label : undefined\n }\n aria-describedby={aria['aria-describedby']}\n aria-valuetext={format(thumb)}\n className=\"border-primary bg-background ring-ring/50 relative block size-4 shrink-0 rounded-full border shadow-sm transition-[color,box-shadow] hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden disabled:pointer-events-none\"\n >\n {sliderShowsTooltip(component, index) && (\n <span\n aria-hidden=\"true\"\n className={cn(\n 'bg-foreground text-background pointer-events-none absolute rounded px-1.5 py-0.5 text-xs whitespace-nowrap tabular-nums',\n vertical\n ? 'start-6 top-1/2 -translate-y-1/2'\n : 'bottom-6 left-1/2 -translate-x-1/2',\n )}\n >\n {format(thumb)}\n </span>\n )}\n </SliderPrimitive.Thumb>\n ))}\n {pips.length > 0 && range > 0 && (\n <div\n aria-hidden=\"true\"\n className={cn(\n 'pointer-events-none absolute',\n vertical ? 'inset-y-0 start-6' : 'inset-x-0 top-4',\n )}\n >\n {pips.map((pip) => {\n // Radix puts the minimum at the bottom of a vertical slider and at the\n // inline start of a horizontal one (the right in RTL).\n const offset = `${pip.percent}%`\n const style = vertical\n ? topToBottom\n ? { top: offset }\n : { bottom: offset }\n : dir === 'rtl'\n ? { right: offset }\n : { left: offset }\n\n return (\n <span\n key={pip.value}\n style={style}\n className={cn(\n 'absolute flex items-center gap-1',\n vertical\n ? cn('flex-row', topToBottom ? '-translate-y-1/2' : 'translate-y-1/2')\n : 'flex-col',\n !vertical && (dir === 'rtl' ? 'translate-x-1/2' : '-translate-x-1/2'),\n )}\n >\n <span\n className={cn(\n 'bg-border block',\n vertical ? 'h-px' : 'w-px',\n pip.labelled && !pip.small\n ? vertical\n ? 'w-2'\n : 'h-2'\n : vertical\n ? 'w-1'\n : 'h-1',\n )}\n />\n {pip.labelled && (\n <span\n className={cn(\n 'text-muted-foreground tabular-nums',\n pip.small ? 'text-[0.625rem]' : 'text-xs',\n )}\n >\n {format(pip.value)}\n </span>\n )}\n </span>\n )\n })}\n </div>\n )}\n </SliderPrimitive.Root>\n </div>\n </FieldWrapper>\n )\n}\n"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { cn } from "../../lib/utils.js";
|
|
2
|
+
import { useHydrated } from "../../hooks/use-hydrated.js";
|
|
2
3
|
import { useStore } from "../../hooks/use-store.js";
|
|
3
4
|
import { lazyComponent } from "../../lib/lazy.js";
|
|
4
|
-
import { useHydrated } from "../../hooks/use-hydrated.js";
|
|
5
5
|
import { HelpdeskChatClient } from "@alfakit/core";
|
|
6
6
|
import { useEffect, useId, useRef, useState } from "react";
|
|
7
7
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useT } from "../../context.js";
|
|
2
2
|
import { usePageActionContext } from "../actions/action-context.js";
|
|
3
3
|
import { cn } from "../../lib/utils.js";
|
|
4
|
+
import { useHydrated } from "../../hooks/use-hydrated.js";
|
|
4
5
|
import { Icon } from "../../lib/icons.js";
|
|
5
6
|
import { Button } from "../ui/button.js";
|
|
6
7
|
import { extraProps } from "../../lib/attributes.js";
|
|
@@ -10,7 +11,6 @@ import { DeferredSchema } from "./deferred.js";
|
|
|
10
11
|
import { useFieldActionContext } from "../schema/field-actions.js";
|
|
11
12
|
import { SchemaRenderer } from "../schema/schema-renderer.js";
|
|
12
13
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "../ui/collapsible.js";
|
|
13
|
-
import { useHydrated } from "../../hooks/use-hydrated.js";
|
|
14
14
|
import { MixedContent } from "./mixed-content.js";
|
|
15
15
|
import { actionsAlignmentClass, headingTag, readPersisted, schemaGapClass, writePersisted } from "@alfakit/core";
|
|
16
16
|
import { createElement, useState } from "react";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useT } from "../../context.js";
|
|
2
2
|
import { cn } from "../../lib/utils.js";
|
|
3
3
|
import { badgeColorClasses, colorClass } from "../../lib/colors.js";
|
|
4
|
+
import { useHydrated } from "../../hooks/use-hydrated.js";
|
|
4
5
|
import { Icon } from "../../lib/icons.js";
|
|
5
6
|
import { Badge } from "../ui/badge.js";
|
|
6
7
|
import { useStoreSelector } from "../../hooks/use-store.js";
|
|
@@ -9,7 +10,6 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs.js";
|
|
|
9
10
|
import { useOptionalFormStore } from "../form/form-context.js";
|
|
10
11
|
import { DeferredSchema, useDeferredBadge } from "./deferred.js";
|
|
11
12
|
import { SchemaRenderer } from "../schema/schema-renderer.js";
|
|
12
|
-
import { useHydrated } from "../../hooks/use-hydrated.js";
|
|
13
13
|
import { componentHasErrors, readPersisted, schemaGapClass, tabFromQuery, tabQueryValue, tabToReveal, writePersisted } from "@alfakit/core";
|
|
14
14
|
import { usePage } from "@inertiajs/react";
|
|
15
15
|
import { useEffect, useState } from "react";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-sidebar.d.ts","names":[],"sources":["../../../src/components/panel/app-sidebar.tsx"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"app-sidebar.d.ts","names":[],"sources":["../../../src/components/panel/app-sidebar.tsx"],"mappings":";;wBAqVgB,8BAAU,IAAA"}
|
|
@@ -17,7 +17,8 @@ import { TenantMenu } from "./tenant-menu.js";
|
|
|
17
17
|
import { UserMenu } from "./user-menu.js";
|
|
18
18
|
import { PREFETCH_CACHE_FOR, flattenNavigationItems, hasActiveNavigationItem, navigationItemText } from "@alfakit/core";
|
|
19
19
|
import { Link } from "@inertiajs/react";
|
|
20
|
-
import {
|
|
20
|
+
import { useState } from "react";
|
|
21
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
21
22
|
import { ChevronRightIcon } from "lucide-react";
|
|
22
23
|
//#region src/components/panel/app-sidebar.tsx
|
|
23
24
|
function NavBadge({ item }) {
|
|
@@ -59,10 +60,28 @@ function NavLink({ item, children, ...props }) {
|
|
|
59
60
|
children
|
|
60
61
|
});
|
|
61
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Open state of a collapsible menu entry: `defaultOpen` at first, and opened again when the
|
|
65
|
+
* entry becomes active. The sidebar stays mounted across visits, so it keeps what the user
|
|
66
|
+
* opened or closed, while a page reached from elsewhere still shows its menu entry.
|
|
67
|
+
*/
|
|
68
|
+
function useOpenWhenActive(defaultOpen, active) {
|
|
69
|
+
const [open, setOpen] = useState(defaultOpen);
|
|
70
|
+
const [wasActive, setWasActive] = useState(active);
|
|
71
|
+
if (active !== wasActive) {
|
|
72
|
+
setWasActive(active);
|
|
73
|
+
if (active) setOpen(true);
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
open,
|
|
77
|
+
onOpenChange: setOpen
|
|
78
|
+
};
|
|
79
|
+
}
|
|
62
80
|
/** Child items (any depth) with their own badges, new-tab links and nested children. */
|
|
63
81
|
function NavSubItem({ item }) {
|
|
82
|
+
const collapsible = useOpenWhenActive(!!item.active, !!item.active);
|
|
64
83
|
if (item.children && item.children.length > 0) return /* @__PURE__ */ jsx(SidebarMenuSubItem, { children: /* @__PURE__ */ jsxs(Collapsible, {
|
|
65
|
-
|
|
84
|
+
...collapsible,
|
|
66
85
|
className: "group/nav-sub",
|
|
67
86
|
children: [/* @__PURE__ */ jsx(CollapsibleTrigger, {
|
|
68
87
|
asChild: true,
|
|
@@ -96,9 +115,10 @@ function NavSubItem({ item }) {
|
|
|
96
115
|
}) });
|
|
97
116
|
}
|
|
98
117
|
function NavItem({ item }) {
|
|
118
|
+
const collapsible = useOpenWhenActive(!!item.active, !!item.active);
|
|
99
119
|
if (item.children && item.children.length > 0) return /* @__PURE__ */ jsx(Collapsible, {
|
|
100
120
|
asChild: true,
|
|
101
|
-
|
|
121
|
+
...collapsible,
|
|
102
122
|
className: "group/collapsible",
|
|
103
123
|
children: /* @__PURE__ */ jsxs(SidebarMenuItem, { children: [/* @__PURE__ */ jsx(CollapsibleTrigger, {
|
|
104
124
|
asChild: true,
|
|
@@ -179,6 +199,8 @@ function NavGroupFlyout({ group }) {
|
|
|
179
199
|
function NavGroup({ group }) {
|
|
180
200
|
const attributes = extraProps(group.extraSidebarAttributes);
|
|
181
201
|
const flyout = Boolean(group.label && group.icon);
|
|
202
|
+
const active = group.items.some((item) => item.active);
|
|
203
|
+
const collapsible = useOpenWhenActive(!group.collapsed || active, active);
|
|
182
204
|
const content = /* @__PURE__ */ jsx(SidebarGroupContent, {
|
|
183
205
|
className: cn(flyout && "group-data-[collapsible=icon]:hidden"),
|
|
184
206
|
children: /* @__PURE__ */ jsx(SidebarMenu, { children: group.items.map((item) => /* @__PURE__ */ jsx(NavItem, { item }, item.url ?? item.label)) })
|
|
@@ -200,7 +222,7 @@ function NavGroup({ group }) {
|
|
|
200
222
|
]
|
|
201
223
|
});
|
|
202
224
|
return /* @__PURE__ */ jsx(Collapsible, {
|
|
203
|
-
|
|
225
|
+
...collapsible,
|
|
204
226
|
className: "group/nav-group",
|
|
205
227
|
children: /* @__PURE__ */ jsxs(SidebarGroup, {
|
|
206
228
|
...attributes,
|
|
@@ -241,7 +263,7 @@ function AppSidebar() {
|
|
|
241
263
|
/* @__PURE__ */ jsxs(SidebarHeader, { children: [
|
|
242
264
|
/* @__PURE__ */ jsx(Slot, { name: "sidebar.start" }),
|
|
243
265
|
/* @__PURE__ */ jsx(Slot, { name: "sidebar.logo.before" }),
|
|
244
|
-
/* @__PURE__ */ jsx(SidebarMenu, { children: /* @__PURE__ */ jsx(SidebarMenuItem, { children: panel.tenancy?.current ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
266
|
+
/* @__PURE__ */ jsx(SidebarMenu, { children: /* @__PURE__ */ jsx(SidebarMenuItem, { children: panel.tenancy?.current ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
245
267
|
/* @__PURE__ */ jsx(Slot, { name: "tenant-menu.before" }),
|
|
246
268
|
/* @__PURE__ */ jsx(TenantMenu, {}),
|
|
247
269
|
/* @__PURE__ */ jsx(Slot, { name: "tenant-menu.after" })
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-sidebar.js","names":[],"sources":["../../../src/components/panel/app-sidebar.tsx"],"sourcesContent":["import {\n PREFETCH_CACHE_FOR,\n flattenNavigationItems,\n hasActiveNavigationItem,\n navigationItemText,\n type NavigationGroupSchema,\n type NavigationItemSchema,\n} from '@alfakit/core'\nimport { Link } from '@inertiajs/react'\nimport { ChevronRightIcon } from 'lucide-react'\nimport type { ComponentProps, ReactNode } from 'react'\nimport { useDirection, usePanel, useT } from '../../context'\nimport { extraProps } from '../../lib/attributes'\nimport { badgeColorClasses, colorClass } from '../../lib/colors'\nimport { Icon } from '../../lib/icons'\nimport { NewTabHint } from '../../lib/new-tab-hint'\nimport { cn } from '../../lib/utils'\nimport { NotificationsMenu } from '../notifications/notifications-menu'\nimport { Badge } from '../ui/badge'\nimport { Collapsible, CollapsibleContent, CollapsibleTrigger } from '../ui/collapsible'\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuTrigger,\n} from '../ui/dropdown-menu'\nimport {\n Sidebar,\n SidebarContent,\n SidebarFooter,\n SidebarGroup,\n SidebarGroupContent,\n SidebarGroupLabel,\n SidebarHeader,\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarMenuSub,\n SidebarMenuSubButton,\n SidebarMenuSubItem,\n SidebarRail,\n} from '../ui/sidebar'\nimport { Brand } from './brand'\nimport { GlobalSearch } from './global-search'\nimport { NavLabel } from './nav-label'\nimport { Slot } from './slot'\nimport { TenantMenu } from './tenant-menu'\nimport { UserMenu } from './user-menu'\n\nexport { Brand } from './brand'\n\nfunction NavBadge({ item }: { item: NavigationItemSchema }) {\n const t = useT()\n\n // `deferNavigationBadges()`: a placeholder until the deferred badges arrive.\n if (item.badgePending) {\n return (\n <span\n data-slot=\"badge\"\n aria-busy=\"true\"\n aria-label={t('loading', {}, 'Loading')}\n className=\"bg-muted-foreground/20 ms-auto inline-block h-4 w-5 animate-pulse rounded-md\"\n />\n )\n }\n\n if (item.badge === undefined || item.badge === null) return null\n\n return (\n <Badge\n variant=\"outline\"\n className={cn('ms-auto px-1.5 py-0', colorClass(badgeColorClasses, item.badgeColor, 'gray'))}\n title={item.badgeTooltip}\n >\n {item.badge}\n </Badge>\n )\n}\n\n/**\n * A navigation link: an Inertia link, or a plain anchor for `openUrlInNewTab()`. Used as an\n * `asChild` target, so it must pass on the classes, data attributes and handlers it receives.\n */\nfunction NavLink({\n item,\n children,\n ...props\n}: { item: NavigationItemSchema; children: ReactNode } & Omit<\n ComponentProps<'a'>,\n 'href' | 'children'\n>) {\n // `NavigationItem::extraAttributes()`; `class` is merged with the link's own.\n const attributes = {\n ...props,\n ...extraProps(item.extraAttributes, props.className),\n } as ComponentProps<'a'>\n\n return item.openInNewTab ? (\n <a {...attributes} href={item.url} target=\"_blank\" rel=\"noopener noreferrer\">\n {children}\n <NewTabHint />\n </a>\n ) : (\n // Slot passes DOM props; Inertia's Link redefines a few event names, so pass them through untyped.\n <Link\n {...(attributes as Record<string, unknown>)}\n href={item.url ?? '#'}\n prefetch\n cacheFor={PREFETCH_CACHE_FOR}\n >\n {children}\n </Link>\n )\n}\n\n/** Child items (any depth) with their own badges, new-tab links and nested children. */\nfunction NavSubItem({ item }: { item: NavigationItemSchema }) {\n if (item.children && item.children.length > 0) {\n return (\n <SidebarMenuSubItem>\n <Collapsible defaultOpen={item.active} className=\"group/nav-sub\">\n <CollapsibleTrigger asChild>\n <SidebarMenuSubButton asChild isActive={item.active}>\n <button type=\"button\" {...extraProps(item.extraAttributes, 'w-full')}>\n {item.icon && <Icon name={item.icon} />}\n <NavLabel item={item} />\n <NavBadge item={item} />\n <ChevronRightIcon className=\"ms-auto transition-transform duration-200 group-data-[state=open]/nav-sub:rotate-90 rtl:rotate-180 rtl:group-data-[state=open]/nav-sub:rotate-90\" />\n </button>\n </SidebarMenuSubButton>\n </CollapsibleTrigger>\n <CollapsibleContent>\n <SidebarMenuSub>\n {item.children.map((child) => (\n <NavSubItem key={child.url ?? child.label} item={child} />\n ))}\n </SidebarMenuSub>\n </CollapsibleContent>\n </Collapsible>\n </SidebarMenuSubItem>\n )\n }\n\n return (\n <SidebarMenuSubItem>\n <SidebarMenuSubButton asChild isActive={item.active}>\n <NavLink item={item}>\n {item.icon && <Icon name={item.icon} />}\n <NavLabel item={item} />\n <NavBadge item={item} />\n </NavLink>\n </SidebarMenuSubButton>\n </SidebarMenuSubItem>\n )\n}\n\nfunction NavItem({ item }: { item: NavigationItemSchema }) {\n if (item.children && item.children.length > 0) {\n return (\n <Collapsible asChild defaultOpen={item.active} className=\"group/collapsible\">\n <SidebarMenuItem>\n <CollapsibleTrigger asChild>\n <SidebarMenuButton\n tooltip={navigationItemText(item)}\n isActive={item.active}\n {...extraProps(item.extraAttributes)}\n >\n {item.icon && <Icon name={item.icon} />}\n <NavLabel item={item} />\n <NavBadge item={item} />\n <ChevronRightIcon className=\"ms-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90 rtl:rotate-180 rtl:group-data-[state=open]/collapsible:rotate-90\" />\n </SidebarMenuButton>\n </CollapsibleTrigger>\n <CollapsibleContent>\n <SidebarMenuSub>\n {item.children.map((child) => (\n <NavSubItem key={child.url ?? child.label} item={child} />\n ))}\n </SidebarMenuSub>\n </CollapsibleContent>\n </SidebarMenuItem>\n </Collapsible>\n )\n }\n\n return (\n <SidebarMenuItem>\n <SidebarMenuButton asChild isActive={item.active} tooltip={navigationItemText(item)}>\n <NavLink item={item}>\n {item.icon && <Icon name={item.icon} />}\n <NavLabel item={item} />\n <NavBadge item={item} />\n </NavLink>\n </SidebarMenuButton>\n </SidebarMenuItem>\n )\n}\n\n/** A group's items in its flyout menu, children indented under their parent. */\nfunction FlyoutItems({ items }: { items: NavigationItemSchema[] }) {\n return flattenNavigationItems(items).map(({ item, depth }, index) => (\n <DropdownMenuItem\n key={`${depth}-${item.label}-${index}`}\n asChild\n data-active={item.active ? 'true' : undefined}\n >\n <NavLink\n item={item}\n aria-current={item.active ? 'page' : undefined}\n className={cn(\n 'data-[active=true]:bg-accent data-[active=true]:font-medium',\n depth === 1 && 'ps-6',\n depth > 1 && 'ps-9',\n )}\n >\n {item.icon && <Icon name={item.icon} />}\n <NavLabel item={item} className=\"truncate\" />\n <NavBadge item={item} />\n </NavLink>\n </DropdownMenuItem>\n ))\n}\n\n/**\n * In the icon-only (collapsed) sidebar, a group with an icon is one button that opens its\n * items in a flyout menu, like Filament. Hidden while the sidebar is expanded.\n */\nfunction NavGroupFlyout({ group }: { group: NavigationGroupSchema }) {\n const direction = useDirection()\n const active = hasActiveNavigationItem(group.items)\n\n return (\n <SidebarMenu className=\"hidden group-data-[collapsible=icon]:flex\" data-nav-flyout=\"\">\n <SidebarMenuItem>\n <DropdownMenu dir={direction}>\n <DropdownMenuTrigger asChild>\n <SidebarMenuButton isActive={active}>\n <Icon name={group.icon!} />\n <span>{group.label}</span>\n </SidebarMenuButton>\n </DropdownMenuTrigger>\n <DropdownMenuContent\n side={direction === 'rtl' ? 'left' : 'right'}\n align=\"start\"\n className=\"min-w-56\"\n >\n <DropdownMenuLabel>{group.label}</DropdownMenuLabel>\n <FlyoutItems items={group.items} />\n </DropdownMenuContent>\n </DropdownMenu>\n </SidebarMenuItem>\n </SidebarMenu>\n )\n}\n\nfunction NavGroup({ group }: { group: NavigationGroupSchema }) {\n // `NavigationGroup::extraSidebarAttributes()`; `class` is merged with the group's own.\n const attributes = extraProps(group.extraSidebarAttributes)\n // A labelled group with an icon collapses into a flyout in the icon-only sidebar.\n const flyout = Boolean(group.label && group.icon)\n const content = (\n <SidebarGroupContent className={cn(flyout && 'group-data-[collapsible=icon]:hidden')}>\n <SidebarMenu>\n {group.items.map((item) => (\n <NavItem key={item.url ?? item.label} item={item} />\n ))}\n </SidebarMenu>\n </SidebarGroupContent>\n )\n const flyoutMenu = flyout ? <NavGroupFlyout group={group} /> : null\n\n if (!group.label) {\n return <SidebarGroup {...attributes}>{content}</SidebarGroup>\n }\n\n if (!group.collapsible) {\n return (\n <SidebarGroup {...attributes}>\n <SidebarGroupLabel>\n {group.icon && <Icon name={group.icon} className=\"me-1.5\" />}\n {group.label}\n </SidebarGroupLabel>\n {content}\n {flyoutMenu}\n </SidebarGroup>\n )\n }\n\n return (\n <Collapsible\n defaultOpen={!group.collapsed || group.items.some((item) => item.active)}\n className=\"group/nav-group\"\n >\n <SidebarGroup {...attributes}>\n <CollapsibleTrigger asChild>\n <SidebarGroupLabel\n asChild\n className=\"hover:bg-sidebar-accent w-full cursor-pointer justify-between\"\n >\n <button type=\"button\">\n <span className=\"flex items-center gap-1.5\">\n {group.icon && <Icon name={group.icon} />}\n {group.label}\n </span>\n <Icon\n name=\"alfakit::panels.sidebar.group.collapse-button\"\n fallback={ChevronRightIcon}\n className=\"size-3.5 transition-transform group-data-[state=open]/nav-group:rotate-90 rtl:rotate-180 rtl:group-data-[state=open]/nav-group:rotate-90\"\n />\n </button>\n </SidebarGroupLabel>\n </CollapsibleTrigger>\n <CollapsibleContent>{content}</CollapsibleContent>\n {flyoutMenu}\n </SidebarGroup>\n </Collapsible>\n )\n}\n\nexport function AppSidebar() {\n const panel = usePanel()\n const direction = useDirection()\n const t = useT()\n\n return (\n <Sidebar\n side={direction === 'rtl' ? 'right' : 'left'}\n collapsible={\n panel.theme?.design?.layout === 'rail'\n ? 'icon'\n : panel.sidebarFullyCollapsibleOnDesktop\n ? 'offcanvas'\n : panel.sidebarCollapsibleOnDesktop\n ? 'icon'\n : 'none'\n }\n variant={panel.theme?.sidebar === 'flush' ? 'sidebar' : (panel.theme?.sidebar ?? 'inset')}\n >\n <SidebarHeader>\n <Slot name=\"sidebar.start\" />\n <Slot name=\"sidebar.logo.before\" />\n <SidebarMenu>\n <SidebarMenuItem>\n {panel.tenancy?.current ? (\n <>\n <Slot name=\"tenant-menu.before\" />\n <TenantMenu />\n <Slot name=\"tenant-menu.after\" />\n </>\n ) : (\n <SidebarMenuButton size=\"lg\" asChild>\n <div>\n <Brand />\n </div>\n </SidebarMenuButton>\n )}\n </SidebarMenuItem>\n </SidebarMenu>\n <Slot name=\"sidebar.logo.after\" />\n {/* `databaseNotifications(position: 'sidebar')` or `topbar(false)`. */}\n {panel.databaseNotifications?.position === 'sidebar' && (\n <SidebarMenu>\n <SidebarMenuItem>\n <NotificationsMenu variant=\"sidebar\" />\n </SidebarMenuItem>\n </SidebarMenu>\n )}\n {/* `globalSearch(position: GlobalSearchPosition::Sidebar)` or `topbar(false)`. */}\n {panel.globalSearch?.position === 'sidebar' && (\n <SidebarMenu>\n <SidebarMenuItem>\n <GlobalSearch variant=\"sidebar\" />\n </SidebarMenuItem>\n </SidebarMenu>\n )}\n <Slot name=\"sidebar.header\" />\n </SidebarHeader>\n <SidebarContent>\n <Slot name=\"sidebar.nav.start\" />\n <nav aria-label={t('sidebar', {}, 'Sidebar')} className=\"contents\">\n {panel.navigation.map((group, index) => (\n <NavGroup key={group.label ?? `group-${index}`} group={group} />\n ))}\n </nav>\n <Slot name=\"sidebar.nav.end\" />\n </SidebarContent>\n <SidebarFooter>\n <Slot name=\"sidebar.footer\" />\n {/* `userMenu(position: UserMenuPosition::Topbar)` shows it in the top bar instead. */}\n {panel.userMenu?.position !== 'topbar' && (\n <SidebarMenu>\n <SidebarMenuItem>\n <UserMenu />\n </SidebarMenuItem>\n </SidebarMenu>\n )}\n <Slot name=\"sidebar.end\" />\n </SidebarFooter>\n <SidebarRail />\n </Sidebar>\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"app-sidebar.js","names":[],"sources":["../../../src/components/panel/app-sidebar.tsx"],"sourcesContent":["import {\n PREFETCH_CACHE_FOR,\n flattenNavigationItems,\n hasActiveNavigationItem,\n navigationItemText,\n type NavigationGroupSchema,\n type NavigationItemSchema,\n} from '@alfakit/core'\nimport { Link } from '@inertiajs/react'\nimport { ChevronRightIcon } from 'lucide-react'\nimport { useState, type ComponentProps, type ReactNode } from 'react'\nimport { useDirection, usePanel, useT } from '../../context'\nimport { extraProps } from '../../lib/attributes'\nimport { badgeColorClasses, colorClass } from '../../lib/colors'\nimport { Icon } from '../../lib/icons'\nimport { NewTabHint } from '../../lib/new-tab-hint'\nimport { cn } from '../../lib/utils'\nimport { NotificationsMenu } from '../notifications/notifications-menu'\nimport { Badge } from '../ui/badge'\nimport { Collapsible, CollapsibleContent, CollapsibleTrigger } from '../ui/collapsible'\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuTrigger,\n} from '../ui/dropdown-menu'\nimport {\n Sidebar,\n SidebarContent,\n SidebarFooter,\n SidebarGroup,\n SidebarGroupContent,\n SidebarGroupLabel,\n SidebarHeader,\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarMenuSub,\n SidebarMenuSubButton,\n SidebarMenuSubItem,\n SidebarRail,\n} from '../ui/sidebar'\nimport { Brand } from './brand'\nimport { GlobalSearch } from './global-search'\nimport { NavLabel } from './nav-label'\nimport { Slot } from './slot'\nimport { TenantMenu } from './tenant-menu'\nimport { UserMenu } from './user-menu'\n\nexport { Brand } from './brand'\n\nfunction NavBadge({ item }: { item: NavigationItemSchema }) {\n const t = useT()\n\n // `deferNavigationBadges()`: a placeholder until the deferred badges arrive.\n if (item.badgePending) {\n return (\n <span\n data-slot=\"badge\"\n aria-busy=\"true\"\n aria-label={t('loading', {}, 'Loading')}\n className=\"bg-muted-foreground/20 ms-auto inline-block h-4 w-5 animate-pulse rounded-md\"\n />\n )\n }\n\n if (item.badge === undefined || item.badge === null) return null\n\n return (\n <Badge\n variant=\"outline\"\n className={cn('ms-auto px-1.5 py-0', colorClass(badgeColorClasses, item.badgeColor, 'gray'))}\n title={item.badgeTooltip}\n >\n {item.badge}\n </Badge>\n )\n}\n\n/**\n * A navigation link: an Inertia link, or a plain anchor for `openUrlInNewTab()`. Used as an\n * `asChild` target, so it must pass on the classes, data attributes and handlers it receives.\n */\nfunction NavLink({\n item,\n children,\n ...props\n}: { item: NavigationItemSchema; children: ReactNode } & Omit<\n ComponentProps<'a'>,\n 'href' | 'children'\n>) {\n // `NavigationItem::extraAttributes()`; `class` is merged with the link's own.\n const attributes = {\n ...props,\n ...extraProps(item.extraAttributes, props.className),\n } as ComponentProps<'a'>\n\n return item.openInNewTab ? (\n <a {...attributes} href={item.url} target=\"_blank\" rel=\"noopener noreferrer\">\n {children}\n <NewTabHint />\n </a>\n ) : (\n // Slot passes DOM props; Inertia's Link redefines a few event names, so pass them through untyped.\n <Link\n {...(attributes as Record<string, unknown>)}\n href={item.url ?? '#'}\n prefetch\n cacheFor={PREFETCH_CACHE_FOR}\n >\n {children}\n </Link>\n )\n}\n\n/**\n * Open state of a collapsible menu entry: `defaultOpen` at first, and opened again when the\n * entry becomes active. The sidebar stays mounted across visits, so it keeps what the user\n * opened or closed, while a page reached from elsewhere still shows its menu entry.\n */\nfunction useOpenWhenActive(defaultOpen: boolean, active: boolean) {\n const [open, setOpen] = useState(defaultOpen)\n const [wasActive, setWasActive] = useState(active)\n\n if (active !== wasActive) {\n setWasActive(active)\n\n if (active) setOpen(true)\n }\n\n return { open, onOpenChange: setOpen }\n}\n\n/** Child items (any depth) with their own badges, new-tab links and nested children. */\nfunction NavSubItem({ item }: { item: NavigationItemSchema }) {\n const collapsible = useOpenWhenActive(!!item.active, !!item.active)\n\n if (item.children && item.children.length > 0) {\n return (\n <SidebarMenuSubItem>\n <Collapsible {...collapsible} className=\"group/nav-sub\">\n <CollapsibleTrigger asChild>\n <SidebarMenuSubButton asChild isActive={item.active}>\n <button type=\"button\" {...extraProps(item.extraAttributes, 'w-full')}>\n {item.icon && <Icon name={item.icon} />}\n <NavLabel item={item} />\n <NavBadge item={item} />\n <ChevronRightIcon className=\"ms-auto transition-transform duration-200 group-data-[state=open]/nav-sub:rotate-90 rtl:rotate-180 rtl:group-data-[state=open]/nav-sub:rotate-90\" />\n </button>\n </SidebarMenuSubButton>\n </CollapsibleTrigger>\n <CollapsibleContent>\n <SidebarMenuSub>\n {item.children.map((child) => (\n <NavSubItem key={child.url ?? child.label} item={child} />\n ))}\n </SidebarMenuSub>\n </CollapsibleContent>\n </Collapsible>\n </SidebarMenuSubItem>\n )\n }\n\n return (\n <SidebarMenuSubItem>\n <SidebarMenuSubButton asChild isActive={item.active}>\n <NavLink item={item}>\n {item.icon && <Icon name={item.icon} />}\n <NavLabel item={item} />\n <NavBadge item={item} />\n </NavLink>\n </SidebarMenuSubButton>\n </SidebarMenuSubItem>\n )\n}\n\nfunction NavItem({ item }: { item: NavigationItemSchema }) {\n const collapsible = useOpenWhenActive(!!item.active, !!item.active)\n\n if (item.children && item.children.length > 0) {\n return (\n <Collapsible asChild {...collapsible} className=\"group/collapsible\">\n <SidebarMenuItem>\n <CollapsibleTrigger asChild>\n <SidebarMenuButton\n tooltip={navigationItemText(item)}\n isActive={item.active}\n {...extraProps(item.extraAttributes)}\n >\n {item.icon && <Icon name={item.icon} />}\n <NavLabel item={item} />\n <NavBadge item={item} />\n <ChevronRightIcon className=\"ms-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90 rtl:rotate-180 rtl:group-data-[state=open]/collapsible:rotate-90\" />\n </SidebarMenuButton>\n </CollapsibleTrigger>\n <CollapsibleContent>\n <SidebarMenuSub>\n {item.children.map((child) => (\n <NavSubItem key={child.url ?? child.label} item={child} />\n ))}\n </SidebarMenuSub>\n </CollapsibleContent>\n </SidebarMenuItem>\n </Collapsible>\n )\n }\n\n return (\n <SidebarMenuItem>\n <SidebarMenuButton asChild isActive={item.active} tooltip={navigationItemText(item)}>\n <NavLink item={item}>\n {item.icon && <Icon name={item.icon} />}\n <NavLabel item={item} />\n <NavBadge item={item} />\n </NavLink>\n </SidebarMenuButton>\n </SidebarMenuItem>\n )\n}\n\n/** A group's items in its flyout menu, children indented under their parent. */\nfunction FlyoutItems({ items }: { items: NavigationItemSchema[] }) {\n return flattenNavigationItems(items).map(({ item, depth }, index) => (\n <DropdownMenuItem\n key={`${depth}-${item.label}-${index}`}\n asChild\n data-active={item.active ? 'true' : undefined}\n >\n <NavLink\n item={item}\n aria-current={item.active ? 'page' : undefined}\n className={cn(\n 'data-[active=true]:bg-accent data-[active=true]:font-medium',\n depth === 1 && 'ps-6',\n depth > 1 && 'ps-9',\n )}\n >\n {item.icon && <Icon name={item.icon} />}\n <NavLabel item={item} className=\"truncate\" />\n <NavBadge item={item} />\n </NavLink>\n </DropdownMenuItem>\n ))\n}\n\n/**\n * In the icon-only (collapsed) sidebar, a group with an icon is one button that opens its\n * items in a flyout menu, like Filament. Hidden while the sidebar is expanded.\n */\nfunction NavGroupFlyout({ group }: { group: NavigationGroupSchema }) {\n const direction = useDirection()\n const active = hasActiveNavigationItem(group.items)\n\n return (\n <SidebarMenu className=\"hidden group-data-[collapsible=icon]:flex\" data-nav-flyout=\"\">\n <SidebarMenuItem>\n <DropdownMenu dir={direction}>\n <DropdownMenuTrigger asChild>\n <SidebarMenuButton isActive={active}>\n <Icon name={group.icon!} />\n <span>{group.label}</span>\n </SidebarMenuButton>\n </DropdownMenuTrigger>\n <DropdownMenuContent\n side={direction === 'rtl' ? 'left' : 'right'}\n align=\"start\"\n className=\"min-w-56\"\n >\n <DropdownMenuLabel>{group.label}</DropdownMenuLabel>\n <FlyoutItems items={group.items} />\n </DropdownMenuContent>\n </DropdownMenu>\n </SidebarMenuItem>\n </SidebarMenu>\n )\n}\n\nfunction NavGroup({ group }: { group: NavigationGroupSchema }) {\n // `NavigationGroup::extraSidebarAttributes()`; `class` is merged with the group's own.\n const attributes = extraProps(group.extraSidebarAttributes)\n // A labelled group with an icon collapses into a flyout in the icon-only sidebar.\n const flyout = Boolean(group.label && group.icon)\n const active = group.items.some((item) => item.active)\n const collapsible = useOpenWhenActive(!group.collapsed || active, active)\n const content = (\n <SidebarGroupContent className={cn(flyout && 'group-data-[collapsible=icon]:hidden')}>\n <SidebarMenu>\n {group.items.map((item) => (\n <NavItem key={item.url ?? item.label} item={item} />\n ))}\n </SidebarMenu>\n </SidebarGroupContent>\n )\n const flyoutMenu = flyout ? <NavGroupFlyout group={group} /> : null\n\n if (!group.label) {\n return <SidebarGroup {...attributes}>{content}</SidebarGroup>\n }\n\n if (!group.collapsible) {\n return (\n <SidebarGroup {...attributes}>\n <SidebarGroupLabel>\n {group.icon && <Icon name={group.icon} className=\"me-1.5\" />}\n {group.label}\n </SidebarGroupLabel>\n {content}\n {flyoutMenu}\n </SidebarGroup>\n )\n }\n\n return (\n <Collapsible {...collapsible} className=\"group/nav-group\">\n <SidebarGroup {...attributes}>\n <CollapsibleTrigger asChild>\n <SidebarGroupLabel\n asChild\n className=\"hover:bg-sidebar-accent w-full cursor-pointer justify-between\"\n >\n <button type=\"button\">\n <span className=\"flex items-center gap-1.5\">\n {group.icon && <Icon name={group.icon} />}\n {group.label}\n </span>\n <Icon\n name=\"alfakit::panels.sidebar.group.collapse-button\"\n fallback={ChevronRightIcon}\n className=\"size-3.5 transition-transform group-data-[state=open]/nav-group:rotate-90 rtl:rotate-180 rtl:group-data-[state=open]/nav-group:rotate-90\"\n />\n </button>\n </SidebarGroupLabel>\n </CollapsibleTrigger>\n <CollapsibleContent>{content}</CollapsibleContent>\n {flyoutMenu}\n </SidebarGroup>\n </Collapsible>\n )\n}\n\nexport function AppSidebar() {\n const panel = usePanel()\n const direction = useDirection()\n const t = useT()\n\n return (\n <Sidebar\n side={direction === 'rtl' ? 'right' : 'left'}\n collapsible={\n panel.theme?.design?.layout === 'rail'\n ? 'icon'\n : panel.sidebarFullyCollapsibleOnDesktop\n ? 'offcanvas'\n : panel.sidebarCollapsibleOnDesktop\n ? 'icon'\n : 'none'\n }\n variant={panel.theme?.sidebar === 'flush' ? 'sidebar' : (panel.theme?.sidebar ?? 'inset')}\n >\n <SidebarHeader>\n <Slot name=\"sidebar.start\" />\n <Slot name=\"sidebar.logo.before\" />\n <SidebarMenu>\n <SidebarMenuItem>\n {panel.tenancy?.current ? (\n <>\n <Slot name=\"tenant-menu.before\" />\n <TenantMenu />\n <Slot name=\"tenant-menu.after\" />\n </>\n ) : (\n <SidebarMenuButton size=\"lg\" asChild>\n <div>\n <Brand />\n </div>\n </SidebarMenuButton>\n )}\n </SidebarMenuItem>\n </SidebarMenu>\n <Slot name=\"sidebar.logo.after\" />\n {/* `databaseNotifications(position: 'sidebar')` or `topbar(false)`. */}\n {panel.databaseNotifications?.position === 'sidebar' && (\n <SidebarMenu>\n <SidebarMenuItem>\n <NotificationsMenu variant=\"sidebar\" />\n </SidebarMenuItem>\n </SidebarMenu>\n )}\n {/* `globalSearch(position: GlobalSearchPosition::Sidebar)` or `topbar(false)`. */}\n {panel.globalSearch?.position === 'sidebar' && (\n <SidebarMenu>\n <SidebarMenuItem>\n <GlobalSearch variant=\"sidebar\" />\n </SidebarMenuItem>\n </SidebarMenu>\n )}\n <Slot name=\"sidebar.header\" />\n </SidebarHeader>\n <SidebarContent>\n <Slot name=\"sidebar.nav.start\" />\n <nav aria-label={t('sidebar', {}, 'Sidebar')} className=\"contents\">\n {panel.navigation.map((group, index) => (\n <NavGroup key={group.label ?? `group-${index}`} group={group} />\n ))}\n </nav>\n <Slot name=\"sidebar.nav.end\" />\n </SidebarContent>\n <SidebarFooter>\n <Slot name=\"sidebar.footer\" />\n {/* `userMenu(position: UserMenuPosition::Topbar)` shows it in the top bar instead. */}\n {panel.userMenu?.position !== 'topbar' && (\n <SidebarMenu>\n <SidebarMenuItem>\n <UserMenu />\n </SidebarMenuItem>\n </SidebarMenu>\n )}\n <Slot name=\"sidebar.end\" />\n </SidebarFooter>\n <SidebarRail />\n </Sidebar>\n )\n}\n"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { usePanel, useT } from "../../context.js";
|
|
2
|
+
import { useHydrated } from "../../hooks/use-hydrated.js";
|
|
2
3
|
import { Icon } from "../../lib/icons.js";
|
|
3
4
|
import { Button } from "../ui/button.js";
|
|
4
|
-
import { useHydrated } from "../../hooks/use-hydrated.js";
|
|
5
5
|
import { Slot } from "./slot.js";
|
|
6
6
|
import { SHORTCUT_IDS, formatKeyBinding, globalSearchSuffix, shortcuts } from "@alfakit/core";
|
|
7
7
|
import { router } from "@inertiajs/react";
|
|
@@ -97,7 +97,7 @@ function GlobalSearch({ variant = "topbar" }) {
|
|
|
97
97
|
}),
|
|
98
98
|
hydrated && suffix?.type === "keyBinding" && /* @__PURE__ */ jsx("kbd", {
|
|
99
99
|
dir: "ltr",
|
|
100
|
-
className: "bg-muted hidden rounded px-1 font-mono text-[10px] sm:inline",
|
|
100
|
+
className: "bg-muted text-foreground/70 hidden rounded px-1 font-mono text-[10px] sm:inline",
|
|
101
101
|
children: formatKeyBinding(suffix.keyBinding)
|
|
102
102
|
})
|
|
103
103
|
]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"global-search.js","names":[],"sources":["../../../src/components/panel/global-search.tsx"],"sourcesContent":["import { formatKeyBinding, globalSearchSuffix, SHORTCUT_IDS, shortcuts } from '@alfakit/core'\nimport { router } from '@inertiajs/react'\nimport { SearchIcon } from 'lucide-react'\nimport { useEffect, useState, type ComponentType } from 'react'\nimport { usePanel, useT } from '../../context'\nimport { useHydrated } from '../../hooks/use-hydrated'\nimport { Icon } from '../../lib/icons'\nimport { Button } from '../ui/button'\nimport { Slot } from './slot'\n\ntype PaletteComponent = ComponentType<{ open: boolean; onOpenChange: (open: boolean) => void }>\n\nlet loadedPalette: PaletteComponent | undefined\nlet loadingPalette: Promise<PaletteComponent> | undefined\n\n/** The palette's code: loaded the first time it opens (or ahead, on hover and focus). */\nfunction loadPalette(): Promise<PaletteComponent> {\n loadingPalette ??= import('./global-search-palette').then(\n (module) => (loadedPalette = module.GlobalSearchPalette),\n (error: unknown) => {\n loadingPalette = undefined\n throw error\n },\n )\n\n return loadingPalette\n}\n\nconst preloadPalette = () => void loadPalette().catch(() => undefined)\n\n/**\n * The search field and its command palette (`GlobalSearchPalette`, loaded on first open):\n * global search results plus (with `alfakit.commandPalette`) the page's header actions,\n * panel commands, navigation, \"create\" links, recent records, theme switching and the\n * shortcuts overlay. `variant=\"sidebar\"` is the full-width field in the sidebar header\n * (`globalSearch(position: GlobalSearchPosition::Sidebar)`).\n */\nexport function GlobalSearch({ variant = 'topbar' }: { variant?: 'topbar' | 'sidebar' }) {\n const t = useT()\n const panel = usePanel()\n const hydrated = useHydrated()\n const [open, setOpen] = useState(false)\n // Undefined on the server and during hydration (nothing loads there), so the markup matches.\n const [Palette, setPalette] = useState<PaletteComponent | undefined>(() => loadedPalette)\n const config = panel.globalSearch\n const palette = panel.commandPalette\n const endpoint = config ? panel.endpoints.globalSearch : undefined\n const placeholder =\n config?.placeholder ??\n palette?.placeholder ??\n t('palette.placeholder', {}, 'Type a command or search…')\n // Without global search the palette still opens with ⌘K / Ctrl+K.\n const suffix = globalSearchSuffix(\n config ?? (palette ? { keyBindings: ['mod+k'], placeholder } : undefined),\n )\n const keys = JSON.stringify(config?.keyBindings ?? ['mod+k'])\n const label = t('shortcuts.open_palette', {}, 'Open the command palette')\n\n // The palette's key bindings (`globalSearchKeyBindings()`): essential, so they keep working\n // with `keyboardShortcuts(false)`; users may change them in the shortcuts overlay.\n useEffect(() => {\n if (!config && !palette) return\n\n return shortcuts.register({\n id: SHORTCUT_IDS.palette,\n keys: JSON.parse(keys) as string[],\n label,\n group: 'general',\n essential: true,\n handler: () => setOpen(true),\n })\n }, [config, palette, keys, label])\n\n // Opening before the code has arrived (a click or `mod+k` right after the page loads)\n // keeps `open` set; the palette mounts open once it has loaded.\n useEffect(() => {\n if (!open || Palette) return\n\n let active = true\n\n loadPalette().then(\n (component) => active && setPalette(() => component),\n () => active && setOpen(false),\n )\n\n return () => {\n active = false\n }\n }, [open, Palette])\n\n // A palette opened while its code was still loading closes when the page changes.\n useEffect(() => router.on('navigate', () => setOpen(false)), [])\n\n if ((!config || !endpoint) && !palette) {\n return null\n }\n\n return (\n <>\n <Slot name=\"global-search.before\" />\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n data-variant={variant}\n className={\n variant === 'sidebar'\n ? 'text-muted-foreground w-full justify-start gap-2 px-2.5 font-normal group-data-[collapsible=icon]:size-8 group-data-[collapsible=icon]:justify-center group-data-[collapsible=icon]:px-0'\n : 'text-muted-foreground size-8 justify-center gap-2 px-0 font-normal sm:w-56 sm:justify-start sm:px-2.5'\n }\n onClick={() => setOpen(true)}\n onPointerEnter={preloadPalette}\n onFocus={preloadPalette}\n >\n <Icon name=\"alfakit::panels.global-search.field\" fallback={SearchIcon} className=\"size-4\" />\n <span\n className={\n variant === 'sidebar'\n ? 'flex-1 truncate text-start group-data-[collapsible=icon]:sr-only'\n : 'sr-only sm:not-sr-only sm:flex-1 sm:text-start'\n }\n >\n {placeholder}\n </span>\n {/* `globalSearchFieldSuffix()`, or the first key binding: it depends on the visitor's\n platform (⌘ or Ctrl), so it renders after hydration. */}\n {suffix?.type === 'text' && (\n <span\n data-slot=\"global-search-suffix\"\n className=\"text-muted-foreground hidden text-[10px] sm:inline\"\n >\n {suffix.text}\n </span>\n )}\n {hydrated && suffix?.type === 'keyBinding' && (\n <kbd
|
|
1
|
+
{"version":3,"file":"global-search.js","names":[],"sources":["../../../src/components/panel/global-search.tsx"],"sourcesContent":["import { formatKeyBinding, globalSearchSuffix, SHORTCUT_IDS, shortcuts } from '@alfakit/core'\nimport { router } from '@inertiajs/react'\nimport { SearchIcon } from 'lucide-react'\nimport { useEffect, useState, type ComponentType } from 'react'\nimport { usePanel, useT } from '../../context'\nimport { useHydrated } from '../../hooks/use-hydrated'\nimport { Icon } from '../../lib/icons'\nimport { Button } from '../ui/button'\nimport { Slot } from './slot'\n\ntype PaletteComponent = ComponentType<{ open: boolean; onOpenChange: (open: boolean) => void }>\n\nlet loadedPalette: PaletteComponent | undefined\nlet loadingPalette: Promise<PaletteComponent> | undefined\n\n/** The palette's code: loaded the first time it opens (or ahead, on hover and focus). */\nfunction loadPalette(): Promise<PaletteComponent> {\n loadingPalette ??= import('./global-search-palette').then(\n (module) => (loadedPalette = module.GlobalSearchPalette),\n (error: unknown) => {\n loadingPalette = undefined\n throw error\n },\n )\n\n return loadingPalette\n}\n\nconst preloadPalette = () => void loadPalette().catch(() => undefined)\n\n/**\n * The search field and its command palette (`GlobalSearchPalette`, loaded on first open):\n * global search results plus (with `alfakit.commandPalette`) the page's header actions,\n * panel commands, navigation, \"create\" links, recent records, theme switching and the\n * shortcuts overlay. `variant=\"sidebar\"` is the full-width field in the sidebar header\n * (`globalSearch(position: GlobalSearchPosition::Sidebar)`).\n */\nexport function GlobalSearch({ variant = 'topbar' }: { variant?: 'topbar' | 'sidebar' }) {\n const t = useT()\n const panel = usePanel()\n const hydrated = useHydrated()\n const [open, setOpen] = useState(false)\n // Undefined on the server and during hydration (nothing loads there), so the markup matches.\n const [Palette, setPalette] = useState<PaletteComponent | undefined>(() => loadedPalette)\n const config = panel.globalSearch\n const palette = panel.commandPalette\n const endpoint = config ? panel.endpoints.globalSearch : undefined\n const placeholder =\n config?.placeholder ??\n palette?.placeholder ??\n t('palette.placeholder', {}, 'Type a command or search…')\n // Without global search the palette still opens with ⌘K / Ctrl+K.\n const suffix = globalSearchSuffix(\n config ?? (palette ? { keyBindings: ['mod+k'], placeholder } : undefined),\n )\n const keys = JSON.stringify(config?.keyBindings ?? ['mod+k'])\n const label = t('shortcuts.open_palette', {}, 'Open the command palette')\n\n // The palette's key bindings (`globalSearchKeyBindings()`): essential, so they keep working\n // with `keyboardShortcuts(false)`; users may change them in the shortcuts overlay.\n useEffect(() => {\n if (!config && !palette) return\n\n return shortcuts.register({\n id: SHORTCUT_IDS.palette,\n keys: JSON.parse(keys) as string[],\n label,\n group: 'general',\n essential: true,\n handler: () => setOpen(true),\n })\n }, [config, palette, keys, label])\n\n // Opening before the code has arrived (a click or `mod+k` right after the page loads)\n // keeps `open` set; the palette mounts open once it has loaded.\n useEffect(() => {\n if (!open || Palette) return\n\n let active = true\n\n loadPalette().then(\n (component) => active && setPalette(() => component),\n () => active && setOpen(false),\n )\n\n return () => {\n active = false\n }\n }, [open, Palette])\n\n // A palette opened while its code was still loading closes when the page changes.\n useEffect(() => router.on('navigate', () => setOpen(false)), [])\n\n if ((!config || !endpoint) && !palette) {\n return null\n }\n\n return (\n <>\n <Slot name=\"global-search.before\" />\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n data-variant={variant}\n className={\n variant === 'sidebar'\n ? 'text-muted-foreground w-full justify-start gap-2 px-2.5 font-normal group-data-[collapsible=icon]:size-8 group-data-[collapsible=icon]:justify-center group-data-[collapsible=icon]:px-0'\n : 'text-muted-foreground size-8 justify-center gap-2 px-0 font-normal sm:w-56 sm:justify-start sm:px-2.5'\n }\n onClick={() => setOpen(true)}\n onPointerEnter={preloadPalette}\n onFocus={preloadPalette}\n >\n <Icon name=\"alfakit::panels.global-search.field\" fallback={SearchIcon} className=\"size-4\" />\n <span\n className={\n variant === 'sidebar'\n ? 'flex-1 truncate text-start group-data-[collapsible=icon]:sr-only'\n : 'sr-only sm:not-sr-only sm:flex-1 sm:text-start'\n }\n >\n {placeholder}\n </span>\n {/* `globalSearchFieldSuffix()`, or the first key binding: it depends on the visitor's\n platform (⌘ or Ctrl), so it renders after hydration. */}\n {suffix?.type === 'text' && (\n <span\n data-slot=\"global-search-suffix\"\n className=\"text-muted-foreground hidden text-[10px] sm:inline\"\n >\n {suffix.text}\n </span>\n )}\n {hydrated && suffix?.type === 'keyBinding' && (\n <kbd\n dir=\"ltr\"\n className=\"bg-muted text-foreground/70 hidden rounded px-1 font-mono text-[10px] sm:inline\"\n >\n {formatKeyBinding(suffix.keyBinding)}\n </kbd>\n )}\n </Button>\n <Slot name=\"global-search.after\" />\n {/* The dialog's code loads on first open (click or shortcut); hovering or focusing the\n field fetches it ahead. Not rendered on the server, like the closed dialog before it. */}\n {Palette && <Palette open={open} onOpenChange={setOpen} />}\n </>\n )\n}\n"],"mappings":";;;;;;;;;;;AAYA,IAAI;AACJ,IAAI;;AAGJ,SAAS,cAAyC;CAChD,mBAAmB,OAAO,6BAA0B,CAAC,MAClD,WAAY,gBAAgB,OAAO,sBACnC,UAAmB;EAClB,iBAAiB,KAAA;EACjB,MAAM;CACR,CACF;CAEA,OAAO;AACT;AAEA,MAAM,uBAAuB,KAAK,YAAY,CAAC,CAAC,YAAY,KAAA,CAAS;;;;;;;;AASrE,SAAgB,aAAa,EAAE,UAAU,YAAgD;CACvF,MAAM,IAAI,KAAK;CACf,MAAM,QAAQ,SAAS;CACvB,MAAM,WAAW,YAAY;CAC7B,MAAM,CAAC,MAAM,WAAW,SAAS,KAAK;CAEtC,MAAM,CAAC,SAAS,cAAc,eAA6C,aAAa;CACxF,MAAM,SAAS,MAAM;CACrB,MAAM,UAAU,MAAM;CACtB,MAAM,WAAW,SAAS,MAAM,UAAU,eAAe,KAAA;CACzD,MAAM,cACJ,QAAQ,eACR,SAAS,eACT,EAAE,uBAAuB,CAAC,GAAG,2BAA2B;CAE1D,MAAM,SAAS,mBACb,WAAW,UAAU;EAAE,aAAa,CAAC,OAAO;EAAG;CAAY,IAAI,KAAA,EACjE;CACA,MAAM,OAAO,KAAK,UAAU,QAAQ,eAAe,CAAC,OAAO,CAAC;CAC5D,MAAM,QAAQ,EAAE,0BAA0B,CAAC,GAAG,0BAA0B;CAIxE,gBAAgB;EACd,IAAI,CAAC,UAAU,CAAC,SAAS;EAEzB,OAAO,UAAU,SAAS;GACxB,IAAI,aAAa;GACjB,MAAM,KAAK,MAAM,IAAI;GACrB;GACA,OAAO;GACP,WAAW;GACX,eAAe,QAAQ,IAAI;EAC7B,CAAC;CACH,GAAG;EAAC;EAAQ;EAAS;EAAM;CAAK,CAAC;CAIjC,gBAAgB;EACd,IAAI,CAAC,QAAQ,SAAS;EAEtB,IAAI,SAAS;EAEb,YAAY,CAAC,CAAC,MACX,cAAc,UAAU,iBAAiB,SAAS,SAC7C,UAAU,QAAQ,KAAK,CAC/B;EAEA,aAAa;GACX,SAAS;EACX;CACF,GAAG,CAAC,MAAM,OAAO,CAAC;CAGlB,gBAAgB,OAAO,GAAG,kBAAkB,QAAQ,KAAK,CAAC,GAAG,CAAC,CAAC;CAE/D,KAAK,CAAC,UAAU,CAAC,aAAa,CAAC,SAC7B,OAAO;CAGT,OACE,qBAAA,YAAA,EAAA,UAAA;EACE,oBAAC,MAAD,EAAM,MAAK,uBAAwB,CAAA;EACnC,qBAAC,QAAD;GACE,MAAK;GACL,SAAQ;GACR,MAAK;GACL,gBAAc;GACd,WACE,YAAY,YACR,6LACA;GAEN,eAAe,QAAQ,IAAI;GAC3B,gBAAgB;GAChB,SAAS;GAZX,UAAA;IAcE,oBAAC,MAAD;KAAM,MAAK;KAAsC,UAAU;KAAY,WAAU;IAAU,CAAA;IAC3F,oBAAC,QAAD;KACE,WACE,YAAY,YACR,qEACA;KAGL,UAAA;IACG,CAAA;IAGL,QAAQ,SAAS,UAChB,oBAAC,QAAD;KACE,aAAU;KACV,WAAU;KAET,UAAA,OAAO;IACJ,CAAA;IAEP,YAAY,QAAQ,SAAS,gBAC5B,oBAAC,OAAD;KACE,KAAI;KACJ,WAAU;KAET,UAAA,iBAAiB,OAAO,UAAU;IAChC,CAAA;GAED;;EACR,oBAAC,MAAD,EAAM,MAAK,sBAAuB,CAAA;EAGjC,WAAW,oBAAC,SAAD;GAAe;GAAM,cAAc;EAAU,CAAA;CACzD,EAAA,CAAA;AAEN"}
|