livemark 0.25.0 → 0.26.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 +3 -2
- package/build/actions/config/define.d.ts +6 -0
- package/build/actions/config/define.unit.js +8 -1
- package/build/actions/escape/copy.d.ts +11 -0
- package/build/actions/escape/copy.js +29 -0
- package/build/actions/escape/copy.unit.d.ts +1 -0
- package/build/actions/escape/copy.unit.js +37 -0
- package/build/actions/escape/list.d.ts +12 -0
- package/build/actions/escape/list.js +27 -0
- package/build/actions/escape/list.unit.d.ts +1 -0
- package/build/actions/escape/list.unit.js +36 -0
- package/build/actions/target/build.d.ts +9 -26
- package/build/actions/target/build.js +50 -71
- package/build/actions/target/build.unit.d.ts +1 -0
- package/build/actions/target/build.unit.js +76 -0
- package/build/actions/target/clear.d.ts +4 -0
- package/build/actions/target/clear.js +14 -0
- package/build/actions/target/clear.unit.d.ts +1 -0
- package/build/actions/target/clear.unit.js +23 -0
- package/build/commands/build.js +1 -2
- package/build/commands/clear.d.ts +5 -0
- package/build/commands/clear.js +15 -0
- package/build/commands/escape.d.ts +4 -3
- package/build/commands/escape.js +45 -48
- package/build/commands/preview.js +1 -2
- package/build/commands/start.js +1 -2
- package/build/components/Abbr.d.ts +1 -1
- package/build/components/Abbr.js +2 -4
- package/build/components/AnsiCode.d.ts +1 -1
- package/build/components/AnsiCode.js +2 -2
- package/build/components/Article.d.ts +2 -1
- package/build/components/Article.js +7 -14
- package/build/components/BackToTop.d.ts +1 -1
- package/build/components/Banner.d.ts +2 -2
- package/build/components/Banner.js +7 -3
- package/build/components/BlogIndex.d.ts +1 -1
- package/build/components/BlogIndex.js +2 -11
- package/build/components/Breadcrumbs.d.ts +1 -1
- package/build/components/Breadcrumbs.js +2 -3
- package/build/components/Callout.d.ts +1 -1
- package/build/components/Callout.js +5 -9
- package/build/components/Card.d.ts +1 -1
- package/build/components/Card.js +2 -3
- package/build/components/Cards.d.ts +1 -1
- package/build/components/ChangelogIndex.d.ts +1 -1
- package/build/components/ChangelogIndex.js +2 -11
- package/build/components/ChoiceList.d.ts +15 -0
- package/build/components/ChoiceList.js +13 -0
- package/build/components/CodeBlock.d.ts +1 -1
- package/build/components/CodeBlock.js +3 -16
- package/build/components/CodeTabs.d.ts +1 -1
- package/build/components/CodeTabs.js +2 -3
- package/build/components/Column.d.ts +1 -1
- package/build/components/Columns.d.ts +1 -1
- package/build/components/Content.d.ts +1 -1
- package/build/components/ContentTab.d.ts +1 -1
- package/build/components/ContentTabs.d.ts +1 -1
- package/build/components/ContentTabs.js +2 -3
- package/build/components/DefaultCatchBoundary.d.ts +1 -1
- package/build/components/DefaultCatchBoundary.js +5 -7
- package/build/components/DefinitionList.d.ts +3 -3
- package/build/components/Details.d.ts +1 -1
- package/build/components/Details.js +2 -5
- package/build/components/FileTree.d.ts +1 -1
- package/build/components/FileTree.js +3 -5
- package/build/components/Footer.d.ts +1 -1
- package/build/components/Footer.js +2 -5
- package/build/components/FootnoteRef.d.ts +1 -1
- package/build/components/FootnoteRef.js +2 -4
- package/build/components/Header.d.ts +6 -0
- package/build/components/Header.js +26 -0
- package/build/components/HeaderMenu.d.ts +2 -0
- package/build/components/HeaderMenu.js +27 -0
- package/build/components/HeaderNav.d.ts +6 -0
- package/build/components/HeaderNav.js +74 -0
- package/build/components/HeaderSearch.d.ts +4 -0
- package/build/components/HeaderSearch.js +33 -0
- package/build/components/Heading.d.ts +6 -6
- package/build/components/InlineBadge.d.ts +2 -2
- package/build/components/InlineBadge.js +1 -1
- package/build/components/InlineIcon.d.ts +1 -1
- package/build/components/InlineToc.d.ts +1 -1
- package/build/components/InlineToc.js +2 -4
- package/build/components/Layout.d.ts +2 -1
- package/build/components/Layout.js +5 -58
- package/build/components/LinkButton.d.ts +2 -2
- package/build/components/LinkButton.js +1 -1
- package/build/components/MenuSheet.d.ts +22 -0
- package/build/components/MenuSheet.js +27 -0
- package/build/components/Mermaid.d.ts +1 -1
- package/build/components/MermaidRenderer.d.ts +1 -1
- package/build/components/MermaidRenderer.js +20 -8
- package/build/components/NotFound.d.ts +1 -1
- package/build/components/NotFound.js +3 -6
- package/build/components/PackageTabs.d.ts +1 -1
- package/build/components/PackageTabs.js +3 -7
- package/build/components/PageToolbar.d.ts +1 -1
- package/build/components/PageToolbar.js +2 -10
- package/build/components/PrevNext.d.ts +1 -1
- package/build/components/PrevNext.js +2 -10
- package/build/components/Root.d.ts +4 -0
- package/build/components/Root.js +8 -0
- package/build/components/Search.d.ts +5 -2
- package/build/components/Search.js +11 -9
- package/build/components/SearchDialog.d.ts +1 -1
- package/build/components/SearchDialog.js +10 -133
- package/build/components/Section.d.ts +1 -1
- package/build/components/SectionHeader.d.ts +1 -1
- package/build/components/SheetPanel.d.ts +12 -0
- package/build/components/SheetPanel.js +19 -0
- package/build/components/Sidebar.d.ts +1 -1
- package/build/components/Sidebar.js +12 -14
- package/build/components/SidebarArticles.d.ts +4 -1
- package/build/components/SidebarArticles.js +5 -9
- package/build/components/SidebarLinks.d.ts +4 -1
- package/build/components/SidebarLinks.js +6 -9
- package/build/components/SidebarPosts.d.ts +4 -1
- package/build/components/SidebarPosts.js +7 -23
- package/build/components/SidebarReleases.d.ts +4 -1
- package/build/components/SidebarReleases.js +5 -15
- package/build/components/SidebarSectionPicker.d.ts +8 -0
- package/build/components/SidebarSectionPicker.js +31 -0
- package/build/components/SidebarSections.d.ts +1 -1
- package/build/components/SidebarSections.js +5 -9
- package/build/components/SiteLink.d.ts +1 -1
- package/build/components/SiteTitle.d.ts +1 -1
- package/build/components/SiteTitle.js +2 -6
- package/build/components/SortableTable.d.ts +1 -1
- package/build/components/SoundCloud.d.ts +1 -1
- package/build/components/SoundCloud.js +2 -2
- package/build/components/TagIndex.d.ts +1 -1
- package/build/components/TagIndex.js +2 -11
- package/build/components/Theme.d.ts +2 -1
- package/build/components/Theme.js +35 -15
- package/build/components/Toc.d.ts +5 -4
- package/build/components/Toc.js +35 -35
- package/build/components/YouTube.d.ts +1 -1
- package/build/components/YouTube.js +2 -2
- package/build/components/ZoomImage.d.ts +1 -1
- package/build/content-collections.js +5 -1
- package/build/elements/accordion.d.ts +4 -4
- package/build/elements/accordion.js +2 -3
- package/build/elements/alert-dialog.d.ts +12 -12
- package/build/elements/alert-dialog.js +2 -4
- package/build/elements/alert.d.ts +4 -4
- package/build/elements/alert.js +2 -2
- package/build/elements/aspect-ratio.d.ts +1 -1
- package/build/elements/avatar.d.ts +6 -6
- package/build/elements/badge.d.ts +1 -1
- package/build/elements/badge.js +3 -1
- package/build/elements/breadcrumb.d.ts +6 -6
- package/build/elements/breadcrumb.js +2 -4
- package/build/elements/button-group.d.ts +2 -2
- package/build/elements/button-group.js +5 -5
- package/build/elements/button.d.ts +2 -2
- package/build/elements/button.js +9 -7
- package/build/elements/calendar.d.ts +2 -2
- package/build/elements/calendar.js +2 -2
- package/build/elements/card.d.ts +7 -7
- package/build/elements/card.js +4 -4
- package/build/elements/carousel.d.ts +5 -5
- package/build/elements/carousel.js +3 -7
- package/build/elements/chart.d.ts +6 -6
- package/build/elements/chart.js +5 -11
- package/build/elements/checkbox.d.ts +1 -1
- package/build/elements/collapsible.d.ts +3 -3
- package/build/elements/command.d.ts +9 -9
- package/build/elements/command.js +5 -11
- package/build/elements/context-menu.d.ts +15 -15
- package/build/elements/context-menu.js +6 -9
- package/build/elements/dialog.d.ts +10 -10
- package/build/elements/dialog.js +2 -6
- package/build/elements/drawer.d.ts +14 -12
- package/build/elements/drawer.js +14 -15
- package/build/elements/dropdown-menu.d.ts +15 -15
- package/build/elements/dropdown-menu.js +7 -10
- package/build/elements/empty.d.ts +6 -6
- package/build/elements/empty.js +2 -2
- package/build/elements/field.d.ts +10 -10
- package/build/elements/field.js +3 -4
- package/build/elements/hover-card.d.ts +3 -3
- package/build/elements/hover-card.js +2 -2
- package/build/elements/input-group.d.ts +7 -7
- package/build/elements/input-group.js +5 -5
- package/build/elements/input-otp.d.ts +4 -4
- package/build/elements/input.d.ts +1 -1
- package/build/elements/input.js +2 -2
- package/build/elements/item.d.ts +9 -9
- package/build/elements/item.js +2 -2
- package/build/elements/kbd.d.ts +2 -2
- package/build/elements/label.d.ts +1 -1
- package/build/elements/menubar.d.ts +16 -16
- package/build/elements/menubar.js +9 -11
- package/build/elements/native-select.d.ts +3 -3
- package/build/elements/native-select.js +2 -4
- package/build/elements/navigation-menu.d.ts +8 -8
- package/build/elements/navigation-menu.js +3 -5
- package/build/elements/pagination.d.ts +7 -7
- package/build/elements/pagination.js +4 -10
- package/build/elements/popover.d.ts +6 -6
- package/build/elements/popover.js +2 -2
- package/build/elements/progress.d.ts +5 -5
- package/build/elements/progress.js +2 -3
- package/build/elements/radio-group.d.ts +2 -2
- package/build/elements/resizable.d.ts +3 -3
- package/build/elements/scroll-area.d.ts +2 -2
- package/build/elements/scroll-area.js +2 -4
- package/build/elements/select.d.ts +9 -9
- package/build/elements/select.js +4 -9
- package/build/elements/separator.d.ts +1 -1
- package/build/elements/sheet.d.ts +8 -8
- package/build/elements/sheet.js +2 -6
- package/build/elements/sidebar.d.ts +19 -19
- package/build/elements/sidebar.js +15 -37
- package/build/elements/skeleton.d.ts +1 -1
- package/build/elements/slider.d.ts +2 -1
- package/build/elements/slider.js +2 -3
- package/build/elements/sonner.d.ts +1 -1
- package/build/elements/spinner.d.ts +1 -1
- package/build/elements/switch.d.ts +1 -1
- package/build/elements/table.d.ts +8 -8
- package/build/elements/tabs.d.ts +4 -4
- package/build/elements/tabs.js +3 -3
- package/build/elements/textarea.d.ts +1 -1
- package/build/elements/textarea.js +2 -2
- package/build/elements/toggle-group.d.ts +3 -2
- package/build/elements/toggle-group.js +3 -3
- package/build/elements/toggle.d.ts +1 -1
- package/build/elements/toggle.js +2 -2
- package/build/elements/tooltip.d.ts +4 -4
- package/build/elements/tooltip.js +2 -3
- package/build/head.d.ts +4 -0
- package/build/head.js +6 -0
- package/build/helpers/article-search.d.ts +19 -0
- package/build/helpers/article-search.js +134 -0
- package/build/helpers/dependency-dir.d.ts +5 -0
- package/build/helpers/dependency-dir.js +20 -0
- package/build/helpers/dependency-dir.unit.d.ts +1 -0
- package/build/helpers/dependency-dir.unit.js +25 -0
- package/build/helpers/dynamic-icon-resolver.d.ts +1 -1
- package/build/helpers/dynamic-icon.d.ts +1 -1
- package/build/helpers/head.d.ts +53 -0
- package/build/helpers/head.js +69 -0
- package/build/helpers/head.unit.d.ts +1 -0
- package/build/helpers/head.unit.js +108 -0
- package/build/helpers/nav-items.d.ts +11 -0
- package/build/helpers/nav-items.js +65 -0
- package/build/helpers/render-inline-markdown.d.ts +1 -0
- package/build/helpers/render-inline-markdown.js +34 -0
- package/build/helpers/render-inline-markdown.unit.d.ts +1 -0
- package/build/helpers/render-inline-markdown.unit.js +20 -0
- package/build/helpers/search-highlight.d.ts +8 -0
- package/build/helpers/search-highlight.js +59 -0
- package/build/helpers/search-highlight.unit.d.ts +1 -0
- package/build/helpers/search-highlight.unit.js +36 -0
- package/build/helpers/target-export.d.ts +9 -0
- package/build/helpers/target-export.js +24 -0
- package/build/helpers/target-export.unit.d.ts +1 -0
- package/build/helpers/target-export.unit.js +25 -0
- package/build/helpers/theme.d.ts +6 -2
- package/build/helpers/theme.js +40 -7
- package/build/helpers/theme.unit.d.ts +1 -0
- package/build/helpers/theme.unit.js +30 -0
- package/build/hooks/hydrated.d.ts +2 -0
- package/build/hooks/hydrated.js +15 -0
- package/build/index.d.ts +1 -0
- package/build/index.js +1 -1
- package/build/models/config.d.ts +32 -6
- package/build/models/config.js +10 -1
- package/build/models/section.d.ts +4 -2
- package/build/models/section.js +1 -1
- package/build/plugins/vite-livemark.d.ts +4 -5
- package/build/plugins/vite-livemark.js +56 -90
- package/build/program.js +4 -2
- package/build/routes/$.d.ts +4 -1
- package/build/routes/$.js +20 -9
- package/build/routes/__root.js +29 -63
- package/build/settings.js +2 -1
- package/package.json +68 -66
- package/{.agents/skills → skills}/livemark-authoring/SKILL.md +2 -2
- package/{.agents/skills → skills}/livemark-configuration/SKILL.md +12 -5
- package/skills/livemark-customization/SKILL.md +145 -0
- package/source/actions/config/define.unit.ts +9 -0
- package/source/actions/escape/copy.ts +35 -0
- package/source/actions/escape/copy.unit.ts +51 -0
- package/source/actions/escape/list.ts +30 -0
- package/source/actions/escape/list.unit.ts +42 -0
- package/source/actions/target/build.ts +49 -69
- package/source/actions/target/build.unit.ts +95 -0
- package/source/actions/target/clear.ts +14 -0
- package/source/actions/target/clear.unit.ts +25 -0
- package/source/commands/build.ts +0 -1
- package/source/commands/clear.ts +15 -0
- package/source/commands/escape.ts +42 -59
- package/source/commands/preview.ts +0 -1
- package/source/commands/start.ts +0 -1
- package/source/components/AnsiCode.tsx +1 -1
- package/source/components/Article.tsx +8 -4
- package/source/components/Banner.tsx +12 -2
- package/source/components/BlogIndex.tsx +1 -1
- package/source/components/Callout.tsx +4 -6
- package/source/components/ChangelogIndex.tsx +1 -1
- package/source/components/ChoiceList.tsx +69 -0
- package/source/components/CodeBlock.tsx +6 -6
- package/source/components/CodeTabs.tsx +1 -1
- package/source/components/ContentTabs.tsx +1 -1
- package/source/components/DefaultCatchBoundary.tsx +4 -6
- package/source/components/Details.tsx +1 -1
- package/source/components/FileTree.tsx +1 -1
- package/source/components/Header.tsx +52 -0
- package/source/components/HeaderMenu.tsx +41 -0
- package/source/components/HeaderNav.tsx +145 -0
- package/source/components/HeaderSearch.tsx +77 -0
- package/source/components/InlineBadge.tsx +1 -1
- package/source/components/InlineToc.tsx +1 -1
- package/source/components/Layout.tsx +16 -160
- package/source/components/LinkButton.tsx +9 -1
- package/source/components/MenuSheet.tsx +92 -0
- package/source/components/MermaidRenderer.tsx +19 -8
- package/source/components/NotFound.tsx +3 -5
- package/source/components/PackageTabs.tsx +2 -2
- package/source/components/PageToolbar.tsx +11 -3
- package/source/components/PrevNext.tsx +2 -2
- package/source/components/Root.tsx +13 -0
- package/source/components/Search.tsx +28 -8
- package/source/components/SearchDialog.tsx +15 -189
- package/source/components/SheetPanel.tsx +69 -0
- package/source/components/Sidebar.tsx +27 -14
- package/source/components/SidebarArticles.tsx +4 -3
- package/source/components/SidebarLinks.tsx +3 -2
- package/source/components/SidebarPosts.tsx +3 -2
- package/source/components/SidebarReleases.tsx +3 -2
- package/source/components/SidebarSectionPicker.tsx +70 -0
- package/source/components/SiteTitle.tsx +5 -5
- package/source/components/SoundCloud.tsx +1 -1
- package/source/components/TagIndex.tsx +1 -1
- package/source/components/Theme.tsx +55 -29
- package/source/components/Toc.tsx +76 -55
- package/source/components/YouTube.tsx +1 -1
- package/source/content-collections.ts +4 -0
- package/source/elements/alert-dialog.tsx +1 -1
- package/source/elements/alert.tsx +1 -1
- package/source/elements/badge.tsx +4 -0
- package/source/elements/button-group.tsx +4 -4
- package/source/elements/button.tsx +10 -6
- package/source/elements/calendar.tsx +1 -1
- package/source/elements/card.tsx +3 -3
- package/source/elements/command.tsx +7 -4
- package/source/elements/context-menu.tsx +5 -5
- package/source/elements/dialog.tsx +1 -1
- package/source/elements/drawer.tsx +59 -43
- package/source/elements/dropdown-menu.tsx +6 -6
- package/source/elements/empty.tsx +1 -1
- package/source/elements/field.tsx +1 -1
- package/source/elements/hover-card.tsx +1 -1
- package/source/elements/input-group.tsx +4 -4
- package/source/elements/input.tsx +1 -1
- package/source/elements/item.tsx +1 -1
- package/source/elements/menubar.tsx +8 -8
- package/source/elements/native-select.tsx +1 -1
- package/source/elements/popover.tsx +1 -1
- package/source/elements/select.tsx +3 -3
- package/source/elements/sidebar.tsx +8 -21
- package/source/elements/tabs.tsx +2 -2
- package/source/elements/textarea.tsx +1 -1
- package/source/elements/toggle-group.tsx +2 -2
- package/source/elements/toggle.tsx +1 -1
- package/source/env.d.ts +1 -0
- package/source/head.ts +7 -0
- package/source/helpers/article-search.tsx +192 -0
- package/source/helpers/dependency-dir.ts +20 -0
- package/source/helpers/dependency-dir.unit.ts +39 -0
- package/source/helpers/head.ts +94 -0
- package/source/helpers/head.unit.ts +116 -0
- package/source/helpers/nav-items.tsx +102 -0
- package/source/helpers/render-inline-markdown.ts +44 -0
- package/source/helpers/render-inline-markdown.unit.ts +30 -0
- package/source/helpers/search-highlight.ts +73 -0
- package/source/helpers/search-highlight.unit.ts +42 -0
- package/source/helpers/target-export.ts +28 -0
- package/source/helpers/target-export.unit.ts +37 -0
- package/source/helpers/theme.ts +40 -6
- package/source/helpers/theme.unit.ts +32 -0
- package/source/hooks/hydrated.ts +18 -0
- package/source/index.ts +1 -0
- package/source/models/config.ts +13 -0
- package/source/models/section.ts +3 -0
- package/source/plugins/vite-livemark.ts +54 -95
- package/source/program.ts +2 -0
- package/source/routes/$.tsx +27 -9
- package/source/routes/__root.tsx +51 -71
- package/source/settings.ts +1 -0
- package/source/styles/general.css +59 -11
- package/source/styles/markdown.css +13 -7
- package/source/styles/shape.css +14 -0
- package/.agents/skills/change-review/SKILL.md +0 -47
- package/.agents/skills/livemark-customization/SKILL.md +0 -118
|
@@ -5,7 +5,15 @@ import { cn } from "cn"
|
|
|
5
5
|
export function LinkButton(props: {
|
|
6
6
|
href: string
|
|
7
7
|
label: string
|
|
8
|
-
variant?:
|
|
8
|
+
variant?:
|
|
9
|
+
| "default"
|
|
10
|
+
| "outline"
|
|
11
|
+
| "secondary"
|
|
12
|
+
| "ghost"
|
|
13
|
+
| "positive"
|
|
14
|
+
| "warning"
|
|
15
|
+
| "destructive"
|
|
16
|
+
| "link"
|
|
9
17
|
size?: "default" | "sm" | "lg"
|
|
10
18
|
}) {
|
|
11
19
|
return (
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { CheckIcon } from "lucide-react"
|
|
2
|
+
import { cloneElement, useState, type ReactElement, type ReactNode } from "react"
|
|
3
|
+
import { cn } from "cn"
|
|
4
|
+
import {
|
|
5
|
+
DropdownMenu,
|
|
6
|
+
DropdownMenuContent,
|
|
7
|
+
DropdownMenuItem,
|
|
8
|
+
DropdownMenuTrigger,
|
|
9
|
+
} from "../elements/dropdown-menu.tsx"
|
|
10
|
+
import { useIsMobile } from "../hooks/media.ts"
|
|
11
|
+
import { SheetPanel } from "./SheetPanel.tsx"
|
|
12
|
+
|
|
13
|
+
/** One row of a `MenuSheet` */
|
|
14
|
+
export type MenuSheetItem = {
|
|
15
|
+
id: string
|
|
16
|
+
label: ReactNode
|
|
17
|
+
icon?: ReactNode
|
|
18
|
+
active?: boolean
|
|
19
|
+
onSelect?: () => void
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** A menu that is an anchored dropdown from a laptop up and a bottom sheet on a
|
|
23
|
+
* phone */
|
|
24
|
+
export function MenuSheet(props: {
|
|
25
|
+
trigger: ReactElement<{ onClick?: () => void; children?: ReactNode }>
|
|
26
|
+
children?: ReactNode
|
|
27
|
+
title: ReactNode
|
|
28
|
+
items: MenuSheetItem[]
|
|
29
|
+
align?: "start" | "end"
|
|
30
|
+
onOpenChange?: (open: boolean) => void
|
|
31
|
+
}) {
|
|
32
|
+
const isMobile = useIsMobile()
|
|
33
|
+
const [open, setOpen] = useState(false)
|
|
34
|
+
const changeOpen = (next: boolean) => {
|
|
35
|
+
setOpen(next)
|
|
36
|
+
props.onOpenChange?.(next)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (isMobile) {
|
|
40
|
+
return (
|
|
41
|
+
<>
|
|
42
|
+
{cloneElement(props.trigger, { onClick: () => changeOpen(true) }, props.children)}
|
|
43
|
+
<SheetPanel open={open} onOpenChange={changeOpen} title={props.title}>
|
|
44
|
+
<ul className="flex w-full min-w-0 flex-col gap-1">
|
|
45
|
+
{props.items.map(item => (
|
|
46
|
+
<li key={item.id}>
|
|
47
|
+
<button
|
|
48
|
+
type="button"
|
|
49
|
+
className={cn(ROW, item.active && ROW_ACTIVE)}
|
|
50
|
+
onClick={() => {
|
|
51
|
+
item.onSelect?.()
|
|
52
|
+
changeOpen(false)
|
|
53
|
+
}}
|
|
54
|
+
>
|
|
55
|
+
{item.icon}
|
|
56
|
+
<span className="flex-1 truncate">{item.label}</span>
|
|
57
|
+
{item.active && <CheckIcon className="size-4 shrink-0" />}
|
|
58
|
+
</button>
|
|
59
|
+
</li>
|
|
60
|
+
))}
|
|
61
|
+
</ul>
|
|
62
|
+
</SheetPanel>
|
|
63
|
+
</>
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<DropdownMenu onOpenChange={props.onOpenChange}>
|
|
69
|
+
{/* Without a `render`, Base UI's trigger falls back to a bare <button> — which
|
|
70
|
+
SSR paints on a phone until `useIsMobile` flips, flashing a stray icon. */}
|
|
71
|
+
<DropdownMenuTrigger render={props.trigger}>{props.children}</DropdownMenuTrigger>
|
|
72
|
+
<DropdownMenuContent align={props.align ?? "end"}>
|
|
73
|
+
{props.items.map(item => (
|
|
74
|
+
<DropdownMenuItem
|
|
75
|
+
key={item.id}
|
|
76
|
+
onClick={item.onSelect}
|
|
77
|
+
className="cursor-pointer gap-2"
|
|
78
|
+
>
|
|
79
|
+
{item.icon}
|
|
80
|
+
<span className={item.active ? "font-medium" : undefined}>{item.label}</span>
|
|
81
|
+
</DropdownMenuItem>
|
|
82
|
+
))}
|
|
83
|
+
</DropdownMenuContent>
|
|
84
|
+
</DropdownMenu>
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const ROW =
|
|
89
|
+
"hover:bg-muted active:bg-muted flex h-10 w-full items-center gap-2.5 overflow-hidden rounded-md p-2 text-left text-base outline-hidden [&_svg]:size-[18px] [&_svg]:shrink-0"
|
|
90
|
+
|
|
91
|
+
const ROW_ACTIVE =
|
|
92
|
+
"bg-sidebar-accent text-sidebar-accent-foreground font-semibold [&_svg]:stroke-[2.5]"
|
|
@@ -3,6 +3,10 @@ import mermaid from "mermaid"
|
|
|
3
3
|
|
|
4
4
|
mermaid.initialize({ startOnLoad: false })
|
|
5
5
|
|
|
6
|
+
// Module-wide so runs never overlap, even across effect re-runs: overlapping runs on
|
|
7
|
+
// one node reject with "reading 'firstChild'".
|
|
8
|
+
let renderQueue = Promise.resolve()
|
|
9
|
+
|
|
6
10
|
/** Renders a Mermaid diagram from a chart definition string */
|
|
7
11
|
export default function MermaidRenderer(props: { children: string }) {
|
|
8
12
|
const ref = useRef<HTMLDivElement>(null)
|
|
@@ -12,14 +16,21 @@ export default function MermaidRenderer(props: { children: string }) {
|
|
|
12
16
|
if (!el) return
|
|
13
17
|
|
|
14
18
|
const render = () => {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
renderQueue = renderQueue
|
|
20
|
+
.then(() => {
|
|
21
|
+
if (!el.isConnected) return
|
|
22
|
+
const isDark = document.documentElement.classList.contains("dark")
|
|
23
|
+
mermaid.initialize({
|
|
24
|
+
startOnLoad: false,
|
|
25
|
+
theme: isDark ? "dark" : "default",
|
|
26
|
+
})
|
|
27
|
+
el.textContent = props.children
|
|
28
|
+
el.removeAttribute("data-processed")
|
|
29
|
+
return mermaid.run({ nodes: [el] })
|
|
30
|
+
})
|
|
31
|
+
.catch(error => {
|
|
32
|
+
if (el.isConnected) console.error(error)
|
|
33
|
+
})
|
|
23
34
|
}
|
|
24
35
|
|
|
25
36
|
render()
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Link } from "@tanstack/react-router"
|
|
2
|
+
import { buttonVariants } from "../elements/button.tsx"
|
|
2
3
|
|
|
3
4
|
export function NotFound({ children }: { children?: any }) {
|
|
4
5
|
return (
|
|
@@ -11,14 +12,11 @@ export function NotFound({ children }: { children?: any }) {
|
|
|
11
12
|
<button
|
|
12
13
|
type="button"
|
|
13
14
|
onClick={() => window.history.back()}
|
|
14
|
-
className=
|
|
15
|
+
className={buttonVariants({ size: "lg" })}
|
|
15
16
|
>
|
|
16
17
|
Go Back
|
|
17
18
|
</button>
|
|
18
|
-
<Link
|
|
19
|
-
to="/"
|
|
20
|
-
className="border border-border px-4 py-2 text-sm font-medium hover:bg-card transition-colors"
|
|
21
|
-
>
|
|
19
|
+
<Link to="/" className={buttonVariants({ variant: "outline", size: "lg" })}>
|
|
22
20
|
Home
|
|
23
21
|
</Link>
|
|
24
22
|
</div>
|
|
@@ -31,7 +31,7 @@ export function PackageTabs(props: PackageTabsProps) {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
return (
|
|
34
|
-
<figure className="not-prose my-4 relative rounded-
|
|
34
|
+
<figure className="not-prose my-4 relative rounded-card border shadow-sm overflow-hidden text-sm bg-sidebar dark:bg-sidebar">
|
|
35
35
|
<div className="flex items-center border-b">
|
|
36
36
|
<div className="flex flex-1 items-center">
|
|
37
37
|
{managers.map(pm => (
|
|
@@ -52,7 +52,7 @@ export function PackageTabs(props: PackageTabsProps) {
|
|
|
52
52
|
<button
|
|
53
53
|
type="button"
|
|
54
54
|
onClick={handleCopy}
|
|
55
|
-
className="me-2 p-1 rounded-
|
|
55
|
+
className="me-2 p-1 rounded-control text-muted-foreground hover:text-foreground transition-colors"
|
|
56
56
|
aria-label="Copy code"
|
|
57
57
|
>
|
|
58
58
|
{copied ? <Check className="size-3.5" /> : <Clipboard className="size-3.5" />}
|
|
@@ -37,13 +37,13 @@ export function PageToolbar(props: {
|
|
|
37
37
|
|
|
38
38
|
return (
|
|
39
39
|
<>
|
|
40
|
-
<div
|
|
41
|
-
<p className="text-sm font-medium mb-1">Actions</p>
|
|
40
|
+
<div data-slot="page-toolbar" className="flex flex-col gap-2 text-sm">
|
|
42
41
|
{sourceHref ? (
|
|
43
42
|
<a
|
|
44
43
|
href={sourceHref}
|
|
45
44
|
target="_blank"
|
|
46
45
|
rel="noopener noreferrer"
|
|
46
|
+
data-slot="page-action"
|
|
47
47
|
className={buttonClass}
|
|
48
48
|
>
|
|
49
49
|
<SourceIcon className="size-3.5" />
|
|
@@ -51,6 +51,7 @@ export function PageToolbar(props: {
|
|
|
51
51
|
</a>
|
|
52
52
|
) : (
|
|
53
53
|
<span
|
|
54
|
+
data-slot="page-action"
|
|
54
55
|
className={`${buttonClass} opacity-50 cursor-not-allowed hover:text-muted-foreground`}
|
|
55
56
|
aria-disabled="true"
|
|
56
57
|
>
|
|
@@ -58,13 +59,19 @@ export function PageToolbar(props: {
|
|
|
58
59
|
{sourceLabel}
|
|
59
60
|
</span>
|
|
60
61
|
)}
|
|
61
|
-
<button
|
|
62
|
+
<button
|
|
63
|
+
type="button"
|
|
64
|
+
onClick={handleCopy}
|
|
65
|
+
data-slot="page-action"
|
|
66
|
+
className={buttonClass}
|
|
67
|
+
>
|
|
62
68
|
{copied ? <Check className="size-3.5" /> : <Copy className="size-3.5" />}
|
|
63
69
|
Copy Markdown
|
|
64
70
|
</button>
|
|
65
71
|
<button
|
|
66
72
|
type="button"
|
|
67
73
|
onClick={() => openIn("https://chatgpt.com/", { hints: "search" })}
|
|
74
|
+
data-slot="page-action"
|
|
68
75
|
className={buttonClass}
|
|
69
76
|
>
|
|
70
77
|
<ExternalLink className="size-3.5" />
|
|
@@ -73,6 +80,7 @@ export function PageToolbar(props: {
|
|
|
73
80
|
<button
|
|
74
81
|
type="button"
|
|
75
82
|
onClick={() => openIn("https://claude.ai/new")}
|
|
83
|
+
data-slot="page-action"
|
|
76
84
|
className={buttonClass}
|
|
77
85
|
>
|
|
78
86
|
<ExternalLink className="size-3.5" />
|
|
@@ -29,7 +29,7 @@ export function PrevNext(props: { path: string }) {
|
|
|
29
29
|
<Link
|
|
30
30
|
to="/$"
|
|
31
31
|
params={{ _splat: prev.path.replace(/^\/|\/$/g, "") }}
|
|
32
|
-
className="flex-1 rounded-
|
|
32
|
+
className="flex-1 rounded-card border shadow-sm p-4 hover:bg-muted/50 transition-colors"
|
|
33
33
|
>
|
|
34
34
|
<span className="flex items-center gap-1 text-sm text-muted-foreground">
|
|
35
35
|
<ChevronLeft className="size-4" />
|
|
@@ -42,7 +42,7 @@ export function PrevNext(props: { path: string }) {
|
|
|
42
42
|
<Link
|
|
43
43
|
to="/$"
|
|
44
44
|
params={{ _splat: next.path.replace(/^\/|\/$/g, "") }}
|
|
45
|
-
className="flex-1 rounded-
|
|
45
|
+
className="flex-1 rounded-card border shadow-sm p-4 hover:bg-muted/50 transition-colors text-right"
|
|
46
46
|
>
|
|
47
47
|
<span className="flex items-center justify-end gap-1 text-sm text-muted-foreground">
|
|
48
48
|
Next
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { useHotkey } from "@tanstack/react-hotkeys"
|
|
2
|
+
|
|
3
|
+
/** Headless wrapper around every route; pages add their own chrome with `Layout` */
|
|
4
|
+
export function Root(props: { children?: React.ReactNode }) {
|
|
5
|
+
useHotkey("J", () =>
|
|
6
|
+
window.scrollBy({ top: window.innerHeight * 0.8, behavior: "smooth" }),
|
|
7
|
+
)
|
|
8
|
+
useHotkey("K", () =>
|
|
9
|
+
window.scrollBy({ top: -window.innerHeight * 0.8, behavior: "smooth" }),
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
return props.children
|
|
13
|
+
}
|
|
@@ -1,27 +1,47 @@
|
|
|
1
1
|
import { useState } from "react"
|
|
2
2
|
import { useHotkey } from "@tanstack/react-hotkeys"
|
|
3
3
|
import { Search as SearchIcon } from "lucide-react"
|
|
4
|
+
import { cn } from "cn"
|
|
5
|
+
import { isMobileNow } from "../hooks/media.ts"
|
|
6
|
+
import { HeaderSearch } from "./HeaderSearch.tsx"
|
|
4
7
|
import { SearchDialog } from "./SearchDialog.tsx"
|
|
5
8
|
|
|
6
|
-
/** Search button with "
|
|
7
|
-
|
|
9
|
+
/** Search button with "S" keyboard shortcut: a command palette from `lg`, an in-header field below.
|
|
10
|
+
* `compact` keeps it an icon from `lg` too, when header sections need the room */
|
|
11
|
+
export function Search(props: { compact?: boolean }) {
|
|
8
12
|
const [open, setOpen] = useState(false)
|
|
13
|
+
const [headerOpen, setHeaderOpen] = useState(false)
|
|
14
|
+
const openSearch = () => (isMobileNow() ? setHeaderOpen(true) : setOpen(true))
|
|
9
15
|
|
|
10
|
-
useHotkey("
|
|
16
|
+
useHotkey("S", openSearch)
|
|
11
17
|
|
|
12
18
|
return (
|
|
13
19
|
<>
|
|
14
20
|
<button
|
|
15
21
|
type="button"
|
|
16
|
-
onClick={
|
|
17
|
-
|
|
22
|
+
onClick={openSearch}
|
|
23
|
+
aria-label="Search"
|
|
24
|
+
aria-keyshortcuts="S"
|
|
25
|
+
title="Search"
|
|
26
|
+
className={cn(
|
|
27
|
+
"flex h-9 items-center gap-2 rounded-control border border-border bg-muted/50 px-2.5 text-sm text-muted-foreground shadow-xs hover:bg-muted transition-colors cursor-pointer",
|
|
28
|
+
!props.compact && "lg:w-56",
|
|
29
|
+
)}
|
|
18
30
|
>
|
|
19
31
|
<SearchIcon className="size-4" />
|
|
20
|
-
<span className="flex-1 text-left">
|
|
21
|
-
|
|
22
|
-
|
|
32
|
+
<span className={cn("hidden flex-1 text-left", !props.compact && "lg:inline")}>
|
|
33
|
+
Search...
|
|
34
|
+
</span>
|
|
35
|
+
<kbd
|
|
36
|
+
className={cn(
|
|
37
|
+
"hidden rounded border border-border bg-background px-1.5 py-0.5 text-[10px] font-mono",
|
|
38
|
+
!props.compact && "lg:inline",
|
|
39
|
+
)}
|
|
40
|
+
>
|
|
41
|
+
S
|
|
23
42
|
</kbd>
|
|
24
43
|
</button>
|
|
44
|
+
{headerOpen && <HeaderSearch onClose={() => setHeaderOpen(false)} />}
|
|
25
45
|
<SearchDialog open={open} onOpenChange={setOpen} />
|
|
26
46
|
</>
|
|
27
47
|
)
|
|
@@ -1,127 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { useNavigate } from "@tanstack/react-router"
|
|
3
|
-
import { create, insertMultiple, search } from "@orama/orama"
|
|
4
|
-
import { sortedArticles } from "../content/article.ts"
|
|
5
|
-
import { resolveArticleIcon } from "../helpers/article-icon.ts"
|
|
1
|
+
import { SearchResultGroups, useArticleSearch } from "../helpers/article-search.tsx"
|
|
6
2
|
import {
|
|
7
3
|
CommandDialog,
|
|
8
4
|
CommandEmpty,
|
|
9
|
-
CommandGroup,
|
|
10
5
|
CommandInput,
|
|
11
|
-
CommandItem,
|
|
12
6
|
CommandList,
|
|
13
7
|
} from "../elements/command.tsx"
|
|
14
8
|
|
|
15
|
-
interface SearchResult {
|
|
16
|
-
title: string
|
|
17
|
-
path: string
|
|
18
|
-
icon?: string
|
|
19
|
-
snippet: string
|
|
20
|
-
group: string
|
|
21
|
-
}
|
|
22
|
-
|
|
23
9
|
/** Full-text search dialog powered by Orama */
|
|
24
10
|
export function SearchDialog(props: {
|
|
25
11
|
open: boolean
|
|
26
12
|
onOpenChange: (open: boolean) => void
|
|
27
13
|
}) {
|
|
28
|
-
const
|
|
29
|
-
const [query, setQuery] = useState("")
|
|
30
|
-
const dbRef = useRef<Awaited<ReturnType<typeof create>> | null>(null)
|
|
31
|
-
const navigate = useNavigate()
|
|
32
|
-
|
|
33
|
-
const ensureIndex = useCallback(async () => {
|
|
34
|
-
if (dbRef.current) return dbRef.current
|
|
35
|
-
const db = await create({
|
|
36
|
-
schema: {
|
|
37
|
-
title: "string",
|
|
38
|
-
description: "string",
|
|
39
|
-
searchText: "string",
|
|
40
|
-
},
|
|
41
|
-
})
|
|
42
|
-
await insertMultiple(
|
|
43
|
-
db,
|
|
44
|
-
sortedArticles.map(a => ({
|
|
45
|
-
title: a.title,
|
|
46
|
-
description: a.description ?? "",
|
|
47
|
-
searchText: a.searchText,
|
|
48
|
-
})),
|
|
49
|
-
)
|
|
50
|
-
dbRef.current = db
|
|
51
|
-
return db
|
|
52
|
-
}, [])
|
|
53
|
-
|
|
54
|
-
const toResult = useCallback(
|
|
55
|
-
(article: (typeof sortedArticles)[number], term: string): SearchResult => ({
|
|
56
|
-
title: article.title,
|
|
57
|
-
path: article.path,
|
|
58
|
-
icon: article.icon,
|
|
59
|
-
snippet: term
|
|
60
|
-
? extractSnippet(article.searchText, term)
|
|
61
|
-
: (article.description ?? ""),
|
|
62
|
-
group: groupFor(article.path),
|
|
63
|
-
}),
|
|
64
|
-
[],
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
const handleSearch = useCallback(
|
|
68
|
-
async (term: string) => {
|
|
69
|
-
setQuery(term)
|
|
70
|
-
if (!term.trim()) {
|
|
71
|
-
setResults([])
|
|
72
|
-
return
|
|
73
|
-
}
|
|
74
|
-
const db = await ensureIndex()
|
|
75
|
-
const hits = await search(db, {
|
|
76
|
-
term,
|
|
77
|
-
properties: ["title", "description", "searchText"],
|
|
78
|
-
tolerance: 1,
|
|
79
|
-
boost: { title: 3, description: 2 },
|
|
80
|
-
})
|
|
81
|
-
setResults(
|
|
82
|
-
hits.hits.map(hit => {
|
|
83
|
-
const article = sortedArticles.find(a => a.title === hit.document.title)
|
|
84
|
-
if (!article)
|
|
85
|
-
return toResult(
|
|
86
|
-
{
|
|
87
|
-
...sortedArticles[0],
|
|
88
|
-
title: hit.document.title,
|
|
89
|
-
path: "",
|
|
90
|
-
searchText: hit.document.description,
|
|
91
|
-
description: hit.document.description,
|
|
92
|
-
icon: "",
|
|
93
|
-
},
|
|
94
|
-
term,
|
|
95
|
-
)
|
|
96
|
-
return toResult(article, term)
|
|
97
|
-
}),
|
|
98
|
-
)
|
|
99
|
-
},
|
|
100
|
-
[ensureIndex, toResult],
|
|
101
|
-
)
|
|
102
|
-
|
|
103
|
-
const handleSelect = useCallback(
|
|
104
|
-
(path: string) => {
|
|
105
|
-
props.onOpenChange(false)
|
|
106
|
-
setQuery("")
|
|
107
|
-
setResults([])
|
|
108
|
-
navigate({
|
|
109
|
-
to: "/$",
|
|
110
|
-
params: { _splat: path.replace(/^\/|\/$/g, "") },
|
|
111
|
-
})
|
|
112
|
-
},
|
|
113
|
-
[navigate, props.onOpenChange],
|
|
114
|
-
)
|
|
115
|
-
|
|
116
|
-
const grouped = useMemo(() => {
|
|
117
|
-
const groups = new Map<string, SearchResult[]>()
|
|
118
|
-
for (const r of results) {
|
|
119
|
-
const list = groups.get(r.group) ?? []
|
|
120
|
-
list.push(r)
|
|
121
|
-
groups.set(r.group, list)
|
|
122
|
-
}
|
|
123
|
-
return groups
|
|
124
|
-
}, [results])
|
|
14
|
+
const search = useArticleSearch()
|
|
125
15
|
|
|
126
16
|
return (
|
|
127
17
|
<CommandDialog
|
|
@@ -134,43 +24,24 @@ export function SearchDialog(props: {
|
|
|
134
24
|
>
|
|
135
25
|
<CommandInput
|
|
136
26
|
placeholder="Search docs..."
|
|
137
|
-
value={query}
|
|
138
|
-
onValueChange={
|
|
139
|
-
onClear={() =>
|
|
27
|
+
value={search.query}
|
|
28
|
+
onValueChange={search.searchArticles}
|
|
29
|
+
onClear={() => search.searchArticles("")}
|
|
140
30
|
/>
|
|
141
31
|
<CommandList className="max-h-80">
|
|
142
32
|
<CommandEmpty>
|
|
143
|
-
{query.trim()
|
|
33
|
+
{search.query.trim()
|
|
144
34
|
? "No results found."
|
|
145
|
-
: "Type to search across all
|
|
35
|
+
: "Type to search across all contents."}
|
|
146
36
|
</CommandEmpty>
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
onSelect={() => handleSelect(result.path)}
|
|
156
|
-
className="py-2.5"
|
|
157
|
-
>
|
|
158
|
-
{Icon && (
|
|
159
|
-
<Icon className="size-5 shrink-0 mt-0.5 self-start opacity-60" />
|
|
160
|
-
)}
|
|
161
|
-
<div className="flex flex-col gap-0.5 min-w-0">
|
|
162
|
-
<span className="text-base font-medium">{result.title}</span>
|
|
163
|
-
{result.snippet && (
|
|
164
|
-
<span className="text-sm text-muted-foreground line-clamp-1">
|
|
165
|
-
<HighlightedText text={result.snippet} term={query} />
|
|
166
|
-
</span>
|
|
167
|
-
)}
|
|
168
|
-
</div>
|
|
169
|
-
</CommandItem>
|
|
170
|
-
)
|
|
171
|
-
})}
|
|
172
|
-
</CommandGroup>
|
|
173
|
-
))}
|
|
37
|
+
<SearchResultGroups
|
|
38
|
+
query={search.query}
|
|
39
|
+
grouped={search.grouped}
|
|
40
|
+
onSelect={path => {
|
|
41
|
+
props.onOpenChange(false)
|
|
42
|
+
search.selectArticle(path)
|
|
43
|
+
}}
|
|
44
|
+
/>
|
|
174
45
|
</CommandList>
|
|
175
46
|
<div className="flex items-center gap-4 border-t mt-2 px-4 py-2.5 text-sm text-muted-foreground">
|
|
176
47
|
<span className="flex items-center gap-1.5">
|
|
@@ -195,48 +66,3 @@ export function SearchDialog(props: {
|
|
|
195
66
|
</CommandDialog>
|
|
196
67
|
)
|
|
197
68
|
}
|
|
198
|
-
|
|
199
|
-
function groupFor(pathname: string) {
|
|
200
|
-
const segments = pathname.replace(/^\/|\/$/g, "").split("/")
|
|
201
|
-
if (segments.length <= 2) return "Pages"
|
|
202
|
-
return segments
|
|
203
|
-
.slice(1, -1)
|
|
204
|
-
.map(s =>
|
|
205
|
-
s
|
|
206
|
-
.split("-")
|
|
207
|
-
.map(w => w.charAt(0).toUpperCase() + w.slice(1))
|
|
208
|
-
.join(" "),
|
|
209
|
-
)
|
|
210
|
-
.join(" › ")
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
function extractSnippet(text: string, term: string) {
|
|
214
|
-
const lower = text.toLowerCase()
|
|
215
|
-
const idx = lower.indexOf(term.toLowerCase())
|
|
216
|
-
if (idx === -1) return text.slice(0, 120)
|
|
217
|
-
const start = Math.max(0, idx - 40)
|
|
218
|
-
const end = Math.min(text.length, idx + term.length + 80)
|
|
219
|
-
const prefix = start > 0 ? "…" : ""
|
|
220
|
-
const suffix = end < text.length ? "…" : ""
|
|
221
|
-
return `${prefix}${text.slice(start, end)}${suffix}`
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
function HighlightedText(props: { text: string; term: string }) {
|
|
225
|
-
if (!props.term.trim()) return <>{props.text}</>
|
|
226
|
-
const escaped = props.term.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
|
227
|
-
const regex = new RegExp(`(${escaped})`, "gi")
|
|
228
|
-
const parts = props.text.split(regex)
|
|
229
|
-
return (
|
|
230
|
-
<>
|
|
231
|
-
{parts.map((part, i) =>
|
|
232
|
-
regex.test(part) ? (
|
|
233
|
-
<span key={i} className="text-foreground font-medium">
|
|
234
|
-
{part}
|
|
235
|
-
</span>
|
|
236
|
-
) : (
|
|
237
|
-
part
|
|
238
|
-
),
|
|
239
|
-
)}
|
|
240
|
-
</>
|
|
241
|
-
)
|
|
242
|
-
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { XIcon } from "lucide-react"
|
|
2
|
+
import { useRef, type ReactNode } from "react"
|
|
3
|
+
import { cn } from "cn"
|
|
4
|
+
import { Button } from "../elements/button.tsx"
|
|
5
|
+
import {
|
|
6
|
+
Drawer,
|
|
7
|
+
DrawerClose,
|
|
8
|
+
DrawerContent,
|
|
9
|
+
DrawerHeader,
|
|
10
|
+
DrawerTitle,
|
|
11
|
+
} from "../elements/drawer.tsx"
|
|
12
|
+
|
|
13
|
+
/** A phone's bottom sheet: title left, close right, a scrolling body and an
|
|
14
|
+
* optional pinned footer. */
|
|
15
|
+
export function SheetPanel(props: {
|
|
16
|
+
open: boolean
|
|
17
|
+
onOpenChange: (open: boolean) => void
|
|
18
|
+
onOpenChangeComplete?: (open: boolean) => void
|
|
19
|
+
title: ReactNode
|
|
20
|
+
footer?: ReactNode
|
|
21
|
+
bodyClassName?: string
|
|
22
|
+
children: ReactNode
|
|
23
|
+
}) {
|
|
24
|
+
const panelRef = useRef<HTMLDivElement>(null)
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<Drawer
|
|
28
|
+
open={props.open}
|
|
29
|
+
onOpenChange={open => props.onOpenChange(open)}
|
|
30
|
+
onOpenChangeComplete={props.onOpenChangeComplete}
|
|
31
|
+
>
|
|
32
|
+
<DrawerContent
|
|
33
|
+
// The body, never its first link: focusing that scrolls the list to it
|
|
34
|
+
// and paints a focus ring the tap never asked for.
|
|
35
|
+
initialFocus={panelRef}
|
|
36
|
+
showCloseButton={false}
|
|
37
|
+
className="max-h-[85dvh] gap-0"
|
|
38
|
+
>
|
|
39
|
+
<DrawerHeader className="shrink-0 flex-row items-center gap-2 pb-2">
|
|
40
|
+
<DrawerTitle className="line-clamp-1 text-lg font-semibold">
|
|
41
|
+
{props.title}
|
|
42
|
+
</DrawerTitle>
|
|
43
|
+
<DrawerClose
|
|
44
|
+
render={<Button variant="ghost" size="icon-sm" className="-mr-2 ml-auto" />}
|
|
45
|
+
>
|
|
46
|
+
<XIcon className="size-5" />
|
|
47
|
+
<span className="sr-only">Close</span>
|
|
48
|
+
</DrawerClose>
|
|
49
|
+
</DrawerHeader>
|
|
50
|
+
<div
|
|
51
|
+
ref={panelRef}
|
|
52
|
+
tabIndex={-1}
|
|
53
|
+
className={cn(
|
|
54
|
+
"min-h-0 flex-1 overflow-y-auto px-4 pt-2 text-base outline-none empty:hidden",
|
|
55
|
+
props.footer ? "pb-2" : "pb-[max(1.5rem,env(safe-area-inset-bottom))]",
|
|
56
|
+
props.bodyClassName,
|
|
57
|
+
)}
|
|
58
|
+
>
|
|
59
|
+
{props.children}
|
|
60
|
+
</div>
|
|
61
|
+
{props.footer && (
|
|
62
|
+
<div className="shrink-0 px-4 py-4 pb-[max(1rem,env(safe-area-inset-bottom))]">
|
|
63
|
+
{props.footer}
|
|
64
|
+
</div>
|
|
65
|
+
)}
|
|
66
|
+
</DrawerContent>
|
|
67
|
+
</Drawer>
|
|
68
|
+
)
|
|
69
|
+
}
|