@evinvest/uikit 0.32.0 → 0.33.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/README.md +21 -10
- package/dist/components/atomic/accordion.js +2 -1
- package/dist/components/atomic/accordion.js.map +1 -1
- package/dist/components/atomic/alert.js +4 -4
- package/dist/components/atomic/alert.js.map +1 -1
- package/dist/components/atomic/badge.js +4 -3
- package/dist/components/atomic/badge.js.map +1 -1
- package/dist/components/atomic/breadcrumb.js +7 -9
- package/dist/components/atomic/breadcrumb.js.map +1 -1
- package/dist/components/atomic/button.js +4 -3
- package/dist/components/atomic/button.js.map +1 -1
- package/dist/components/atomic/calendar.js +10 -25
- package/dist/components/atomic/calendar.js.map +1 -1
- package/dist/components/atomic/checkbox.js +2 -1
- package/dist/components/atomic/checkbox.js.map +1 -1
- package/dist/components/atomic/command.js +5 -20
- package/dist/components/atomic/command.js.map +1 -1
- package/dist/components/atomic/context-menu.js +6 -13
- package/dist/components/atomic/context-menu.js.map +1 -1
- package/dist/components/atomic/dialog.js +4 -2
- package/dist/components/atomic/dialog.js.map +1 -1
- package/dist/components/atomic/dropdown-menu.js +6 -14
- package/dist/components/atomic/dropdown-menu.js.map +1 -1
- package/dist/components/atomic/hover-card.js.map +1 -1
- package/dist/components/atomic/info-tip.js +8 -9
- package/dist/components/atomic/info-tip.js.map +1 -1
- package/dist/components/atomic/input-otp.js +2 -1
- package/dist/components/atomic/input-otp.js.map +1 -1
- package/dist/components/atomic/key-value.js +5 -4
- package/dist/components/atomic/key-value.js.map +1 -1
- package/dist/components/atomic/menubar.js +7 -18
- package/dist/components/atomic/menubar.js.map +1 -1
- package/dist/components/atomic/native-select.js +1 -1
- package/dist/components/atomic/native-select.js.map +1 -1
- package/dist/components/atomic/navigation-menu.js +6 -14
- package/dist/components/atomic/navigation-menu.js.map +1 -1
- package/dist/components/atomic/pagination.js +18 -19
- package/dist/components/atomic/pagination.js.map +1 -1
- package/dist/components/atomic/popover.js.map +1 -1
- package/dist/components/atomic/radio-group.js +5 -4
- package/dist/components/atomic/radio-group.js.map +1 -1
- package/dist/components/atomic/section-descriptor.js +5 -8
- package/dist/components/atomic/section-descriptor.js.map +1 -1
- package/dist/components/atomic/select-listbox.js +2 -2
- package/dist/components/atomic/select-listbox.js.map +1 -1
- package/dist/components/atomic/select.js +5 -13
- package/dist/components/atomic/select.js.map +1 -1
- package/dist/components/atomic/sheet.js +4 -2
- package/dist/components/atomic/sheet.js.map +1 -1
- package/dist/components/atomic/sidebar.js +7 -26
- package/dist/components/atomic/sidebar.js.map +1 -1
- package/dist/components/atomic/slider.js.map +1 -1
- package/dist/components/atomic/sonner.js +20 -18
- package/dist/components/atomic/sonner.js.map +1 -1
- package/dist/components/atomic/spinner.js +6 -3
- package/dist/components/atomic/spinner.js.map +1 -1
- package/dist/components/atomic/tabs.js.map +1 -1
- package/dist/components/atomic/toggle-group.js +2 -2
- package/dist/components/atomic/toggle-group.js.map +1 -1
- package/dist/components/atomic/toggle.js +3 -6
- package/dist/components/atomic/toggle.js.map +1 -1
- package/dist/components/atomic/tooltip.js.map +1 -1
- package/dist/components/derived/carousel.js +6 -6
- package/dist/components/derived/carousel.js.map +1 -1
- package/dist/components/derived/command-palette.js +8 -7
- package/dist/components/derived/command-palette.js.map +1 -1
- package/dist/components/derived/confirm-dialog.js +8 -13
- package/dist/components/derived/confirm-dialog.js.map +1 -1
- package/dist/components/derived/date-time-picker.js +10 -14
- package/dist/components/derived/date-time-picker.js.map +1 -1
- package/dist/components/derived/field.js.map +1 -1
- package/dist/components/derived/form.js +0 -1
- package/dist/components/derived/form.js.map +1 -1
- package/dist/components/derived/icon-button.js.map +1 -1
- package/dist/components/derived/item.js +0 -1
- package/dist/components/derived/item.js.map +1 -1
- package/dist/components/derived/popover-form.js +4 -3
- package/dist/components/derived/popover-form.js.map +1 -1
- package/dist/components/derived/settings.js +4 -4
- package/dist/components/derived/settings.js.map +1 -1
- package/dist/components/shell/account-menu-profile.js +1 -1
- package/dist/components/shell/account-menu-profile.js.map +1 -1
- package/dist/components/shell/account-settings-dialog.js +3 -2
- package/dist/components/shell/account-settings-dialog.js.map +1 -1
- package/dist/components/shell/aside.js +6 -4
- package/dist/components/shell/aside.js.map +1 -1
- package/dist/components/shell/auth-wall.js +3 -5
- package/dist/components/shell/auth-wall.js.map +1 -1
- package/dist/components/shell/bottom-tab-bar.js +3 -3
- package/dist/components/shell/bottom-tab-bar.js.map +1 -1
- package/dist/components/shell/chat-composer.js +9 -7
- package/dist/components/shell/chat-composer.js.map +1 -1
- package/dist/components/shell/chat-message.js.map +1 -1
- package/dist/components/shell/chat-typing.js +2 -1
- package/dist/components/shell/chat-typing.js.map +1 -1
- package/dist/components/shell/footer.js +26 -24
- package/dist/components/shell/footer.js.map +1 -1
- package/dist/components/shell/header-controls.js +17 -15
- package/dist/components/shell/header-controls.js.map +1 -1
- package/dist/components/shell/hotbar.js +158 -0
- package/dist/components/shell/hotbar.js.map +1 -0
- package/dist/components/shell/logo.js +3 -2
- package/dist/components/shell/logo.js.map +1 -1
- package/dist/components/shell/mobile-app-bar.js +4 -4
- package/dist/components/shell/mobile-app-bar.js.map +1 -1
- package/dist/components/shell/nav-collapse.js +6 -5
- package/dist/components/shell/nav-collapse.js.map +1 -1
- package/dist/components/shell/resource-error.js +2 -2
- package/dist/components/shell/resource-error.js.map +1 -1
- package/dist/components/shell/section-nav.js +3 -3
- package/dist/components/shell/section-nav.js.map +1 -1
- package/dist/components/shell/shell-icons.js +7 -22
- package/dist/components/shell/shell-icons.js.map +1 -1
- package/dist/components/shell/shell-nav.js +4 -4
- package/dist/components/shell/shell-nav.js.map +1 -1
- package/dist/components/shell/smart-header.js.map +1 -1
- package/dist/components/shell/status-screen.js +35 -35
- package/dist/components/shell/status-screen.js.map +1 -1
- package/dist/components/shell/system-banner.js +6 -6
- package/dist/components/shell/system-banner.js.map +1 -1
- package/dist/components/shell/workspace-tab.js +2 -1
- package/dist/components/shell/workspace-tab.js.map +1 -1
- package/dist/generated/accent.js +10 -10
- package/dist/generated/accent.js.map +1 -1
- package/dist/generated/accordion.js +6 -2
- package/dist/generated/accordion.js.map +1 -1
- package/dist/generated/account-list.js +4 -4
- package/dist/generated/account-list.js.map +1 -1
- package/dist/generated/account-settings-dialog.js +6 -2
- package/dist/generated/account-settings-dialog.js.map +1 -1
- package/dist/generated/alert-dialog.js +1 -1
- package/dist/generated/alert-dialog.js.map +1 -1
- package/dist/generated/alert.js +3 -10
- package/dist/generated/alert.js.map +1 -1
- package/dist/generated/aside.js +7 -0
- package/dist/generated/aside.js.map +1 -1
- package/dist/generated/badge.js +2 -11
- package/dist/generated/badge.js.map +1 -1
- package/dist/generated/band.js +10 -10
- package/dist/generated/band.js.map +1 -1
- package/dist/generated/breadcrumb.js +1 -1
- package/dist/generated/breadcrumb.js.map +1 -1
- package/dist/generated/button-group.js +3 -3
- package/dist/generated/button-group.js.map +1 -1
- package/dist/generated/button.js +4 -20
- package/dist/generated/button.js.map +1 -1
- package/dist/generated/calendar.js +5 -5
- package/dist/generated/calendar.js.map +1 -1
- package/dist/generated/card.js +1 -1
- package/dist/generated/card.js.map +1 -1
- package/dist/generated/carousel.js +1 -1
- package/dist/generated/carousel.js.map +1 -1
- package/dist/generated/chat-composer.js +9 -2
- package/dist/generated/chat-composer.js.map +1 -1
- package/dist/generated/chat-message.js +25 -19
- package/dist/generated/chat-message.js.map +1 -1
- package/dist/generated/checkbox.js +1 -1
- package/dist/generated/checkbox.js.map +1 -1
- package/dist/generated/command-palette.js +10 -1
- package/dist/generated/command-palette.js.map +1 -1
- package/dist/generated/command.js +8 -6
- package/dist/generated/command.js.map +1 -1
- package/dist/generated/confirm-dialog.js +8 -8
- package/dist/generated/confirm-dialog.js.map +1 -1
- package/dist/generated/context-menu.js +7 -5
- package/dist/generated/context-menu.js.map +1 -1
- package/dist/generated/copy.js +97 -0
- package/dist/generated/copy.js.map +1 -0
- package/dist/generated/date-time-picker.js +10 -0
- package/dist/generated/date-time-picker.js.map +1 -1
- package/dist/generated/dialog.js +2 -2
- package/dist/generated/dialog.js.map +1 -1
- package/dist/generated/drawer.js +6 -6
- package/dist/generated/drawer.js.map +1 -1
- package/dist/generated/dropdown-menu.js +7 -5
- package/dist/generated/dropdown-menu.js.map +1 -1
- package/dist/generated/empty.js +5 -5
- package/dist/generated/empty.js.map +1 -1
- package/dist/generated/field.js +9 -7
- package/dist/generated/field.js.map +1 -1
- package/dist/generated/footer.js +45 -0
- package/dist/generated/footer.js.map +1 -0
- package/dist/generated/header-controls.js +5 -10
- package/dist/generated/header-controls.js.map +1 -1
- package/dist/generated/hover-card.js +1 -1
- package/dist/generated/hover-card.js.map +1 -1
- package/dist/generated/icon-button.js +4 -2
- package/dist/generated/icon-button.js.map +1 -1
- package/dist/generated/icons.js +36 -0
- package/dist/generated/icons.js.map +1 -0
- package/dist/generated/info-tip.js +5 -0
- package/dist/generated/info-tip.js.map +1 -0
- package/dist/generated/input-group.js +10 -10
- package/dist/generated/input-group.js.map +1 -1
- package/dist/generated/input-otp.js +2 -2
- package/dist/generated/input-otp.js.map +1 -1
- package/dist/generated/input.js +4 -4
- package/dist/generated/input.js.map +1 -1
- package/dist/generated/item.js +11 -11
- package/dist/generated/item.js.map +1 -1
- package/dist/generated/kbd.js +1 -1
- package/dist/generated/kbd.js.map +1 -1
- package/dist/generated/key-value.js +7 -1
- package/dist/generated/key-value.js.map +1 -1
- package/dist/generated/label.js +1 -1
- package/dist/generated/label.js.map +1 -1
- package/dist/generated/logo.js +9 -0
- package/dist/generated/logo.js.map +1 -0
- package/dist/generated/menu.js +7 -0
- package/dist/generated/menu.js.map +1 -0
- package/dist/generated/menubar.js +10 -8
- package/dist/generated/menubar.js.map +1 -1
- package/dist/generated/navigation-menu.js +11 -5
- package/dist/generated/navigation-menu.js.map +1 -1
- package/dist/generated/orientation.js +5 -0
- package/dist/generated/orientation.js.map +1 -0
- package/dist/generated/pagination.js +15 -0
- package/dist/generated/pagination.js.map +1 -0
- package/dist/generated/popover-form.js +5 -0
- package/dist/generated/popover-form.js.map +1 -1
- package/dist/generated/popover.js +1 -1
- package/dist/generated/popover.js.map +1 -1
- package/dist/generated/primitives.js +7 -0
- package/dist/generated/primitives.js.map +1 -0
- package/dist/generated/radio-group.js +5 -1
- package/dist/generated/radio-group.js.map +1 -1
- package/dist/generated/section-descriptor.js +7 -0
- package/dist/generated/section-descriptor.js.map +1 -0
- package/dist/generated/select.js +16 -10
- package/dist/generated/select.js.map +1 -1
- package/dist/generated/settings.js +2 -7
- package/dist/generated/settings.js.map +1 -1
- package/dist/generated/sheet.js +1 -1
- package/dist/generated/sheet.js.map +1 -1
- package/dist/generated/sidebar.js +24 -12
- package/dist/generated/sidebar.js.map +1 -1
- package/dist/generated/size.js +12 -0
- package/dist/generated/size.js.map +1 -0
- package/dist/generated/skeleton.js +1 -1
- package/dist/generated/skeleton.js.map +1 -1
- package/dist/generated/slider.js +2 -2
- package/dist/generated/slider.js.map +1 -1
- package/dist/generated/slot.js +5 -0
- package/dist/generated/slot.js.map +1 -0
- package/dist/generated/smart-header.js +2 -0
- package/dist/generated/smart-header.js.map +1 -1
- package/dist/generated/sonner.js +11 -13
- package/dist/generated/sonner.js.map +1 -1
- package/dist/generated/spinner.js +5 -0
- package/dist/generated/spinner.js.map +1 -0
- package/dist/generated/status-screen.js +27 -0
- package/dist/generated/status-screen.js.map +1 -0
- package/dist/generated/switch.js +2 -2
- package/dist/generated/switch.js.map +1 -1
- package/dist/generated/system-banner.js +6 -16
- package/dist/generated/system-banner.js.map +1 -1
- package/dist/generated/table.js +9 -9
- package/dist/generated/table.js.map +1 -1
- package/dist/generated/tabs.js +2 -2
- package/dist/generated/tabs.js.map +1 -1
- package/dist/generated/textarea.js +1 -1
- package/dist/generated/textarea.js.map +1 -1
- package/dist/generated/toggle-group.js +9 -0
- package/dist/generated/toggle-group.js.map +1 -0
- package/dist/generated/toggle.js +1 -9
- package/dist/generated/toggle.js.map +1 -1
- package/dist/generated/tone.js +12 -0
- package/dist/generated/tone.js.map +1 -0
- package/dist/generated/tooltip.js +1 -1
- package/dist/generated/tooltip.js.map +1 -1
- package/dist/generated/workspace-tab.js +3 -3
- package/dist/generated/workspace-tab.js.map +1 -1
- package/dist/icons/icon-shapes.js +10 -0
- package/dist/icons/icon-shapes.js.map +1 -0
- package/dist/icons/select-chevron.js +2 -1
- package/dist/icons/select-chevron.js.map +1 -1
- package/dist/icons/ui-icons.js +3 -57
- package/dist/icons/ui-icons.js.map +1 -1
- package/dist/index.d.ts +203 -155
- package/dist/index.js +1 -0
- package/dist/lib/cn.js +1 -0
- package/dist/lib/cn.js.map +1 -1
- package/dist/primitives/use-floating.js.map +1 -1
- package/dist/primitives/use-roving-focus.js.map +1 -1
- package/package.json +1 -1
- package/styles/ev.css +13 -76
- package/styles/theme.css +133 -66
- package/styles/tokens.css +157 -149
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/shell/chat-message.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../../lib/cn\";\nimport {\n CHAT_ATTACHMENT_BASE,\n CHAT_ATTACHMENT_BODY,\n CHAT_ATTACHMENT_ICON,\n CHAT_ATTACHMENT_META,\n CHAT_ATTACHMENT_NAME,\n CHAT_ATTACHMENT_PROGRESS,\n CHAT_ATTACHMENT_PROGRESS_FILL,\n CHAT_ATTACHMENT_ROW,\n CHAT_BUBBLE_BASE,\n CHAT_MARKER_DOT,\n CHAT_MARKER_RULE,\n CHAT_MARKER_SEPARATOR,\n CHAT_MARKER_SEPARATOR_LABEL,\n CHAT_MARKER_STATUS,\n CHAT_MARKER_TOOL,\n CHAT_MARKER_TOOL_BODY,\n CHAT_MARKER_TOOL_BORDER,\n CHAT_MARKER_TOOL_CHECK,\n CHAT_MARKER_TOOL_DETAIL,\n CHAT_MARKER_TOOL_ERROR_BORDER,\n CHAT_MARKER_TOOL_ICON,\n CHAT_MARKER_TOOL_META,\n CHAT_MARKER_TOOL_TITLE,\n CHAT_MESSAGE_AVATAR,\n CHAT_MESSAGE_AVATAR_TILE,\n CHAT_MESSAGE_BASE,\n CHAT_MESSAGE_BODY,\n CHAT_MESSAGE_BODY_ASSISTANT,\n CHAT_MESSAGE_BODY_USER,\n CHAT_MESSAGE_HEADER,\n CHAT_MESSAGE_NAME,\n CHAT_MESSAGE_TIME,\n CHAT_MESSAGE_USER,\n chatBubbleVariantClasses,\n chatStatusDotClasses,\n chatToolMetaClasses,\n} from \"../../generated/chat-message\";\nimport type { ChatBubbleVariant, ChatStatusState, ChatToolState } from \"../../generated/chat-message\";\nimport { ChatStreamText } from \"./chat-stream-text\";\nimport { IconButton } from \"../derived/icon-button\";\nimport { UiIcon } from \"../../icons/ui-icons\";\n\nexport type { ChatBubbleVariant };\n\nexport interface ChatBubbleProps extends React.ComponentProps<\"div\"> {\n variant?: ChatBubbleVariant;\n /**\n * Still receiving text: a soft caret pulses after it, and string children\n * fade in word by word (`ChatStreamText`). Stays set until the text is complete.\n */\n streaming?: boolean;\n}\n\n/** One block of message text; `assistant` has no box so long answers read as a page. */\nexport function ChatBubble({ variant = \"assistant\", streaming = false, className, children, ...props }: ChatBubbleProps) {\n // Once a bubble has streamed it keeps its word wrappers and its caret slot, so\n // the last words finish landing and the caret fades out instead of vanishing.\n const [live, setLive] = React.useState(streaming);\n if (streaming && !live) setLive(true);\n return (\n <div\n data-slot=\"chat-bubble\"\n data-variant={variant}\n data-streaming={streaming ? \"\" : undefined}\n data-caret={live ? \"\" : undefined}\n className={cn(CHAT_BUBBLE_BASE, chatBubbleVariantClasses[variant], className)}\n {...props}\n >\n {live && typeof children === \"string\" ? <ChatStreamText text={children} streaming={streaming} /> : children}\n </div>\n );\n}\n\nexport interface ChatMessageProps extends Omit<React.ComponentProps<\"div\">, \"role\"> {\n role: \"assistant\" | \"user\";\n /** \"Assistant\", \"You\". */\n name?: React.ReactNode;\n /** Already formatted: \"13:04\". */\n time?: React.ReactNode;\n /** The assistant's tile; a default sparkle tile when omitted. */\n avatar?: React.ReactNode;\n /** Drops the name row and the avatar, for a narrow dock. */\n compact?: boolean;\n}\n\n/** A turn: who spoke, when, then the bubbles and markers. Fades in and rises once when it mounts. */\nexport function ChatMessage({ role, name, time, avatar, compact = false, className, children, ...props }: ChatMessageProps) {\n const user = role === \"user\";\n return (\n <div\n data-slot=\"chat-message\"\n data-role={role}\n data-enter=\"rise\"\n className={cn(CHAT_MESSAGE_BASE, user && CHAT_MESSAGE_USER, className)}\n {...props}\n >\n {!user && !compact && (\n <div className={CHAT_MESSAGE_AVATAR}>\n {avatar ?? (\n <span className={CHAT_MESSAGE_AVATAR_TILE}>\n <UiIcon name=\"sparkles\" />\n </span>\n )}\n </div>\n )}\n <div className={cn(CHAT_MESSAGE_BODY, user ? CHAT_MESSAGE_BODY_USER : CHAT_MESSAGE_BODY_ASSISTANT)}>\n {!compact && (name != null || time != null) && (\n <div className={CHAT_MESSAGE_HEADER}>\n {name != null && <span className={CHAT_MESSAGE_NAME}>{name}</span>}\n {time != null && <span className={CHAT_MESSAGE_TIME}>{time}</span>}\n </div>\n )}\n {children}\n </div>\n </div>\n );\n}\n\nexport type ChatMarkerProps = Omit<React.ComponentProps<\"div\">, \"title\"> &\n (\n | { type: \"status\"; state?: ChatStatusState }\n | {\n type: \"tool\";\n state?: ChatToolState;\n /** The tool's name. */\n title: React.ReactNode;\n detail?: React.ReactNode;\n /** The right-hand note: \"Running…\", \"0.4s\", \"Failed · 1.2s\". */\n meta?: React.ReactNode;\n }\n | { type: \"separator\" }\n );\n\n/** The quiet lines between messages: progress, a tool call, or a day break. */\nexport function ChatMarker(allProps: ChatMarkerProps) {\n if (allProps.type === \"separator\") {\n const { type: _type, className, children, ...rest } = allProps;\n return (\n <div data-slot=\"chat-marker\" data-type=\"separator\" className={cn(CHAT_MARKER_SEPARATOR, className)} {...rest}>\n <span className={CHAT_MARKER_RULE} />\n <span className={CHAT_MARKER_SEPARATOR_LABEL}>{children}</span>\n <span className={CHAT_MARKER_RULE} />\n </div>\n );\n }\n if (allProps.type === \"status\") {\n const { type: _type, state = \"done\", className, children, ...rest } = allProps;\n return (\n <div\n data-slot=\"chat-marker\"\n data-type=\"status\"\n data-state={state}\n role={state === \"running\" ? \"status\" : undefined}\n className={cn(CHAT_MARKER_STATUS, className)}\n {...rest}\n >\n <span\n aria-hidden\n className={cn(CHAT_MARKER_DOT, chatStatusDotClasses[state])}\n />\n {children}\n </div>\n );\n }\n const { type: _type, state = \"done\", title, detail, meta, className, children, ...rest } = allProps;\n return (\n <div\n data-slot=\"chat-marker\"\n data-type=\"tool\"\n data-state={state}\n className={cn(\n CHAT_MARKER_TOOL,\n state === \"error\" ? CHAT_MARKER_TOOL_ERROR_BORDER : CHAT_MARKER_TOOL_BORDER,\n className,\n )}\n {...rest}\n >\n <UiIcon name={state === \"error\" ? \"alert\" : \"sparkles\"} className={CHAT_MARKER_TOOL_ICON} />\n <div className={CHAT_MARKER_TOOL_BODY}>\n <span className={CHAT_MARKER_TOOL_TITLE}>{title}</span>\n {detail != null && <span className={CHAT_MARKER_TOOL_DETAIL}>{detail}</span>}\n {children}\n </div>\n {meta != null && (\n <span className={cn(CHAT_MARKER_TOOL_META, chatToolMetaClasses[state])}>{meta}\n </span>\n )}\n {state === \"done\" && <UiIcon name=\"check\" className={CHAT_MARKER_TOOL_CHECK} />}\n </div>\n );\n}\n\nexport interface ChatAttachmentProps extends Omit<React.ComponentProps<\"div\">, \"children\"> {\n type?: \"file\" | \"image\";\n state?: \"ready\" | \"uploading\";\n name: string;\n /** \"PDF · 1.2 MB\"; while uploading, defaults to the percentage. */\n meta?: React.ReactNode;\n /** 0 to 100, while uploading. */\n progress?: number;\n /** Shows the remove button. */\n onRemove?: () => void;\n /** Default `\"Remove {name}\"`. */\n removeLabel?: string;\n}\n\n/** A card for a file in a message or in the composer. */\nexport function ChatAttachment({\n type = \"file\",\n state = \"ready\",\n name,\n meta,\n progress = 0,\n onRemove,\n removeLabel,\n className,\n ...props\n}: ChatAttachmentProps) {\n const pct = Math.min(100, Math.max(0, Math.round(progress)));\n return (\n <div\n data-slot=\"chat-attachment\"\n data-type={type}\n data-state={state}\n className={cn(CHAT_ATTACHMENT_BASE, className)}\n {...props}\n >\n <div className={CHAT_ATTACHMENT_ROW}>\n <span className={CHAT_ATTACHMENT_ICON}>\n <UiIcon name={type === \"image\" ? \"image\" : \"file\"} />\n </span>\n <div className={CHAT_ATTACHMENT_BODY}>\n <span className={CHAT_ATTACHMENT_NAME}>{name}</span>\n <span className={CHAT_ATTACHMENT_META}>{meta ?? (state === \"uploading\" ? `Uploading… ${pct}%` : null)}</span>\n </div>\n {onRemove && (\n <IconButton aria-label={removeLabel ?? `Remove ${name}`} size=\"sm\" onClick={onRemove}>\n <UiIcon name=\"x\" />\n </IconButton>\n )}\n </div>\n {state === \"uploading\" && (\n <div\n role=\"progressbar\"\n aria-label={name}\n aria-valuemin={0}\n aria-valuemax={100}\n aria-valuenow={pct}\n className={CHAT_ATTACHMENT_PROGRESS}\n >\n <div className={CHAT_ATTACHMENT_PROGRESS_FILL} style={{ width: `${pct}%` }} />\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":["_type","className","children","rest","state"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/components/shell/chat-message.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../../lib/cn\";\nimport {\n CHAT_ATTACHMENT_BASE,\n CHAT_ATTACHMENT_BODY,\n CHAT_ATTACHMENT_ICON,\n CHAT_ATTACHMENT_META,\n CHAT_ATTACHMENT_NAME,\n CHAT_ATTACHMENT_PROGRESS,\n CHAT_ATTACHMENT_PROGRESS_FILL,\n CHAT_ATTACHMENT_ROW,\n CHAT_BUBBLE_BASE,\n CHAT_MARKER_DOT,\n CHAT_MARKER_RULE,\n CHAT_MARKER_SEPARATOR,\n CHAT_MARKER_SEPARATOR_LABEL,\n CHAT_MARKER_STATUS,\n CHAT_MARKER_TOOL,\n CHAT_MARKER_TOOL_BODY,\n CHAT_MARKER_TOOL_BORDER,\n CHAT_MARKER_TOOL_CHECK,\n CHAT_MARKER_TOOL_DETAIL,\n CHAT_MARKER_TOOL_ERROR_BORDER,\n CHAT_MARKER_TOOL_ICON,\n CHAT_MARKER_TOOL_META,\n CHAT_MARKER_TOOL_TITLE,\n CHAT_MESSAGE_AVATAR,\n CHAT_MESSAGE_AVATAR_TILE,\n CHAT_MESSAGE_BASE,\n CHAT_MESSAGE_BODY,\n CHAT_MESSAGE_BODY_ASSISTANT,\n CHAT_MESSAGE_BODY_USER,\n CHAT_MESSAGE_HEADER,\n CHAT_MESSAGE_NAME,\n CHAT_MESSAGE_TIME,\n CHAT_MESSAGE_USER,\n chatBubbleVariantClasses,\n chatStatusDotClasses,\n chatToolMetaClasses,\n} from \"../../generated/chat-message\";\nimport type { ChatBubbleVariant, ChatStatusState, ChatToolState } from \"../../generated/chat-message\";\nimport { ChatStreamText } from \"./chat-stream-text\";\nimport { IconButton } from \"../derived/icon-button\";\nimport { UiIcon } from \"../../icons/ui-icons\";\nimport type { ChatAttachmentState, ChatAttachmentType, ChatMessageRole } from \"../../generated/chat-message\";\n\nexport type { ChatBubbleVariant };\n\nexport interface ChatBubbleProps extends React.ComponentProps<\"div\"> {\n variant?: ChatBubbleVariant;\n /**\n * Still receiving text: a soft caret pulses after it, and string children\n * fade in word by word (`ChatStreamText`). Stays set until the text is complete.\n */\n streaming?: boolean;\n}\n\n/** One block of message text; `assistant` has no box so long answers read as a page. */\nexport function ChatBubble({ variant = \"assistant\", streaming = false, className, children, ...props }: ChatBubbleProps) {\n // Once a bubble has streamed it keeps its word wrappers and its caret slot, so\n // the last words finish landing and the caret fades out instead of vanishing.\n const [live, setLive] = React.useState(streaming);\n if (streaming && !live) setLive(true);\n return (\n <div\n data-slot=\"chat-bubble\"\n data-variant={variant}\n data-streaming={streaming ? \"\" : undefined}\n data-caret={live ? \"\" : undefined}\n className={cn(CHAT_BUBBLE_BASE, chatBubbleVariantClasses[variant], className)}\n {...props}\n >\n {live && typeof children === \"string\" ? <ChatStreamText text={children} streaming={streaming} /> : children}\n </div>\n );\n}\n\nexport interface ChatMessageProps extends Omit<React.ComponentProps<\"div\">, \"role\"> {\n role: ChatMessageRole;\n /** \"Assistant\", \"You\". */\n name?: React.ReactNode;\n /** Already formatted: \"13:04\". */\n time?: React.ReactNode;\n /** The assistant's tile; a default sparkle tile when omitted. */\n avatar?: React.ReactNode;\n /** Drops the name row and the avatar, for a narrow dock. */\n compact?: boolean;\n}\n\n/** A turn: who spoke, when, then the bubbles and markers. Fades in and rises once when it mounts. */\nexport function ChatMessage({ role, name, time, avatar, compact = false, className, children, ...props }: ChatMessageProps) {\n const user = role === \"user\";\n return (\n <div\n data-slot=\"chat-message\"\n data-role={role}\n data-enter=\"rise\"\n className={cn(CHAT_MESSAGE_BASE, user && CHAT_MESSAGE_USER, className)}\n {...props}\n >\n {!user && !compact && (\n <div className={CHAT_MESSAGE_AVATAR}>\n {avatar ?? (\n <span className={CHAT_MESSAGE_AVATAR_TILE}>\n <UiIcon name=\"sparkles\" />\n </span>\n )}\n </div>\n )}\n <div className={cn(CHAT_MESSAGE_BODY, user ? CHAT_MESSAGE_BODY_USER : CHAT_MESSAGE_BODY_ASSISTANT)}>\n {!compact && (name != null || time != null) && (\n <div className={CHAT_MESSAGE_HEADER}>\n {name != null && <span className={CHAT_MESSAGE_NAME}>{name}</span>}\n {time != null && <span className={CHAT_MESSAGE_TIME}>{time}</span>}\n </div>\n )}\n {children}\n </div>\n </div>\n );\n}\n\nexport type ChatMarkerProps = Omit<React.ComponentProps<\"div\">, \"title\"> &\n (\n | { type: \"status\"; state?: ChatStatusState }\n | {\n type: \"tool\";\n state?: ChatToolState;\n /** The tool's name. */\n title: React.ReactNode;\n detail?: React.ReactNode;\n /** The right-hand note: \"Running…\", \"0.4s\", \"Failed · 1.2s\". */\n meta?: React.ReactNode;\n }\n | { type: \"separator\" }\n );\n\n/** The quiet lines between messages: progress, a tool call, or a day break. */\nexport function ChatMarker(allProps: ChatMarkerProps) {\n if (allProps.type === \"separator\") {\n const { type: _type, className, children, ...rest } = allProps;\n return (\n <div data-slot=\"chat-marker\" data-type=\"separator\" className={cn(CHAT_MARKER_SEPARATOR, className)} {...rest}>\n <span className={CHAT_MARKER_RULE} />\n <span className={CHAT_MARKER_SEPARATOR_LABEL}>{children}</span>\n <span className={CHAT_MARKER_RULE} />\n </div>\n );\n }\n if (allProps.type === \"status\") {\n const { type: _type, state = \"done\", className, children, ...rest } = allProps;\n return (\n <div\n data-slot=\"chat-marker\"\n data-type=\"status\"\n data-state={state}\n role={state === \"running\" ? \"status\" : undefined}\n className={cn(CHAT_MARKER_STATUS, className)}\n {...rest}\n >\n <span\n aria-hidden\n className={cn(CHAT_MARKER_DOT, chatStatusDotClasses[state])}\n />\n {children}\n </div>\n );\n }\n const { type: _type, state = \"done\", title, detail, meta, className, children, ...rest } = allProps;\n return (\n <div\n data-slot=\"chat-marker\"\n data-type=\"tool\"\n data-state={state}\n className={cn(\n CHAT_MARKER_TOOL,\n state === \"error\" ? CHAT_MARKER_TOOL_ERROR_BORDER : CHAT_MARKER_TOOL_BORDER,\n className,\n )}\n {...rest}\n >\n <UiIcon name={state === \"error\" ? \"alert\" : \"sparkles\"} className={CHAT_MARKER_TOOL_ICON} />\n <div className={CHAT_MARKER_TOOL_BODY}>\n <span className={CHAT_MARKER_TOOL_TITLE}>{title}</span>\n {detail != null && <span className={CHAT_MARKER_TOOL_DETAIL}>{detail}</span>}\n {children}\n </div>\n {meta != null && (\n <span className={cn(CHAT_MARKER_TOOL_META, chatToolMetaClasses[state])}>{meta}\n </span>\n )}\n {state === \"done\" && <UiIcon name=\"check\" className={CHAT_MARKER_TOOL_CHECK} />}\n </div>\n );\n}\n\nexport interface ChatAttachmentProps extends Omit<React.ComponentProps<\"div\">, \"children\"> {\n type?: ChatAttachmentType;\n state?: ChatAttachmentState;\n name: string;\n /** \"PDF · 1.2 MB\"; while uploading, defaults to the percentage. */\n meta?: React.ReactNode;\n /** 0 to 100, while uploading. */\n progress?: number;\n /** Shows the remove button. */\n onRemove?: () => void;\n /** Default `\"Remove {name}\"`. */\n removeLabel?: string;\n}\n\n/** A card for a file in a message or in the composer. */\nexport function ChatAttachment({\n type = \"file\",\n state = \"ready\",\n name,\n meta,\n progress = 0,\n onRemove,\n removeLabel,\n className,\n ...props\n}: ChatAttachmentProps) {\n const pct = Math.min(100, Math.max(0, Math.round(progress)));\n return (\n <div\n data-slot=\"chat-attachment\"\n data-type={type}\n data-state={state}\n className={cn(CHAT_ATTACHMENT_BASE, className)}\n {...props}\n >\n <div className={CHAT_ATTACHMENT_ROW}>\n <span className={CHAT_ATTACHMENT_ICON}>\n <UiIcon name={type === \"image\" ? \"image\" : \"file\"} />\n </span>\n <div className={CHAT_ATTACHMENT_BODY}>\n <span className={CHAT_ATTACHMENT_NAME}>{name}</span>\n <span className={CHAT_ATTACHMENT_META}>{meta ?? (state === \"uploading\" ? `Uploading… ${pct}%` : null)}</span>\n </div>\n {onRemove && (\n <IconButton aria-label={removeLabel ?? `Remove ${name}`} size=\"sm\" onClick={onRemove}>\n <UiIcon name=\"x\" />\n </IconButton>\n )}\n </div>\n {state === \"uploading\" && (\n <div\n role=\"progressbar\"\n aria-label={name}\n aria-valuemin={0}\n aria-valuemax={100}\n aria-valuenow={pct}\n className={CHAT_ATTACHMENT_PROGRESS}\n >\n <div className={CHAT_ATTACHMENT_PROGRESS_FILL} style={{ width: `${pct}%` }} />\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":["_type","className","children","rest","state"]}
|
|
@@ -2,7 +2,8 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
import { CHAT_TYPING_BASE, CHAT_TYPING_DOT, CHAT_TYPING_STILL } from "../../generated/chat-typing.js";
|
|
4
4
|
import { cn } from "../../lib/cn.js";
|
|
5
|
-
|
|
5
|
+
import * as copy from "../../generated/copy.js";
|
|
6
|
+
function ChatTyping({ label = copy.CHAT_TYPING, className, ...props }) {
|
|
6
7
|
return /* @__PURE__ */ jsxs(
|
|
7
8
|
"span",
|
|
8
9
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/shell/chat-typing.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { CHAT_TYPING_BASE, CHAT_TYPING_DOT, CHAT_TYPING_STILL } from \"../../generated/chat-typing\";\nimport { cn } from \"../../lib/cn\";\n\nexport interface ChatTypingProps extends Omit<React.ComponentProps<\"span\">, \"children\"> {\n /** Names the state for assistive tech. Default `\"Assistant is thinking\"`. */\n label?: string;\n}\n\n/**\n * The wait before the first token: three dots whose opacity ripples. Under\n * reduced motion CSS swaps them for a still \"…\". It is a status region, so a\n * screen reader hears the label once when it appears.\n */\nexport function ChatTyping({ label =
|
|
1
|
+
{"version":3,"sources":["../../../src/components/shell/chat-typing.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { CHAT_TYPING_BASE, CHAT_TYPING_DOT, CHAT_TYPING_STILL } from \"../../generated/chat-typing\";\nimport { cn } from \"../../lib/cn\";\nimport * as copy from \"../../generated/copy\";\n\nexport interface ChatTypingProps extends Omit<React.ComponentProps<\"span\">, \"children\"> {\n /** Names the state for assistive tech. Default `\"Assistant is thinking\"`. */\n label?: string;\n}\n\n/**\n * The wait before the first token: three dots whose opacity ripples. Under\n * reduced motion CSS swaps them for a still \"…\". It is a status region, so a\n * screen reader hears the label once when it appears.\n */\nexport function ChatTyping({ label = copy.CHAT_TYPING, className, ...props }: ChatTypingProps) {\n return (\n <span\n role=\"status\"\n aria-label={label}\n data-slot=\"chat-typing\"\n className={cn(CHAT_TYPING_BASE, className)}\n {...props}\n >\n <span aria-hidden data-dot className={CHAT_TYPING_DOT} />\n <span aria-hidden data-dot className={CHAT_TYPING_DOT} />\n <span aria-hidden data-dot className={CHAT_TYPING_DOT} />\n <span aria-hidden data-still className={CHAT_TYPING_STILL}>\n …\n </span>\n </span>\n );\n}\n"],"mappings":"AAiBI,SAOE,KAPF;AAjBJ,YAAY,WAAW;AACvB,SAAS,kBAAkB,iBAAiB,yBAAyB;AACrE,SAAS,UAAU;AACnB,YAAY,UAAU;AAYf,SAAS,WAAW,EAAE,QAAQ,KAAK,aAAa,WAAW,GAAG,MAAM,GAAoB;AAC7F,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,cAAY;AAAA,MACZ,aAAU;AAAA,MACV,WAAW,GAAG,kBAAkB,SAAS;AAAA,MACxC,GAAG;AAAA,MAEJ;AAAA,4BAAC,UAAK,eAAW,MAAC,YAAQ,MAAC,WAAW,iBAAiB;AAAA,QACvD,oBAAC,UAAK,eAAW,MAAC,YAAQ,MAAC,WAAW,iBAAiB;AAAA,QACvD,oBAAC,UAAK,eAAW,MAAC,YAAQ,MAAC,WAAW,iBAAiB;AAAA,QACvD,oBAAC,UAAK,eAAW,MAAC,cAAU,MAAC,WAAW,mBAAmB,oBAE3D;AAAA;AAAA;AAAA,EACF;AAEJ;","names":[]}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
import { Container } from "../atomic/container.js";
|
|
4
|
-
|
|
4
|
+
import * as copy from "../../generated/copy.js";
|
|
5
|
+
import { FOOTER_BRAND, FOOTER_COPYRIGHT, FOOTER_DESCRIPTION, FOOTER_GRID, FOOTER_HEADING, FOOTER_LEGAL_LINK, FOOTER_LEGAL_LINKS, FOOTER_LEGAL_SEPARATOR, FOOTER_LOCKUP, FOOTER_LOCKUP_TEXT, FOOTER_NAV_COLUMN, FOOTER_NAV_LINK, FOOTER_NAV_LIST, FOOTER_NEWSLETTER_BLURB, FOOTER_NEWSLETTER_COLUMN, FOOTER_OFFICES_LIST, FOOTER_OFFICE_NAME, FOOTER_ROOT, FOOTER_TAGLINE, FOOTER_VERSION, FOOTER_WIDE_COLUMN } from "../../generated/footer.js";
|
|
6
|
+
const HEADING = FOOTER_HEADING;
|
|
5
7
|
function Footer({
|
|
6
8
|
lockup,
|
|
7
9
|
brand,
|
|
@@ -11,10 +13,10 @@ function Footer({
|
|
|
11
13
|
copyright,
|
|
12
14
|
nav = [],
|
|
13
15
|
offices = [],
|
|
14
|
-
officesHeading =
|
|
16
|
+
officesHeading = copy.FOOTER_OFFICES,
|
|
15
17
|
legalLinks = [],
|
|
16
18
|
newsletter,
|
|
17
|
-
newsletterHeading =
|
|
19
|
+
newsletterHeading = copy.FOOTER_NEWSLETTER,
|
|
18
20
|
newsletterBlurb = "",
|
|
19
21
|
version,
|
|
20
22
|
commitHref,
|
|
@@ -24,47 +26,47 @@ function Footer({
|
|
|
24
26
|
const L = linkComponent ?? "a";
|
|
25
27
|
const composed = brand !== void 0 || mark !== void 0;
|
|
26
28
|
const hasBrandColumn = lockup !== void 0 || composed || description !== void 0 || legalLinks.length > 0;
|
|
27
|
-
return /* @__PURE__ */ jsxs("footer", { "data-slot": "footer", className:
|
|
29
|
+
return /* @__PURE__ */ jsxs("footer", { "data-slot": "footer", className: FOOTER_ROOT, children: [
|
|
28
30
|
children,
|
|
29
31
|
/* @__PURE__ */ jsxs(Container, { children: [
|
|
30
|
-
/* @__PURE__ */ jsxs("div", { className:
|
|
31
|
-
hasBrandColumn && /* @__PURE__ */ jsxs("div", { className:
|
|
32
|
+
/* @__PURE__ */ jsxs("div", { className: FOOTER_GRID, children: [
|
|
33
|
+
hasBrandColumn && /* @__PURE__ */ jsxs("div", { className: FOOTER_WIDE_COLUMN, children: [
|
|
32
34
|
lockup !== void 0 ? /* @__PURE__ */ jsx("div", { "data-slot": "footer-lockup", className: "mb-6", children: lockup }) : composed && /* @__PURE__ */ jsx(FooterLockup, { brand, mark, tagline }),
|
|
33
|
-
description !== void 0 && /* @__PURE__ */ jsx("p", { className:
|
|
34
|
-
legalLinks.length > 0 && /* @__PURE__ */ jsx("div", { className:
|
|
35
|
-
i > 0 && /* @__PURE__ */ jsx("span", { className:
|
|
36
|
-
/* @__PURE__ */ jsx(L, { href: link.href, className:
|
|
35
|
+
description !== void 0 && /* @__PURE__ */ jsx("p", { className: FOOTER_DESCRIPTION, children: description }),
|
|
36
|
+
legalLinks.length > 0 && /* @__PURE__ */ jsx("div", { className: FOOTER_LEGAL_LINKS, children: legalLinks.map((link, i) => /* @__PURE__ */ jsxs(React.Fragment, { children: [
|
|
37
|
+
i > 0 && /* @__PURE__ */ jsx("span", { className: FOOTER_LEGAL_SEPARATOR, children: "|" }),
|
|
38
|
+
/* @__PURE__ */ jsx(L, { href: link.href, className: FOOTER_LEGAL_LINK, children: link.label })
|
|
37
39
|
] }, link.label)) })
|
|
38
40
|
] }),
|
|
39
|
-
nav.map((group) => /* @__PURE__ */ jsxs("nav", { "aria-label": `Footer ${group.heading} links`, className:
|
|
41
|
+
nav.map((group) => /* @__PURE__ */ jsxs("nav", { "aria-label": `Footer ${group.heading} links`, className: FOOTER_NAV_COLUMN, children: [
|
|
40
42
|
/* @__PURE__ */ jsx("h4", { className: HEADING, children: group.heading }),
|
|
41
|
-
/* @__PURE__ */ jsx("ul", { className:
|
|
43
|
+
/* @__PURE__ */ jsx("ul", { className: FOOTER_NAV_LIST, children: group.links.map((link) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
|
|
42
44
|
L,
|
|
43
45
|
{
|
|
44
46
|
href: link.href,
|
|
45
|
-
className:
|
|
47
|
+
className: FOOTER_NAV_LINK,
|
|
46
48
|
children: link.label
|
|
47
49
|
}
|
|
48
50
|
) }, link.href)) })
|
|
49
51
|
] }, group.heading)),
|
|
50
|
-
offices.length > 0 && /* @__PURE__ */ jsxs("div", { className:
|
|
52
|
+
offices.length > 0 && /* @__PURE__ */ jsxs("div", { className: FOOTER_WIDE_COLUMN, children: [
|
|
51
53
|
/* @__PURE__ */ jsx("h4", { className: HEADING, children: officesHeading }),
|
|
52
|
-
/* @__PURE__ */ jsx("ul", { className:
|
|
53
|
-
/* @__PURE__ */ jsx("strong", { className:
|
|
54
|
+
/* @__PURE__ */ jsx("ul", { className: FOOTER_OFFICES_LIST, children: offices.map((office) => /* @__PURE__ */ jsxs("li", { children: [
|
|
55
|
+
/* @__PURE__ */ jsx("strong", { className: FOOTER_OFFICE_NAME, children: office.name }),
|
|
54
56
|
office.address
|
|
55
57
|
] }, office.name)) })
|
|
56
58
|
] }),
|
|
57
|
-
newsletter && /* @__PURE__ */ jsxs("div", { className:
|
|
59
|
+
newsletter && /* @__PURE__ */ jsxs("div", { className: FOOTER_NEWSLETTER_COLUMN, children: [
|
|
58
60
|
/* @__PURE__ */ jsx("h4", { className: HEADING, children: newsletterHeading }),
|
|
59
|
-
/* @__PURE__ */ jsx("p", { className:
|
|
61
|
+
/* @__PURE__ */ jsx("p", { className: FOOTER_NEWSLETTER_BLURB, children: newsletterBlurb }),
|
|
60
62
|
newsletter
|
|
61
63
|
] })
|
|
62
64
|
] }),
|
|
63
|
-
(copyright !== void 0 || version !== void 0) && /* @__PURE__ */ jsx("div", { className:
|
|
65
|
+
(copyright !== void 0 || version !== void 0) && /* @__PURE__ */ jsx("div", { className: FOOTER_COPYRIGHT, children: /* @__PURE__ */ jsxs("p", { children: [
|
|
64
66
|
copyright,
|
|
65
67
|
version && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
66
68
|
copyright !== void 0 && " ",
|
|
67
|
-
/* @__PURE__ */ jsx("a", { href: commitHref, className:
|
|
69
|
+
/* @__PURE__ */ jsx("a", { href: commitHref, className: FOOTER_VERSION, children: version })
|
|
68
70
|
] })
|
|
69
71
|
] }) })
|
|
70
72
|
] })
|
|
@@ -75,11 +77,11 @@ function FooterLockup({
|
|
|
75
77
|
mark,
|
|
76
78
|
tagline
|
|
77
79
|
}) {
|
|
78
|
-
return /* @__PURE__ */ jsxs("div", { "data-slot": "footer-lockup", className:
|
|
80
|
+
return /* @__PURE__ */ jsxs("div", { "data-slot": "footer-lockup", className: FOOTER_LOCKUP, children: [
|
|
79
81
|
mark,
|
|
80
|
-
(brand !== void 0 || tagline !== void 0) && /* @__PURE__ */ jsxs("div", { className:
|
|
81
|
-
brand !== void 0 && /* @__PURE__ */ jsx("span", { className:
|
|
82
|
-
tagline !== void 0 && /* @__PURE__ */ jsx("span", { className:
|
|
82
|
+
(brand !== void 0 || tagline !== void 0) && /* @__PURE__ */ jsxs("div", { className: FOOTER_LOCKUP_TEXT, children: [
|
|
83
|
+
brand !== void 0 && /* @__PURE__ */ jsx("span", { className: FOOTER_BRAND, children: brand }),
|
|
84
|
+
tagline !== void 0 && /* @__PURE__ */ jsx("span", { className: FOOTER_TAGLINE, children: tagline })
|
|
83
85
|
] })
|
|
84
86
|
] });
|
|
85
87
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/shell/footer.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { Container } from \"../atomic/container\";\
|
|
1
|
+
{"version":3,"sources":["../../../src/components/shell/footer.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { Container } from \"../atomic/container\";\nimport * as copy from \"../../generated/copy\";\nimport type { FooterLink, FooterLinkGroup, FooterOffice } from \"../../generated/footer\";\nimport { FOOTER_BRAND, FOOTER_COPYRIGHT, FOOTER_DESCRIPTION, FOOTER_GRID, FOOTER_HEADING, FOOTER_LEGAL_LINK, FOOTER_LEGAL_LINKS, FOOTER_LEGAL_SEPARATOR, FOOTER_LOCKUP, FOOTER_LOCKUP_TEXT, FOOTER_NAV_COLUMN, FOOTER_NAV_LINK, FOOTER_NAV_LIST, FOOTER_NEWSLETTER_BLURB, FOOTER_NEWSLETTER_COLUMN, FOOTER_OFFICES_LIST, FOOTER_OFFICE_NAME, FOOTER_ROOT, FOOTER_TAGLINE, FOOTER_VERSION, FOOTER_WIDE_COLUMN } from \"../../generated/footer\";\n\nexport type { FooterLink, FooterLinkGroup, FooterOffice };\n\nexport interface FooterProps {\n /** The whole lock-up; replaces the one `mark` + `brand` + `tagline` compose. */\n lockup?: React.ReactNode;\n /** The wordmark beside the mark, set in the display family. */\n brand?: string;\n /** The mark beside the brand name. Sized by the caller. */\n mark?: React.ReactNode;\n tagline?: string;\n description?: React.ReactNode;\n copyright?: React.ReactNode;\n nav?: readonly FooterLinkGroup[];\n offices?: readonly FooterOffice[];\n officesHeading?: string;\n legalLinks?: readonly FooterLink[];\n /** The newsletter form; the newsletter column renders only when present. */\n newsletter?: React.ReactNode;\n newsletterHeading?: string;\n newsletterBlurb?: string;\n version?: string;\n commitHref?: string;\n linkComponent?: React.ElementType;\n children?: React.ReactNode;\n}\n\nconst HEADING = FOOTER_HEADING;\n\n/**\n * A 12-col footer grid — brand 3 | sitemap groups 2 each | offices 3 |\n * newsletter 2 — with the legal links and copyright line. On mobile the sitemap\n * columns sit side by side. The kit brings no content of its own: every string,\n * the mark and the lock-up are the caller's, and each column renders only when\n * it has something in it. `children` render right after the `<footer>` tag —\n * the slot for app-side extras like a build-version easter egg.\n *\n * The lock-up is either composed from `mark` + `brand` + `tagline`, or handed\n * over whole as `lockup` when a brand's arrangement is not that one.\n *\n * `linkComponent` lets Next hosts pass `next/link`; everyone else gets `<a>`.\n */\nexport function Footer({\n lockup,\n brand,\n mark,\n tagline,\n description,\n copyright,\n nav = [],\n offices = [],\n officesHeading = copy.FOOTER_OFFICES,\n legalLinks = [],\n newsletter,\n newsletterHeading = copy.FOOTER_NEWSLETTER,\n newsletterBlurb = \"\",\n version,\n commitHref,\n linkComponent,\n children,\n}: FooterProps) {\n const L = linkComponent ?? \"a\";\n const composed = brand !== undefined || mark !== undefined;\n const hasBrandColumn = lockup !== undefined || composed || description !== undefined || legalLinks.length > 0;\n\n return (\n <footer data-slot=\"footer\" className={FOOTER_ROOT}>\n {children}\n <Container>\n <div className={FOOTER_GRID}>\n {hasBrandColumn && (\n <div className={FOOTER_WIDE_COLUMN}>\n {lockup !== undefined ? (\n <div data-slot=\"footer-lockup\" className=\"mb-6\">{lockup}</div>\n ) : (\n composed && <FooterLockup brand={brand} mark={mark} tagline={tagline} />\n )}\n {description !== undefined && (\n <p className={FOOTER_DESCRIPTION}>\n {description}\n </p>\n )}\n {legalLinks.length > 0 && (\n <div className={FOOTER_LEGAL_LINKS}>\n {legalLinks.map((link, i) => (\n <React.Fragment key={link.label}>\n {i > 0 && <span className={FOOTER_LEGAL_SEPARATOR}>|</span>}\n <L href={link.href} className={FOOTER_LEGAL_LINK}>\n {link.label}\n </L>\n </React.Fragment>\n ))}\n </div>\n )}\n </div>\n )}\n\n {nav.map((group) => (\n <nav key={group.heading} aria-label={`Footer ${group.heading} links`} className={FOOTER_NAV_COLUMN}>\n <h4 className={HEADING}>{group.heading}</h4>\n <ul className={FOOTER_NAV_LIST}>\n {group.links.map((link) => (\n <li key={link.href}>\n <L\n href={link.href}\n className={FOOTER_NAV_LINK}\n >\n {link.label}\n </L>\n </li>\n ))}\n </ul>\n </nav>\n ))}\n\n {offices.length > 0 && (\n <div className={FOOTER_WIDE_COLUMN}>\n <h4 className={HEADING}>{officesHeading}</h4>\n <ul className={FOOTER_OFFICES_LIST}>\n {offices.map((office) => (\n <li key={office.name}>\n <strong className={FOOTER_OFFICE_NAME}>\n {office.name}\n </strong>\n {office.address}\n </li>\n ))}\n </ul>\n </div>\n )}\n\n {newsletter && (\n <div className={FOOTER_NEWSLETTER_COLUMN}>\n <h4 className={HEADING}>{newsletterHeading}</h4>\n {/* What the source's <Tier tier=\"alt\"><Text> emits: alt body size\n + the info variant, then the mb-4 the caller adds. */}\n <p className={FOOTER_NEWSLETTER_BLURB}>\n {newsletterBlurb}\n </p>\n {newsletter}\n </div>\n )}\n </div>\n\n {(copyright !== undefined || version !== undefined) && (\n <div className={FOOTER_COPYRIGHT}>\n <p>\n {copyright}\n {version && (\n <>\n {copyright !== undefined && \" \"}\n <a href={commitHref} className={FOOTER_VERSION}>\n {version}\n </a>\n </>\n )}\n </p>\n </div>\n )}\n </Container>\n </footer>\n );\n}\n\nfunction FooterLockup({\n brand,\n mark,\n tagline,\n}: {\n brand: string | undefined;\n mark: React.ReactNode;\n tagline: string | undefined;\n}) {\n return (\n <div data-slot=\"footer-lockup\" className={FOOTER_LOCKUP}>\n {mark}\n {(brand !== undefined || tagline !== undefined) && (\n <div className={FOOTER_LOCKUP_TEXT}>\n {brand !== undefined && (\n <span className={FOOTER_BRAND}>{brand}</span>\n )}\n {tagline !== undefined && (\n <span className={FOOTER_TAGLINE}>\n {tagline}\n </span>\n )}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -26,9 +26,9 @@ import {
|
|
|
26
26
|
NOTIFICATION_BELL_ITEM_TITLE_UNREAD,
|
|
27
27
|
NOTIFICATION_BELL_LIST,
|
|
28
28
|
NOTIFICATION_BELL_TRIGGER,
|
|
29
|
-
NOTIFICATION_DOT
|
|
30
|
-
headerBalanceToneClasses
|
|
29
|
+
NOTIFICATION_DOT
|
|
31
30
|
} from "../../generated/header-controls.js";
|
|
31
|
+
import { toneClasses } from "../../generated/tone.js";
|
|
32
32
|
import { AnimatedNumber } from "../derived/animated-number.js";
|
|
33
33
|
import { IconButton } from "../derived/icon-button.js";
|
|
34
34
|
import { Kbd } from "../atomic/kbd.js";
|
|
@@ -36,22 +36,23 @@ import { Popover, PopoverContent, PopoverTrigger } from "../atomic/popover.js";
|
|
|
36
36
|
import { Settled } from "./settled.js";
|
|
37
37
|
import { Skeleton } from "../atomic/skeleton.js";
|
|
38
38
|
import { UiIcon } from "../../icons/ui-icons.js";
|
|
39
|
+
import * as copy from "../../generated/copy.js";
|
|
39
40
|
const USD = { style: "currency", currency: "USD" };
|
|
40
41
|
function HeaderBalance({
|
|
41
|
-
label =
|
|
42
|
+
label = copy.HEADER_BALANCE,
|
|
42
43
|
value,
|
|
43
44
|
amount,
|
|
44
45
|
format = USD,
|
|
45
46
|
locales,
|
|
46
47
|
loading = false,
|
|
47
48
|
icon,
|
|
48
|
-
tone = "
|
|
49
|
+
tone = "neutral",
|
|
49
50
|
onClick,
|
|
50
51
|
disabled,
|
|
51
52
|
className,
|
|
52
53
|
...props
|
|
53
54
|
}) {
|
|
54
|
-
const face = cn(HEADER_BALANCE_BASE, onClick && HEADER_BALANCE_INTERACTIVE, className);
|
|
55
|
+
const face = cn(HEADER_BALANCE_BASE, toneClasses[tone], onClick && HEADER_BALANCE_INTERACTIVE, className);
|
|
55
56
|
const body = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
56
57
|
icon,
|
|
57
58
|
/* @__PURE__ */ jsx("span", { className: HEADER_BALANCE_LABEL, children: label }),
|
|
@@ -61,7 +62,7 @@ function HeaderBalance({
|
|
|
61
62
|
loading,
|
|
62
63
|
skeleton: /* @__PURE__ */ jsx(Skeleton, { className: HEADER_BALANCE_SKELETON }),
|
|
63
64
|
className: HEADER_BALANCE_SETTLED,
|
|
64
|
-
children: /* @__PURE__ */ jsx("span", { className:
|
|
65
|
+
children: /* @__PURE__ */ jsx("span", { className: HEADER_BALANCE_FIGURE, children: amount === void 0 ? value : /* @__PURE__ */ jsx(AnimatedNumber, { value: amount, format, locales }) })
|
|
65
66
|
}
|
|
66
67
|
),
|
|
67
68
|
onClick && /* @__PURE__ */ jsx(UiIcon, { name: "chevron-down", className: HEADER_BALANCE_CHEVRON })
|
|
@@ -73,7 +74,7 @@ function HeaderBalance({
|
|
|
73
74
|
}
|
|
74
75
|
function NotificationBell({ notifications, onSelect, labels = {}, footer, className }) {
|
|
75
76
|
const unread = notifications.filter((n) => n.unread).length;
|
|
76
|
-
const name = labels.trigger ??
|
|
77
|
+
const name = labels.trigger ?? copy.NOTIFICATION_BELL_TRIGGER;
|
|
77
78
|
const spoken = unread > 0 ? `${name}, ${(labels.unread ?? ((n) => `${n} unread`))(unread)}` : name;
|
|
78
79
|
return /* @__PURE__ */ jsxs(Popover, { children: [
|
|
79
80
|
/* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(IconButton, { "aria-label": spoken, variant: "outline", className: cn(NOTIFICATION_BELL_TRIGGER, className), children: [
|
|
@@ -81,7 +82,7 @@ function NotificationBell({ notifications, onSelect, labels = {}, footer, classN
|
|
|
81
82
|
unread > 0 && /* @__PURE__ */ jsx("span", { "data-slot": "notification-dot", "aria-hidden": true, className: NOTIFICATION_DOT })
|
|
82
83
|
] }) }),
|
|
83
84
|
/* @__PURE__ */ jsxs(PopoverContent, { align: "end", className: NOTIFICATION_BELL_CONTENT, children: [
|
|
84
|
-
notifications.length === 0 ? /* @__PURE__ */ jsx("p", { className: NOTIFICATION_BELL_EMPTY, children: labels.empty ??
|
|
85
|
+
notifications.length === 0 ? /* @__PURE__ */ jsx("p", { className: NOTIFICATION_BELL_EMPTY, children: labels.empty ?? copy.NOTIFICATION_BELL_EMPTY }) : /* @__PURE__ */ jsx("ul", { className: NOTIFICATION_BELL_LIST, children: notifications.map((n) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
|
|
85
86
|
"button",
|
|
86
87
|
{
|
|
87
88
|
type: "button",
|
|
@@ -111,21 +112,21 @@ function NotificationBell({ notifications, onSelect, labels = {}, footer, classN
|
|
|
111
112
|
] });
|
|
112
113
|
}
|
|
113
114
|
const APPLE = /Mac|iPhone|iPad|iPod/;
|
|
114
|
-
function
|
|
115
|
-
const [
|
|
115
|
+
function useAppleKeys() {
|
|
116
|
+
const [apple, setApple] = React.useState(false);
|
|
116
117
|
React.useEffect(() => {
|
|
117
118
|
const nav = navigator;
|
|
118
|
-
|
|
119
|
+
setApple(APPLE.test(nav.userAgentData?.platform ?? nav.platform ?? ""));
|
|
119
120
|
}, []);
|
|
120
|
-
return
|
|
121
|
+
return apple;
|
|
121
122
|
}
|
|
122
123
|
function HeaderSearch({
|
|
123
|
-
placeholder =
|
|
124
|
+
placeholder = copy.HEADER_SEARCH,
|
|
124
125
|
shortcut,
|
|
125
126
|
className,
|
|
126
127
|
...props
|
|
127
128
|
}) {
|
|
128
|
-
const platformShortcut =
|
|
129
|
+
const platformShortcut = useAppleKeys() ? copy.COMMAND_K : copy.CTRL_K;
|
|
129
130
|
return /* @__PURE__ */ jsxs(
|
|
130
131
|
"button",
|
|
131
132
|
{
|
|
@@ -144,6 +145,7 @@ function HeaderSearch({
|
|
|
144
145
|
export {
|
|
145
146
|
HeaderBalance,
|
|
146
147
|
HeaderSearch,
|
|
147
|
-
NotificationBell
|
|
148
|
+
NotificationBell,
|
|
149
|
+
useAppleKeys
|
|
148
150
|
};
|
|
149
151
|
//# sourceMappingURL=header-controls.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/shell/header-controls.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../../lib/cn\";\nimport {\n HEADER_BALANCE_BASE,\n HEADER_BALANCE_CHEVRON,\n HEADER_BALANCE_FIGURE,\n HEADER_BALANCE_INTERACTIVE,\n HEADER_BALANCE_LABEL,\n HEADER_BALANCE_SETTLED,\n HEADER_BALANCE_SKELETON,\n HEADER_SEARCH_BASE,\n HEADER_SEARCH_PLACEHOLDER,\n NOTIFICATION_BELL_CONTENT,\n NOTIFICATION_BELL_EMPTY,\n NOTIFICATION_BELL_FOOTER,\n NOTIFICATION_BELL_ITEM,\n NOTIFICATION_BELL_ITEM_BODY,\n NOTIFICATION_BELL_ITEM_DESCRIPTION,\n NOTIFICATION_BELL_ITEM_DOT,\n NOTIFICATION_BELL_ITEM_DOT_READ,\n NOTIFICATION_BELL_ITEM_DOT_UNREAD,\n NOTIFICATION_BELL_ITEM_TIME,\n NOTIFICATION_BELL_ITEM_TITLE,\n NOTIFICATION_BELL_ITEM_TITLE_UNREAD,\n NOTIFICATION_BELL_LIST,\n NOTIFICATION_BELL_TRIGGER,\n NOTIFICATION_DOT,\n headerBalanceToneClasses,\n type HeaderBalanceTone,\n} from \"../../generated/header-controls\";\nimport { AnimatedNumber } from \"../derived/animated-number\";\nimport { IconButton } from \"../derived/icon-button\";\nimport { Kbd } from \"../atomic/kbd\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"../atomic/popover\";\nimport { Settled } from \"./settled\";\nimport { Skeleton } from \"../atomic/skeleton\";\nimport { UiIcon } from \"../../icons/ui-icons\";\n\nexport interface HeaderBalanceProps extends Omit<React.HTMLAttributes<HTMLElement>, \"children\"> {\n /** Only meaningful with `onClick`. */\n disabled?: boolean;\n label?: React.ReactNode;\n /** The figure, already formatted: \"$0.00\". Ignored when `amount` is given. */\n value?: React.ReactNode;\n /** The figure as a number: it rolls to each new amount, formatted by `format` and `locales`. */\n amount?: number;\n /** Formatting for `amount`. Default: USD currency. */\n format?: Intl.NumberFormatOptions;\n locales?: Intl.LocalesArgument;\n /** True until the balance is known; the figure then reveals in place of the skeleton. */\n loading?: boolean;\n /** A glyph before the label. */\n icon?: React.ReactNode;\n /** `warn` colours the figure as a low-funds signal. */\n tone?: HeaderBalanceTone;\n}\n\nconst USD: Intl.NumberFormatOptions = { style: \"currency\", currency: \"USD\" };\n\n/** \"Balance: $0.00\" in the header; a button only when given `onClick`. */\nexport function HeaderBalance({\n label = \"Balance:\",\n value,\n amount,\n format = USD,\n locales,\n loading = false,\n icon,\n tone = \"default\",\n onClick,\n disabled,\n className,\n ...props\n}: HeaderBalanceProps) {\n const face = cn(HEADER_BALANCE_BASE, onClick && HEADER_BALANCE_INTERACTIVE, className);\n const body = (\n <>\n {icon}\n <span className={HEADER_BALANCE_LABEL}>{label}</span>\n <Settled\n loading={loading}\n skeleton={<Skeleton className={HEADER_BALANCE_SKELETON} />}\n className={HEADER_BALANCE_SETTLED}\n >\n <span className={cn(HEADER_BALANCE_FIGURE, headerBalanceToneClasses[tone])}>\n {amount === undefined ? value : <AnimatedNumber value={amount} format={format} locales={locales} />}\n </span>\n </Settled>\n {onClick && <UiIcon name=\"chevron-down\" className={HEADER_BALANCE_CHEVRON} />}\n </>\n );\n if (!onClick) {\n return (\n <div data-slot=\"header-balance\" className={face} {...props}>\n {body}\n </div>\n );\n }\n return (\n <button type=\"button\" data-slot=\"header-balance\" className={face} onClick={onClick} disabled={disabled} {...props}>\n {body}\n </button>\n );\n}\n\nexport interface HeaderNotification {\n id: string;\n title: React.ReactNode;\n description?: React.ReactNode;\n /** Already formatted: \"2m ago\". */\n time?: React.ReactNode;\n unread?: boolean;\n}\n\nexport interface NotificationBellLabels {\n /** The bell's name. Default `\"Notifications\"`. */\n trigger?: string;\n /** Spoken after the name when something is unread; gets the count. Default `\"{n} unread\"`. */\n unread?: (count: number) => string;\n /** Default `\"No notifications\"`. */\n empty?: string;\n}\n\nexport interface NotificationBellProps {\n notifications: readonly HeaderNotification[];\n onSelect?: (id: string) => void;\n labels?: NotificationBellLabels;\n /** Under the list: \"View all\". */\n footer?: React.ReactNode;\n className?: string;\n}\n\n/** The bell with an unread dot; the list opens in a popover. */\nexport function NotificationBell({ notifications, onSelect, labels = {}, footer, className }: NotificationBellProps) {\n const unread = notifications.filter((n) => n.unread).length;\n const name = labels.trigger ?? \"Notifications\";\n const spoken = unread > 0 ? `${name}, ${(labels.unread ?? ((n) => `${n} unread`))(unread)}` : name;\n return (\n <Popover>\n <PopoverTrigger asChild>\n <IconButton aria-label={spoken} variant=\"outline\" className={cn(NOTIFICATION_BELL_TRIGGER, className)}>\n <UiIcon name=\"bell\" />\n {unread > 0 && (\n <span data-slot=\"notification-dot\" aria-hidden className={NOTIFICATION_DOT} />\n )}\n </IconButton>\n </PopoverTrigger>\n <PopoverContent align=\"end\" className={NOTIFICATION_BELL_CONTENT}>\n {notifications.length === 0 ? (\n <p className={NOTIFICATION_BELL_EMPTY}>{labels.empty ?? \"No notifications\"}</p>\n ) : (\n <ul className={NOTIFICATION_BELL_LIST}>\n {notifications.map((n) => (\n <li key={n.id}>\n <button\n type=\"button\"\n onClick={() => onSelect?.(n.id)}\n className={NOTIFICATION_BELL_ITEM}\n >\n <span\n aria-hidden\n className={cn(\n NOTIFICATION_BELL_ITEM_DOT,\n n.unread ? NOTIFICATION_BELL_ITEM_DOT_UNREAD : NOTIFICATION_BELL_ITEM_DOT_READ,\n )}\n />\n <span className={NOTIFICATION_BELL_ITEM_BODY}>\n <span className={cn(NOTIFICATION_BELL_ITEM_TITLE, n.unread && NOTIFICATION_BELL_ITEM_TITLE_UNREAD)}>{n.title}</span>\n {n.description != null && <span className={NOTIFICATION_BELL_ITEM_DESCRIPTION}>{n.description}</span>}\n </span>\n {n.time != null && <span className={NOTIFICATION_BELL_ITEM_TIME}>{n.time}</span>}\n </button>\n </li>\n ))}\n </ul>\n )}\n {footer != null && <div className={NOTIFICATION_BELL_FOOTER}>{footer}</div>}\n </PopoverContent>\n </Popover>\n );\n}\n\nexport interface HeaderSearchProps extends React.ComponentProps<\"button\"> {\n placeholder?: string;\n /** The shortcut hint at the end. Default: `⌘K` on Apple platforms, `Ctrl K` elsewhere. */\n shortcut?: string;\n}\n\nconst APPLE = /Mac|iPhone|iPad|iPod/;\n\n// Read after mount, so the server render and the first client render agree.\nfunction usePlatformShortcut(): string {\n const [shortcut, setShortcut] = React.useState(\"Ctrl K\");\n React.useEffect(() => {\n const nav = navigator as Navigator & { userAgentData?: { platform?: string } };\n if (APPLE.test(nav.userAgentData?.platform ?? nav.platform ?? \"\")) setShortcut(\"⌘K\");\n }, []);\n return shortcut;\n}\n\n/** A button dressed as the search field; it opens the palette rather than taking input. */\nexport function HeaderSearch({\n placeholder = \"Search or jump to…\",\n shortcut,\n className,\n ...props\n}: HeaderSearchProps) {\n const platformShortcut = usePlatformShortcut();\n return (\n <button\n type=\"button\"\n data-slot=\"header-search\"\n className={cn(HEADER_SEARCH_BASE, className)}\n {...props}\n >\n <UiIcon name=\"search\" />\n <span className={HEADER_SEARCH_PLACEHOLDER}>{placeholder}</span>\n <Kbd>{shortcut ?? platformShortcut}</Kbd>\n </button>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/components/shell/header-controls.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../../lib/cn\";\nimport {\n HEADER_BALANCE_BASE,\n HEADER_BALANCE_CHEVRON,\n HEADER_BALANCE_FIGURE,\n HEADER_BALANCE_INTERACTIVE,\n HEADER_BALANCE_LABEL,\n HEADER_BALANCE_SETTLED,\n HEADER_BALANCE_SKELETON,\n HEADER_SEARCH_BASE,\n HEADER_SEARCH_PLACEHOLDER,\n NOTIFICATION_BELL_CONTENT,\n NOTIFICATION_BELL_EMPTY,\n NOTIFICATION_BELL_FOOTER,\n NOTIFICATION_BELL_ITEM,\n NOTIFICATION_BELL_ITEM_BODY,\n NOTIFICATION_BELL_ITEM_DESCRIPTION,\n NOTIFICATION_BELL_ITEM_DOT,\n NOTIFICATION_BELL_ITEM_DOT_READ,\n NOTIFICATION_BELL_ITEM_DOT_UNREAD,\n NOTIFICATION_BELL_ITEM_TIME,\n NOTIFICATION_BELL_ITEM_TITLE,\n NOTIFICATION_BELL_ITEM_TITLE_UNREAD,\n NOTIFICATION_BELL_LIST,\n NOTIFICATION_BELL_TRIGGER,\n NOTIFICATION_DOT,\n} from \"../../generated/header-controls\";\nimport { toneClasses, type Tone } from \"../../generated/tone\";\nimport { AnimatedNumber } from \"../derived/animated-number\";\nimport { IconButton } from \"../derived/icon-button\";\nimport { Kbd } from \"../atomic/kbd\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"../atomic/popover\";\nimport { Settled } from \"./settled\";\nimport { Skeleton } from \"../atomic/skeleton\";\nimport { UiIcon } from \"../../icons/ui-icons\";\nimport * as copy from \"../../generated/copy\";\n\nexport interface HeaderBalanceProps extends Omit<React.HTMLAttributes<HTMLElement>, \"children\"> {\n /** Only meaningful with `onClick`. */\n disabled?: boolean;\n label?: React.ReactNode;\n /** The figure, already formatted: \"$0.00\". Ignored when `amount` is given. */\n value?: React.ReactNode;\n /** The figure as a number: it rolls to each new amount, formatted by `format` and `locales`. */\n amount?: number;\n /** Formatting for `amount`. Default: USD currency. */\n format?: Intl.NumberFormatOptions;\n locales?: Intl.LocalesArgument;\n /** True until the balance is known; the figure then reveals in place of the skeleton. */\n loading?: boolean;\n /** A glyph before the label. */\n icon?: React.ReactNode;\n /** `warn` colours the figure as a low-funds signal. */\n tone?: Tone;\n}\n\nconst USD: Intl.NumberFormatOptions = { style: \"currency\", currency: \"USD\" };\n\n/** \"Balance: $0.00\" in the header; a button only when given `onClick`. */\nexport function HeaderBalance({\n label = copy.HEADER_BALANCE,\n value,\n amount,\n format = USD,\n locales,\n loading = false,\n icon,\n tone = \"neutral\",\n onClick,\n disabled,\n className,\n ...props\n}: HeaderBalanceProps) {\n const face = cn(HEADER_BALANCE_BASE, toneClasses[tone], onClick && HEADER_BALANCE_INTERACTIVE, className);\n const body = (\n <>\n {icon}\n <span className={HEADER_BALANCE_LABEL}>{label}</span>\n <Settled\n loading={loading}\n skeleton={<Skeleton className={HEADER_BALANCE_SKELETON} />}\n className={HEADER_BALANCE_SETTLED}\n >\n <span className={HEADER_BALANCE_FIGURE}>\n {amount === undefined ? value : <AnimatedNumber value={amount} format={format} locales={locales} />}\n </span>\n </Settled>\n {onClick && <UiIcon name=\"chevron-down\" className={HEADER_BALANCE_CHEVRON} />}\n </>\n );\n if (!onClick) {\n return (\n <div data-slot=\"header-balance\" className={face} {...props}>\n {body}\n </div>\n );\n }\n return (\n <button type=\"button\" data-slot=\"header-balance\" className={face} onClick={onClick} disabled={disabled} {...props}>\n {body}\n </button>\n );\n}\n\nexport interface HeaderNotification {\n id: string;\n title: React.ReactNode;\n description?: React.ReactNode;\n /** Already formatted: \"2m ago\". */\n time?: React.ReactNode;\n unread?: boolean;\n}\n\nexport interface NotificationBellLabels {\n /** The bell's name. Default `\"Notifications\"`. */\n trigger?: string;\n /** Spoken after the name when something is unread; gets the count. Default `\"{n} unread\"`. */\n unread?: (count: number) => string;\n /** Default `\"No notifications\"`. */\n empty?: string;\n}\n\nexport interface NotificationBellProps {\n notifications: readonly HeaderNotification[];\n onSelect?: (id: string) => void;\n labels?: NotificationBellLabels;\n /** Under the list: \"View all\". */\n footer?: React.ReactNode;\n className?: string;\n}\n\n/** The bell with an unread dot; the list opens in a popover. */\nexport function NotificationBell({ notifications, onSelect, labels = {}, footer, className }: NotificationBellProps) {\n const unread = notifications.filter((n) => n.unread).length;\n const name = labels.trigger ?? copy.NOTIFICATION_BELL_TRIGGER;\n const spoken = unread > 0 ? `${name}, ${(labels.unread ?? ((n) => `${n} unread`))(unread)}` : name;\n return (\n <Popover>\n <PopoverTrigger asChild>\n <IconButton aria-label={spoken} variant=\"outline\" className={cn(NOTIFICATION_BELL_TRIGGER, className)}>\n <UiIcon name=\"bell\" />\n {unread > 0 && (\n <span data-slot=\"notification-dot\" aria-hidden className={NOTIFICATION_DOT} />\n )}\n </IconButton>\n </PopoverTrigger>\n <PopoverContent align=\"end\" className={NOTIFICATION_BELL_CONTENT}>\n {notifications.length === 0 ? (\n <p className={NOTIFICATION_BELL_EMPTY}>{labels.empty ?? copy.NOTIFICATION_BELL_EMPTY}</p>\n ) : (\n <ul className={NOTIFICATION_BELL_LIST}>\n {notifications.map((n) => (\n <li key={n.id}>\n <button\n type=\"button\"\n onClick={() => onSelect?.(n.id)}\n className={NOTIFICATION_BELL_ITEM}\n >\n <span\n aria-hidden\n className={cn(\n NOTIFICATION_BELL_ITEM_DOT,\n n.unread ? NOTIFICATION_BELL_ITEM_DOT_UNREAD : NOTIFICATION_BELL_ITEM_DOT_READ,\n )}\n />\n <span className={NOTIFICATION_BELL_ITEM_BODY}>\n <span className={cn(NOTIFICATION_BELL_ITEM_TITLE, n.unread && NOTIFICATION_BELL_ITEM_TITLE_UNREAD)}>{n.title}</span>\n {n.description != null && <span className={NOTIFICATION_BELL_ITEM_DESCRIPTION}>{n.description}</span>}\n </span>\n {n.time != null && <span className={NOTIFICATION_BELL_ITEM_TIME}>{n.time}</span>}\n </button>\n </li>\n ))}\n </ul>\n )}\n {footer != null && <div className={NOTIFICATION_BELL_FOOTER}>{footer}</div>}\n </PopoverContent>\n </Popover>\n );\n}\n\nexport interface HeaderSearchProps extends React.ComponentProps<\"button\"> {\n placeholder?: string;\n /** The shortcut hint at the end. Default: `⌘K` on Apple platforms, `Ctrl K` elsewhere. */\n shortcut?: string;\n}\n\nconst APPLE = /Mac|iPhone|iPad|iPod/;\n\n/** Whether ⌘ is the modifier: read after mount, so the server render and the first client render agree. */\nexport function useAppleKeys(): boolean {\n const [apple, setApple] = React.useState(false);\n React.useEffect(() => {\n const nav = navigator as Navigator & { userAgentData?: { platform?: string } };\n setApple(APPLE.test(nav.userAgentData?.platform ?? nav.platform ?? \"\"));\n }, []);\n return apple;\n}\n\n/** A button dressed as the search field; it opens the palette rather than taking input. */\nexport function HeaderSearch({\n placeholder = copy.HEADER_SEARCH,\n shortcut,\n className,\n ...props\n}: HeaderSearchProps) {\n const platformShortcut = useAppleKeys() ? copy.COMMAND_K : copy.CTRL_K;\n return (\n <button\n type=\"button\"\n data-slot=\"header-search\"\n className={cn(HEADER_SEARCH_BASE, className)}\n {...props}\n >\n <UiIcon name=\"search\" />\n <span className={HEADER_SEARCH_PLACEHOLDER}>{placeholder}</span>\n <Kbd>{shortcut ?? platformShortcut}</Kbd>\n </button>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cn } from "../../lib/cn.js";
|
|
5
|
+
import { toneClasses } from "../../generated/tone.js";
|
|
6
|
+
import { iconShapes } from "../../icons/icon-shapes.js";
|
|
7
|
+
import { Button } from "../atomic/button.js";
|
|
8
|
+
import { Checkbox } from "../atomic/checkbox.js";
|
|
9
|
+
import { Kbd } from "../atomic/kbd.js";
|
|
10
|
+
import { Popover, PopoverContent, PopoverTrigger } from "../atomic/popover.js";
|
|
11
|
+
import { useAppleKeys } from "./header-controls.js";
|
|
12
|
+
import { TriangleAlertIcon } from "./shell-icons.js";
|
|
13
|
+
function parseShortcut(shortcut) {
|
|
14
|
+
const mod = shortcut.startsWith("mod+");
|
|
15
|
+
const key = mod ? shortcut.slice(4) : shortcut;
|
|
16
|
+
return { mod, key, code: /\d/.test(key) ? `Digit${key}` : `Key${key}` };
|
|
17
|
+
}
|
|
18
|
+
const STORAGE_KEY = "ev-hotbar";
|
|
19
|
+
const NO_OVERRIDES = {};
|
|
20
|
+
let snapshot = { raw: null, value: NO_OVERRIDES };
|
|
21
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
22
|
+
function readOverrides() {
|
|
23
|
+
const raw = localStorage.getItem(STORAGE_KEY);
|
|
24
|
+
if (raw === snapshot.raw) return snapshot.value;
|
|
25
|
+
const value = raw === null ? NO_OVERRIDES : JSON.parse(raw);
|
|
26
|
+
if (typeof value !== "object" || value === null || Array.isArray(value) || Object.values(value).some((v) => typeof v !== "boolean"))
|
|
27
|
+
throw new Error(`localStorage["${STORAGE_KEY}"] is not an {actionId: boolean} map: ${raw}`);
|
|
28
|
+
snapshot = { raw, value };
|
|
29
|
+
return snapshot.value;
|
|
30
|
+
}
|
|
31
|
+
function writeOverrides(next) {
|
|
32
|
+
if (Object.keys(next).length === 0) localStorage.removeItem(STORAGE_KEY);
|
|
33
|
+
else localStorage.setItem(STORAGE_KEY, JSON.stringify(next));
|
|
34
|
+
listeners.forEach((l) => l());
|
|
35
|
+
}
|
|
36
|
+
function subscribeOverrides(listener) {
|
|
37
|
+
const onStorage = (e) => {
|
|
38
|
+
if (e.key === STORAGE_KEY || e.key === null) listener();
|
|
39
|
+
};
|
|
40
|
+
listeners.add(listener);
|
|
41
|
+
window.addEventListener("storage", onStorage);
|
|
42
|
+
return () => {
|
|
43
|
+
listeners.delete(listener);
|
|
44
|
+
window.removeEventListener("storage", onStorage);
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const EDITABLE = "input, textarea, select, [contenteditable]:not([contenteditable='false'])";
|
|
48
|
+
function Hotbar({ actions, status, takeover, labels = {}, className, ...props }) {
|
|
49
|
+
const parsed = React.useMemo(() => {
|
|
50
|
+
const ids = /* @__PURE__ */ new Set();
|
|
51
|
+
const codes = /* @__PURE__ */ new Set();
|
|
52
|
+
return actions.map((action) => {
|
|
53
|
+
if (ids.has(action.id)) throw new Error(`Hotbar: two actions have the id "${action.id}"`);
|
|
54
|
+
ids.add(action.id);
|
|
55
|
+
if (action.shortcut === void 0) return { action, shortcut: void 0 };
|
|
56
|
+
const shortcut = parseShortcut(action.shortcut);
|
|
57
|
+
const chord = `${shortcut.mod}:${shortcut.code}`;
|
|
58
|
+
if (codes.has(chord)) throw new Error(`Hotbar: two actions are bound to ${action.shortcut}`);
|
|
59
|
+
codes.add(chord);
|
|
60
|
+
return { action, shortcut };
|
|
61
|
+
});
|
|
62
|
+
}, [actions]);
|
|
63
|
+
const latest = React.useRef(parsed);
|
|
64
|
+
React.useEffect(() => {
|
|
65
|
+
latest.current = parsed;
|
|
66
|
+
});
|
|
67
|
+
React.useEffect(() => {
|
|
68
|
+
const onKeyDown = (e) => {
|
|
69
|
+
if (e.defaultPrevented || e.altKey || e.shiftKey) return;
|
|
70
|
+
const mod = e.metaKey || e.ctrlKey;
|
|
71
|
+
const hit = latest.current.find(({ shortcut }) => shortcut?.code === e.code && shortcut.mod === mod);
|
|
72
|
+
if (hit === void 0) return;
|
|
73
|
+
if (!mod && e.target instanceof Element && e.target.closest(EDITABLE)) return;
|
|
74
|
+
e.preventDefault();
|
|
75
|
+
if (!e.repeat) hit.action.onSelect();
|
|
76
|
+
};
|
|
77
|
+
document.addEventListener("keydown", onKeyDown);
|
|
78
|
+
return () => document.removeEventListener("keydown", onKeyDown);
|
|
79
|
+
}, []);
|
|
80
|
+
const overrides = React.useSyncExternalStore(subscribeOverrides, readOverrides, () => NO_OVERRIDES);
|
|
81
|
+
const isPinned = (action) => overrides[action.id] ?? action.pinned ?? true;
|
|
82
|
+
const setPinned = (action, pinned) => {
|
|
83
|
+
const next = { ...overrides };
|
|
84
|
+
delete next[action.id];
|
|
85
|
+
if (pinned !== (action.pinned ?? true)) next[action.id] = pinned;
|
|
86
|
+
writeOverrides(next);
|
|
87
|
+
};
|
|
88
|
+
const apple = useAppleKeys();
|
|
89
|
+
const kbd = (s) => s.mod ? apple ? `\u2318${s.key}` : `Ctrl ${s.key}` : s.key;
|
|
90
|
+
const [customizing, setCustomizing] = React.useState(false);
|
|
91
|
+
return /* @__PURE__ */ jsx(
|
|
92
|
+
"section",
|
|
93
|
+
{
|
|
94
|
+
"data-slot": "hotbar",
|
|
95
|
+
"aria-label": labels.region ?? "Hotbar",
|
|
96
|
+
className: cn(
|
|
97
|
+
"@container border-t border-border-subtle bg-background",
|
|
98
|
+
takeover && [toneClasses[takeover.tone], "border-(color:--tone-edge) bg-[image:linear-gradient(var(--tone-wash),var(--tone-wash))]"],
|
|
99
|
+
className
|
|
100
|
+
),
|
|
101
|
+
...props,
|
|
102
|
+
children: /* @__PURE__ */ jsxs("div", { className: "flex h-(--control-h-touch) items-center gap-2 overflow-x-auto px-3 @5xl:h-(--control-h) @5xl:gap-1.5 @5xl:overflow-visible", children: [
|
|
103
|
+
takeover ? /* @__PURE__ */ jsxs("div", { "data-slot": "hotbar-takeover", role: "alert", className: "flex min-w-0 items-center gap-2 text-body-s font-medium text-(color:--tone-ink)", children: [
|
|
104
|
+
/* @__PURE__ */ jsx(TriangleAlertIcon, { className: "size-4 shrink-0" }),
|
|
105
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: takeover.message }),
|
|
106
|
+
takeover.action
|
|
107
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
108
|
+
parsed.filter(({ action }) => isPinned(action)).map(({ action, shortcut }) => /* @__PURE__ */ jsxs(
|
|
109
|
+
"button",
|
|
110
|
+
{
|
|
111
|
+
type: "button",
|
|
112
|
+
"data-slot": "hotbar-action",
|
|
113
|
+
"aria-pressed": action.pressed,
|
|
114
|
+
onClick: action.onSelect,
|
|
115
|
+
className: cn(ITEM, action.pressed && PRESSED),
|
|
116
|
+
children: [
|
|
117
|
+
/* @__PURE__ */ jsx(action.icon, { className: "size-4 shrink-0" }),
|
|
118
|
+
action.label,
|
|
119
|
+
shortcut && /* @__PURE__ */ jsx(Kbd, { className: "hidden @5xl:inline-flex", children: kbd(shortcut) })
|
|
120
|
+
]
|
|
121
|
+
},
|
|
122
|
+
action.id
|
|
123
|
+
)),
|
|
124
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "hidden h-4 w-px shrink-0 bg-border @5xl:block" }),
|
|
125
|
+
/* @__PURE__ */ jsxs(Popover, { open: customizing, onOpenChange: setCustomizing, children: [
|
|
126
|
+
/* @__PURE__ */ jsxs(PopoverTrigger, { type: "button", "data-slot": "hotbar-customize", className: cn(ITEM, "@5xl:w-(--control-h-2xs) @5xl:justify-center @5xl:px-0"), children: [
|
|
127
|
+
/* @__PURE__ */ jsx("svg", { className: "size-4 shrink-0", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: iconShapes("sliders-horizontal") }),
|
|
128
|
+
/* @__PURE__ */ jsx("span", { className: "@5xl:sr-only", children: labels.customize ?? "Customize" })
|
|
129
|
+
] }),
|
|
130
|
+
/* @__PURE__ */ jsxs(PopoverContent, { side: "top", align: "start", className: "w-80 p-0", children: [
|
|
131
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-0.5 px-3 pt-3 pb-2", children: [
|
|
132
|
+
/* @__PURE__ */ jsx("p", { className: "text-body-l font-medium", children: labels.title ?? "Customize hotbar" }),
|
|
133
|
+
/* @__PURE__ */ jsx("p", { className: "text-caption text-ink-soft", children: labels.description ?? "Pinned actions show in the bar and in the mobile chip row." })
|
|
134
|
+
] }),
|
|
135
|
+
/* @__PURE__ */ jsx("ul", { className: "flex flex-col px-1 pb-1", children: parsed.map(({ action, shortcut }) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("label", { className: "flex h-(--control-h-sm) cursor-pointer items-center gap-2.5 rounded-control px-2 text-body text-ink-mid hover:bg-hover", children: [
|
|
136
|
+
/* @__PURE__ */ jsx(Checkbox, { checked: isPinned(action), onCheckedChange: (pinned) => setPinned(action, pinned) }),
|
|
137
|
+
/* @__PURE__ */ jsx(action.icon, { className: "size-4 shrink-0" }),
|
|
138
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: action.label }),
|
|
139
|
+
shortcut && /* @__PURE__ */ jsx(Kbd, { children: kbd(shortcut) })
|
|
140
|
+
] }) }, action.id)) }),
|
|
141
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-t border-border px-3 py-2", children: [
|
|
142
|
+
/* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: () => writeOverrides(NO_OVERRIDES), children: labels.reset ?? "Reset to default" }),
|
|
143
|
+
/* @__PURE__ */ jsx(Button, { size: "sm", onClick: () => setCustomizing(false), children: labels.done ?? "Done" })
|
|
144
|
+
] })
|
|
145
|
+
] })
|
|
146
|
+
] })
|
|
147
|
+
] }),
|
|
148
|
+
status != null && /* @__PURE__ */ jsx("div", { "data-slot": "hotbar-status", className: "ml-auto hidden shrink-0 items-center @5xl:flex", children: status })
|
|
149
|
+
] })
|
|
150
|
+
}
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
const ITEM = "inline-flex h-(--control-h-xs) shrink-0 items-center gap-1.5 whitespace-nowrap rounded-chip border border-border bg-secondary pr-3 pl-2 text-body-s text-ink-mid transition-colors outline-none hover:text-ink focus-visible:ring-2 focus-visible:ring-ring @5xl:h-(--control-h-2xs) @5xl:rounded-control @5xl:border-transparent @5xl:bg-transparent @5xl:px-2 @5xl:hover:bg-hover";
|
|
154
|
+
const PRESSED = `${toneClasses.primary} border-(color:--tone-edge) bg-(color:--tone-fill) text-(color:--tone-ink) hover:text-(color:--tone-ink) @5xl:border-(color:--tone-edge) @5xl:bg-(color:--tone-fill)`;
|
|
155
|
+
export {
|
|
156
|
+
Hotbar
|
|
157
|
+
};
|
|
158
|
+
//# sourceMappingURL=hotbar.js.map
|