A design system in a box. hip-ui.tngl.io/docs/introduction
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

better nav

+502 -458
+15 -456
apps/docs/src/routes/docs.$.tsx
··· 1 - import type { MDXComponents } from "mdx/types"; 2 - import type { JSX as Jsx } from "react/jsx-runtime"; 3 - 4 - import * as stylex from "@stylexjs/stylex"; 5 - import { 6 - createFileRoute, 7 - createLink, 8 - useLocation, 9 - useMatches, 10 - } from "@tanstack/react-router"; 1 + import { createFileRoute } from "@tanstack/react-router"; 11 2 import { allDocs } from "content-collections"; 12 - import { Moon, Sun } from "lucide-react"; 13 - import { useEffect, useState } from "react"; 14 3 import { modules, pages } from "virtual:content"; 15 - 16 - import type { LinkProps } from "@/components/link"; 17 4 18 5 import { Flex } from "@/components/flex"; 19 - import { IconButton } from "@/components/icon-button"; 20 - import { Link as TypographyLink } from "@/components/link"; 21 - import { 22 - Sidebar, 23 - SidebarGroup, 24 - SidebarHeader, 25 - SidebarItem, 26 - SidebarSection, 27 - } from "@/components/sidebar"; 28 - import { SidebarLayout } from "@/components/sidebar-layout"; 29 - import { TableOfContents } from "@/components/table-of-contents"; 30 - import { 31 - Blockquote, 32 - Body, 33 - Heading1, 34 - Heading2, 35 - Heading3, 36 - Heading4, 37 - Heading5, 38 - InlineCode, 39 - LinkedHeading, 40 - ListItem, 41 - OrderedList, 42 - Pre, 43 - UnorderedList, 44 - } from "@/components/typography"; 6 + import { Heading1 } from "@/components/typography"; 45 7 import { Text } from "@/components/typography/text"; 46 8 import { CopyForLLMButton } from "@/lib/CopyForLLM"; 47 - import { MarkdownExportContext } from "@/lib/MarkdownExportContext"; 48 - import { ThemePicker } from "@/lib/ThemePicker"; 49 - import { UnShikiCode } from "@/lib/UnShiki"; 50 9 51 - import { breakpoints } from "../components/theme/breakpoints.stylex"; 52 - import { 53 - size as sizeSpace, 54 - verticalSpace, 55 - } from "../components/theme/semantic-spacing.stylex"; 56 - 57 - declare global { 58 - // eslint-disable-next-line @typescript-eslint/no-namespace 59 - namespace JSX { 60 - type ElementClass = Jsx.ElementClass; 61 - type Element = Jsx.Element; 62 - type IntrinsicElements = Jsx.IntrinsicElements; 63 - } 64 - } 65 - 66 - const TypographyRouterLink = createLink(TypographyLink); 67 - const SidebarItemLink = createLink(SidebarItem); 68 - 69 - interface SidebarLeafEntry { 70 - id: string; 71 - label: string; 72 - to: "/docs/$"; 73 - params: { _splat: string }; 74 - } 75 - 76 - interface SidebarSectionEntry { 77 - id: string; 78 - label: string; 79 - items: Array<SidebarLeafEntry>; 80 - } 81 - 82 - interface SidebarGroupEntry { 83 - id: string; 84 - label?: string; 85 - items: Array<SidebarLeafEntry | SidebarSectionEntry>; 86 - } 87 - 88 - type SidebarTopLevelEntry = SidebarLeafEntry | SidebarGroupEntry; 89 - 90 - const componentDocs = allDocs.filter((doc) => 91 - doc._meta.directory.startsWith("components"), 92 - ); 93 - const foundationDocs = allDocs.filter((doc) => 94 - doc._meta.directory.startsWith("foundations"), 95 - ); 96 - const showCaseDocs = allDocs.filter((doc) => 97 - doc._meta.directory.startsWith("showcase"), 98 - ); 99 - 100 - // oxlint-disable-next-line eslint-plugin-unicorn(no-array-reduce) 101 - const componentGroups = componentDocs.reduce( 102 - (acc, doc) => { 103 - const pathParts = doc._meta.path.split("/"); 104 - const folderName = 105 - pathParts.length > 2 && pathParts[1] ? pathParts[1] : "components"; 106 - 107 - if (!acc[folderName]) { 108 - acc[folderName] = []; 109 - } 110 - 111 - acc[folderName]?.push(doc); 112 - 113 - return acc; 114 - }, 115 - {} as Record<string, typeof componentDocs>, 116 - ); 117 - 118 - const componentItems: Array<SidebarSectionEntry> = Object.entries( 119 - componentGroups, 120 - ) 121 - .toSorted(([a], [b]) => a.localeCompare(b)) 122 - .map(([folderName, docs]) => ({ 123 - id: `components-${folderName}`, 124 - label: folderName.charAt(0).toUpperCase() + folderName.slice(1), 125 - items: docs 126 - .toSorted((a, b) => a.title.localeCompare(b.title)) 127 - .map((doc) => ({ 128 - id: doc._meta.path, 129 - label: doc.title, 130 - to: "/docs/$", 131 - params: { _splat: doc._meta.path }, 132 - })), 133 - })); 134 - 135 - const sidebarItems: Array<SidebarTopLevelEntry> = [ 136 - { 137 - id: "getting-started", 138 - items: [ 139 - { 140 - id: "introduction", 141 - label: "Introduction", 142 - to: "/docs/$", 143 - params: { _splat: "introduction" }, 144 - }, 145 - { 146 - id: "ai", 147 - label: "AI Usage", 148 - to: "/docs/$", 149 - params: { _splat: "ai" }, 150 - }, 151 - ], 152 - }, 153 - { 154 - id: "foundations", 155 - label: "Foundations", 156 - items: foundationDocs.map((doc) => ({ 157 - id: doc._meta.path, 158 - label: doc.title, 159 - to: "/docs/$", 160 - params: { _splat: doc._meta.path }, 161 - })), 162 - }, 163 - { 164 - id: "components", 165 - label: "Components", 166 - items: componentItems, 167 - }, 168 - { 169 - id: "showcases", 170 - label: "Showcases", 171 - items: showCaseDocs.map( 172 - (doc): SidebarLeafEntry => ({ 173 - id: doc._meta.path, 174 - label: doc.title, 175 - to: "/docs/$", 176 - params: { _splat: doc._meta.path }, 177 - }), 178 - ), 179 - }, 180 - ]; 181 - 182 - const flatItems: Array<SidebarLeafEntry> = sidebarItems.flatMap((item) => { 183 - if (!("items" in item)) { 184 - return [item]; 185 - } 186 - 187 - return item.items.flatMap((subItem) => { 188 - if ("items" in subItem) { 189 - return subItem.items; 190 - } 191 - 192 - return [subItem]; 193 - }); 194 - }); 195 - 196 - const styles = stylex.create({ 197 - header: { 198 - marginBottom: verticalSpace["10xl"], 199 - marginTop: { 200 - default: sizeSpace["xl"], 201 - [breakpoints.sm]: sizeSpace["4xl"], 202 - }, 203 - }, 204 - defaultMargin: { 205 - marginBottom: verticalSpace["5xl"], 206 - marginTop: verticalSpace["5xl"], 207 - }, 208 - h2: { 209 - marginBottom: verticalSpace["7xl"], 210 - marginTop: verticalSpace["10xl"], 211 - }, 212 - h3: { 213 - marginBottom: verticalSpace["4xl"], 214 - marginTop: verticalSpace["7xl"], 215 - }, 216 - h4: { 217 - marginBottom: verticalSpace["7xl"], 218 - marginTop: verticalSpace["7xl"], 219 - }, 220 - h5: { 221 - marginBottom: verticalSpace["7xl"], 222 - marginTop: verticalSpace["7xl"], 223 - }, 224 - grow: { 225 - flexGrow: 1, 226 - minWidth: 0, 227 - }, 228 - }); 229 - 230 - function DarkModeToggle() { 231 - const [colorScheme, setColorScheme] = useState<"light" | "dark">("light"); 232 - 233 - const toggleColorScheme = () => { 234 - const newColorScheme = colorScheme === "light" ? "dark" : "light"; 235 - 236 - setColorScheme(newColorScheme); 237 - localStorage.setItem("hip-ui-color-scheme", newColorScheme); 238 - document.body.style.colorScheme = newColorScheme; 239 - }; 240 - 241 - useEffect(() => { 242 - const localColorScheme = localStorage.getItem("hip-ui-color-scheme"); 243 - 244 - if (localColorScheme) { 245 - setColorScheme(localColorScheme as "light" | "dark"); 246 - } 247 - }, []); 248 - 249 - return ( 250 - <IconButton 251 - variant="secondary" 252 - label="Toggle Dark Mode" 253 - onPress={toggleColorScheme} 254 - > 255 - {colorScheme === "dark" ? <Moon /> : <Sun />} 256 - </IconButton> 257 - ); 258 - } 259 - 260 - function Link({ href, ...props }: LinkProps) { 261 - if (href && href.startsWith("/")) { 262 - const splat = href.split("/").slice(2).join("/"); 263 - return ( 264 - <TypographyRouterLink 265 - to="/docs/$" 266 - params={{ _splat: splat }} 267 - {...props} 268 - /> 269 - ); 270 - } 271 - 272 - return <TypographyLink {...props} href={href} />; 273 - } 274 - 275 - const components: MDXComponents = { 276 - h1: ({ className: _className, style: _style, ...props }) => ( 277 - <Heading1 {...props} /> 278 - ), 279 - h2: ({ className: _className, style: _style, ...props }) => ( 280 - <LinkedHeading id={props.id} style={styles.h2}> 281 - <Heading2 {...props} /> 282 - </LinkedHeading> 283 - ), 284 - h3: ({ className: _className, style: _style, ...props }) => ( 285 - <LinkedHeading id={props.id} style={styles.h3}> 286 - <Heading3 {...props} /> 287 - </LinkedHeading> 288 - ), 289 - h4: ({ className: _className, style: _style, ...props }) => ( 290 - <LinkedHeading id={props.id} style={styles.h4}> 291 - <Heading4 {...props} /> 292 - </LinkedHeading> 293 - ), 294 - h5: ({ className: _className, style: _style, ...props }) => ( 295 - <LinkedHeading id={props.id} style={styles.h5}> 296 - <Heading5 {...props} /> 297 - </LinkedHeading> 298 - ), 299 - p: ({ className: _className, style: _style, ...props }) => ( 300 - <Body {...props} style={styles.defaultMargin} /> 301 - ), 302 - a: ({ className: _className, style: _style, ...props }) => ( 303 - <Link {...(props as LinkProps)} /> 304 - ), 305 - ul: ({ className: _className, style: _style, ...props }) => ( 306 - <UnorderedList {...props} style={styles.defaultMargin} /> 307 - ), 308 - ol: ({ className: _className, style: _style, ...props }) => ( 309 - <OrderedList {...props} style={styles.defaultMargin} /> 310 - ), 311 - li: ({ className: _className, style: _style, ...props }) => ( 312 - <ListItem {...props} /> 313 - ), 314 - pre: ({ className: _className, style: _style, ...props }) => ( 315 - <Pre {...props} style={styles.defaultMargin} /> 316 - ), 317 - code: ({ className: _className, style: _style, ...props }) => ( 318 - <InlineCode {...props} /> 319 - ), 320 - blockquote: ({ className: _className, style: _style, ...props }) => ( 321 - <Blockquote {...props} style={styles.defaultMargin} /> 322 - ), 323 - }; 324 - 325 - function DocSidebar() { 326 - const location = useLocation(); 327 - const matches = useMatches(); 328 - const match = matches.find( 329 - (routeMatch) => routeMatch.pathname === location.pathname, 330 - ); 331 - const currentSplat = 332 - match?.params && 333 - "_splat" in match.params && 334 - typeof match.params._splat === "string" 335 - ? match.params._splat.replace("/docs/", "") 336 - : undefined; 337 - const currentItem = flatItems.find((item) => item.id === currentSplat); 338 - 339 - return ( 340 - <Sidebar> 341 - <SidebarHeader 342 - action={ 343 - <Flex gap="xxs" align="center"> 344 - <ThemePicker /> 345 - <DarkModeToggle /> 346 - </Flex> 347 - } 348 - > 349 - <Text font="title" size="4xl" weight="bold"> 350 - Hip UI 351 - </Text> 352 - </SidebarHeader> 353 - {sidebarItems.map((item) => { 354 - if (!("items" in item)) { 355 - return ( 356 - <SidebarItemLink 357 - key={item.id} 358 - to={item.to} 359 - params={item.params} 360 - isActive={currentItem?.id === item.id} 361 - > 362 - {item.label} 363 - </SidebarItemLink> 364 - ); 365 - } 366 - 367 - const contents = item.items.map((subItem) => { 368 - if ("items" in subItem) { 369 - return ( 370 - <> 371 - {subItem.items.map((leafItem) => ( 372 - <SidebarItemLink 373 - key={leafItem.id} 374 - to={leafItem.to} 375 - params={leafItem.params} 376 - isActive={currentItem?.id === leafItem.id} 377 - > 378 - {leafItem.label} 379 - </SidebarItemLink> 380 - ))} 381 - </> 382 - ); 383 - } 384 - 385 - return ( 386 - <SidebarItemLink 387 - key={subItem.id} 388 - to={subItem.to} 389 - params={subItem.params} 390 - isActive={currentItem?.id === subItem.id} 391 - > 392 - {subItem.label} 393 - </SidebarItemLink> 394 - ); 395 - }); 396 - 397 - if (item.label) { 398 - return ( 399 - <SidebarGroup title={item.label} key={item.id}> 400 - <SidebarSection>{contents}</SidebarSection> 401 - </SidebarGroup> 402 - ); 403 - } 404 - 405 - return <SidebarSection key={item.id}>{contents}</SidebarSection>; 406 - })} 407 - </Sidebar> 408 - ); 409 - } 10 + import { docMdxComponents, docPageStyles } from "./docs"; 410 11 411 12 export const Route = createFileRoute("/docs/$")({ 412 13 component: RouteComponent, ··· 434 35 }); 435 36 436 37 function RouteComponent() { 38 + const { docPath } = Route.useLoaderData(); 437 39 const { _splat } = Route.useParams(); 438 - const { toc, isMarkdown, docPath } = Route.useLoaderData(); 439 40 const doc = allDocs.find((d) => 440 41 docPath.match(new RegExp(`${d._meta.path}$`)), 441 42 ); ··· 450 51 throw new Error(`Content not found: ${docPath}`); 451 52 } 452 53 453 - const isShowcase = docPath.includes("showcase"); 454 - 455 - if (isShowcase) { 456 - return <Page components={components} />; 457 - } 458 - 459 - if (isMarkdown) { 460 - return ( 461 - <MarkdownExportContext.Provider value={{ isMarkdown, docPath }}> 462 - <div data-markdown-export> 463 - <Flex direction="column" gap="7xl" style={styles.header}> 464 - <Heading1>{doc.title}</Heading1> 465 - <Text size="xl" variant="secondary"> 466 - {doc.description} 467 - </Text> 468 - </Flex> 469 - <Page 470 - components={{ 471 - code: (props) => { 472 - if (props.className?.includes("language-")) { 473 - return <UnShikiCode {...props} />; 474 - } 475 - 476 - return <code {...props} />; 477 - }, 478 - }} 479 - /> 480 - </div> 481 - </MarkdownExportContext.Provider> 482 - ); 483 - } 484 - 485 54 return ( 486 - <SidebarLayout.Root> 487 - <SidebarLayout.NavigationSidebar> 488 - <DocSidebar /> 489 - </SidebarLayout.NavigationSidebar> 490 - <SidebarLayout.Page> 491 - <Flex direction="column" gap="7xl" style={styles.header}> 492 - <Flex align="center" gap="xl"> 493 - <Heading1 style={styles.grow}>{doc.title}</Heading1> 494 - <CopyForLLMButton /> 495 - </Flex> 496 - <Text size="xl" variant="secondary"> 497 - {doc.description} 498 - </Text> 55 + <> 56 + <Flex direction="column" gap="7xl" style={docPageStyles.header}> 57 + <Flex align="center" gap="xl"> 58 + <Heading1 style={docPageStyles.grow}>{doc.title}</Heading1> 59 + <CopyForLLMButton /> 499 60 </Flex> 500 - <Page components={components} /> 501 - </SidebarLayout.Page> 502 - {toc && ( 503 - <SidebarLayout.InconsequentialSidebar visible="md"> 504 - <TableOfContents toc={toc} /> 505 - </SidebarLayout.InconsequentialSidebar> 506 - )} 507 - </SidebarLayout.Root> 61 + <Text size="xl" variant="secondary"> 62 + {doc.description} 63 + </Text> 64 + </Flex> 65 + <Page components={docMdxComponents} /> 66 + </> 508 67 ); 509 68 }
+487 -2
apps/docs/src/routes/docs.tsx
··· 1 - import { Outlet, createFileRoute } from "@tanstack/react-router"; 1 + /* eslint-disable react-refresh/only-export-components */ 2 + import type { MDXComponents } from "mdx/types"; 3 + import type { ComponentProps } from "react"; 4 + import type { JSX as Jsx } from "react/jsx-runtime"; 5 + 6 + import * as stylex from "@stylexjs/stylex"; 7 + import { 8 + Outlet, 9 + createFileRoute, 10 + createLink, 11 + useLocation, 12 + useMatches, 13 + } from "@tanstack/react-router"; 14 + import { allDocs } from "content-collections"; 15 + import { Moon, Sun } from "lucide-react"; 16 + import { Suspense, useEffect, useState } from "react"; 17 + import { pages } from "virtual:content"; 18 + 19 + import type { LinkProps } from "@/components/link"; 20 + 21 + import { Flex } from "@/components/flex"; 22 + import { IconButton } from "@/components/icon-button"; 23 + import { Link as TypographyLink } from "@/components/link"; 24 + import { 25 + Sidebar, 26 + SidebarGroup, 27 + SidebarHeader, 28 + SidebarItem, 29 + SidebarSection, 30 + } from "@/components/sidebar"; 31 + import { SidebarLayout } from "@/components/sidebar-layout"; 32 + import { TableOfContents } from "@/components/table-of-contents"; 33 + import { 34 + Blockquote, 35 + Body, 36 + Heading1, 37 + Heading2, 38 + Heading3, 39 + Heading4, 40 + Heading5, 41 + InlineCode, 42 + LinkedHeading, 43 + ListItem, 44 + OrderedList, 45 + Pre, 46 + UnorderedList, 47 + } from "@/components/typography"; 48 + import { Text } from "@/components/typography/text"; 49 + import { MarkdownExportContext } from "@/lib/MarkdownExportContext"; 50 + import { ThemePicker } from "@/lib/ThemePicker"; 51 + import { UnShikiCode } from "@/lib/UnShiki"; 52 + 53 + import { breakpoints } from "../components/theme/breakpoints.stylex"; 54 + import { 55 + size as sizeSpace, 56 + verticalSpace, 57 + } from "../components/theme/semantic-spacing.stylex"; 58 + 59 + declare global { 60 + // eslint-disable-next-line @typescript-eslint/no-namespace 61 + namespace JSX { 62 + type ElementClass = Jsx.ElementClass; 63 + type Element = Jsx.Element; 64 + type IntrinsicElements = Jsx.IntrinsicElements; 65 + } 66 + } 67 + 68 + const TypographyRouterLink = createLink(TypographyLink); 69 + const SidebarItemLink = createLink(SidebarItem); 70 + 71 + interface SidebarLeafEntry { 72 + id: string; 73 + label: string; 74 + to: "/docs/$"; 75 + params: { _splat: string }; 76 + } 77 + 78 + interface SidebarSectionEntry { 79 + id: string; 80 + label: string; 81 + items: Array<SidebarLeafEntry>; 82 + } 83 + 84 + interface SidebarGroupEntry { 85 + id: string; 86 + label?: string; 87 + items: Array<SidebarLeafEntry | SidebarSectionEntry>; 88 + } 89 + 90 + type SidebarTopLevelEntry = SidebarLeafEntry | SidebarGroupEntry; 91 + 92 + const componentDocs = allDocs.filter((doc) => 93 + doc._meta.directory.startsWith("components"), 94 + ); 95 + const foundationDocs = allDocs.filter((doc) => 96 + doc._meta.directory.startsWith("foundations"), 97 + ); 98 + const showCaseDocs = allDocs.filter((doc) => 99 + doc._meta.directory.startsWith("showcase"), 100 + ); 101 + 102 + // oxlint-disable-next-line eslint-plugin-unicorn(no-array-reduce) 103 + const componentGroups = componentDocs.reduce( 104 + (acc, doc) => { 105 + const pathParts = doc._meta.path.split("/"); 106 + const folderName = 107 + pathParts.length > 2 && pathParts[1] ? pathParts[1] : "components"; 108 + 109 + if (!acc[folderName]) { 110 + acc[folderName] = []; 111 + } 112 + 113 + acc[folderName]?.push(doc); 114 + 115 + return acc; 116 + }, 117 + {} as Record<string, typeof componentDocs>, 118 + ); 119 + 120 + const componentItems: Array<SidebarSectionEntry> = Object.entries( 121 + componentGroups, 122 + ) 123 + .toSorted(([a], [b]) => a.localeCompare(b)) 124 + .map(([folderName, docs]) => ({ 125 + id: `components-${folderName}`, 126 + label: folderName.charAt(0).toUpperCase() + folderName.slice(1), 127 + items: docs 128 + .toSorted((a, b) => a.title.localeCompare(b.title)) 129 + .map((doc) => ({ 130 + id: doc._meta.path, 131 + label: doc.title, 132 + to: "/docs/$", 133 + params: { _splat: doc._meta.path }, 134 + })), 135 + })); 136 + 137 + const sidebarItems: Array<SidebarTopLevelEntry> = [ 138 + { 139 + id: "getting-started", 140 + items: [ 141 + { 142 + id: "introduction", 143 + label: "Introduction", 144 + to: "/docs/$", 145 + params: { _splat: "introduction" }, 146 + }, 147 + { 148 + id: "ai", 149 + label: "AI Usage", 150 + to: "/docs/$", 151 + params: { _splat: "ai" }, 152 + }, 153 + ], 154 + }, 155 + { 156 + id: "foundations", 157 + label: "Foundations", 158 + items: foundationDocs.map((doc) => ({ 159 + id: doc._meta.path, 160 + label: doc.title, 161 + to: "/docs/$", 162 + params: { _splat: doc._meta.path }, 163 + })), 164 + }, 165 + { 166 + id: "components", 167 + label: "Components", 168 + items: componentItems, 169 + }, 170 + { 171 + id: "showcases", 172 + label: "Showcases", 173 + items: showCaseDocs.map( 174 + (doc): SidebarLeafEntry => ({ 175 + id: doc._meta.path, 176 + label: doc.title, 177 + to: "/docs/$", 178 + params: { _splat: doc._meta.path }, 179 + }), 180 + ), 181 + }, 182 + ]; 183 + 184 + const flatItems: Array<SidebarLeafEntry> = sidebarItems.flatMap((item) => { 185 + if (!("items" in item)) { 186 + return [item]; 187 + } 188 + 189 + return item.items.flatMap((subItem) => { 190 + if ("items" in subItem) { 191 + return subItem.items; 192 + } 193 + 194 + return [subItem]; 195 + }); 196 + }); 197 + 198 + export const docPageStyles = stylex.create({ 199 + header: { 200 + marginBottom: verticalSpace["10xl"], 201 + marginTop: { 202 + default: sizeSpace["xl"], 203 + [breakpoints.sm]: sizeSpace["4xl"], 204 + }, 205 + }, 206 + defaultMargin: { 207 + marginBottom: verticalSpace["5xl"], 208 + marginTop: verticalSpace["5xl"], 209 + }, 210 + h2: { 211 + marginBottom: verticalSpace["7xl"], 212 + marginTop: verticalSpace["10xl"], 213 + }, 214 + h3: { 215 + marginBottom: verticalSpace["4xl"], 216 + marginTop: verticalSpace["7xl"], 217 + }, 218 + h4: { 219 + marginBottom: verticalSpace["7xl"], 220 + marginTop: verticalSpace["7xl"], 221 + }, 222 + h5: { 223 + marginBottom: verticalSpace["7xl"], 224 + marginTop: verticalSpace["7xl"], 225 + }, 226 + grow: { 227 + flexGrow: 1, 228 + minWidth: 0, 229 + }, 230 + }); 231 + 232 + function DarkModeToggle() { 233 + const [colorScheme, setColorScheme] = useState<"light" | "dark">("light"); 234 + 235 + const toggleColorScheme = () => { 236 + const newColorScheme = colorScheme === "light" ? "dark" : "light"; 237 + 238 + setColorScheme(newColorScheme); 239 + localStorage.setItem("hip-ui-color-scheme", newColorScheme); 240 + document.body.style.colorScheme = newColorScheme; 241 + }; 242 + 243 + useEffect(() => { 244 + const localColorScheme = localStorage.getItem("hip-ui-color-scheme"); 245 + 246 + if (localColorScheme) { 247 + setColorScheme(localColorScheme as "light" | "dark"); 248 + } 249 + }, []); 250 + 251 + return ( 252 + <IconButton 253 + variant="secondary" 254 + label="Toggle Dark Mode" 255 + onPress={toggleColorScheme} 256 + > 257 + {colorScheme === "dark" ? <Moon /> : <Sun />} 258 + </IconButton> 259 + ); 260 + } 261 + 262 + function Link({ href, ...props }: LinkProps) { 263 + if (href && href.startsWith("/")) { 264 + const splat = href.split("/").slice(2).join("/"); 265 + return ( 266 + <TypographyRouterLink 267 + to="/docs/$" 268 + params={{ _splat: splat }} 269 + {...props} 270 + /> 271 + ); 272 + } 273 + 274 + return <TypographyLink {...props} href={href} />; 275 + } 276 + 277 + export const docMdxComponents: MDXComponents = { 278 + h1: ({ className: _className, style: _style, ...props }) => ( 279 + <Heading1 {...props} /> 280 + ), 281 + h2: ({ className: _className, style: _style, ...props }) => ( 282 + <LinkedHeading id={props.id} style={docPageStyles.h2}> 283 + <Heading2 {...props} /> 284 + </LinkedHeading> 285 + ), 286 + h3: ({ className: _className, style: _style, ...props }) => ( 287 + <LinkedHeading id={props.id} style={docPageStyles.h3}> 288 + <Heading3 {...props} /> 289 + </LinkedHeading> 290 + ), 291 + h4: ({ className: _className, style: _style, ...props }) => ( 292 + <LinkedHeading id={props.id} style={docPageStyles.h4}> 293 + <Heading4 {...props} /> 294 + </LinkedHeading> 295 + ), 296 + h5: ({ className: _className, style: _style, ...props }) => ( 297 + <LinkedHeading id={props.id} style={docPageStyles.h5}> 298 + <Heading5 {...props} /> 299 + </LinkedHeading> 300 + ), 301 + p: ({ className: _className, style: _style, ...props }) => ( 302 + <Body {...props} style={docPageStyles.defaultMargin} /> 303 + ), 304 + a: ({ className: _className, style: _style, ...props }) => ( 305 + <Link {...(props as LinkProps)} /> 306 + ), 307 + ul: ({ className: _className, style: _style, ...props }) => ( 308 + <UnorderedList {...props} style={docPageStyles.defaultMargin} /> 309 + ), 310 + ol: ({ className: _className, style: _style, ...props }) => ( 311 + <OrderedList {...props} style={docPageStyles.defaultMargin} /> 312 + ), 313 + li: ({ className: _className, style: _style, ...props }) => ( 314 + <ListItem {...props} /> 315 + ), 316 + pre: ({ className: _className, style: _style, ...props }) => ( 317 + <Pre {...props} style={docPageStyles.defaultMargin} /> 318 + ), 319 + code: ({ className: _className, style: _style, ...props }) => ( 320 + <InlineCode {...props} /> 321 + ), 322 + blockquote: ({ className: _className, style: _style, ...props }) => ( 323 + <Blockquote {...props} style={docPageStyles.defaultMargin} /> 324 + ), 325 + }; 326 + 327 + function DocSidebar() { 328 + const location = useLocation(); 329 + const matches = useMatches(); 330 + const match = matches.find( 331 + (routeMatch) => routeMatch.pathname === location.pathname, 332 + ); 333 + const currentSplat = 334 + match?.params && 335 + "_splat" in match.params && 336 + typeof match.params._splat === "string" 337 + ? match.params._splat.replace("/docs/", "") 338 + : undefined; 339 + const currentItem = flatItems.find((item) => item.id === currentSplat); 340 + 341 + return ( 342 + <Sidebar> 343 + <SidebarHeader 344 + action={ 345 + <Flex gap="xxs" align="center"> 346 + <ThemePicker /> 347 + <DarkModeToggle /> 348 + </Flex> 349 + } 350 + > 351 + <Text font="title" size="4xl" weight="bold"> 352 + Hip UI 353 + </Text> 354 + </SidebarHeader> 355 + {sidebarItems.map((item) => { 356 + if (!("items" in item)) { 357 + return ( 358 + <SidebarItemLink 359 + key={item.id} 360 + to={item.to} 361 + params={item.params} 362 + isActive={currentItem?.id === item.id} 363 + > 364 + {item.label} 365 + </SidebarItemLink> 366 + ); 367 + } 368 + 369 + const contents = item.items.map((subItem) => { 370 + if ("items" in subItem) { 371 + return ( 372 + <> 373 + {subItem.items.map((leafItem) => ( 374 + <SidebarItemLink 375 + key={leafItem.id} 376 + to={leafItem.to} 377 + params={leafItem.params} 378 + isActive={currentItem?.id === leafItem.id} 379 + > 380 + {leafItem.label} 381 + </SidebarItemLink> 382 + ))} 383 + </> 384 + ); 385 + } 386 + 387 + return ( 388 + <SidebarItemLink 389 + key={subItem.id} 390 + to={subItem.to} 391 + params={subItem.params} 392 + isActive={currentItem?.id === subItem.id} 393 + > 394 + {subItem.label} 395 + </SidebarItemLink> 396 + ); 397 + }); 398 + 399 + if (item.label) { 400 + return ( 401 + <SidebarGroup title={item.label} key={item.id}> 402 + <SidebarSection>{contents}</SidebarSection> 403 + </SidebarGroup> 404 + ); 405 + } 406 + 407 + return <SidebarSection key={item.id}>{contents}</SidebarSection>; 408 + })} 409 + </Sidebar> 410 + ); 411 + } 2 412 3 413 export const Route = createFileRoute("/docs")({ 4 414 component: RouteComponent, 5 415 }); 6 416 7 417 function RouteComponent() { 8 - return <Outlet />; 418 + const location = useLocation(); 419 + const matches = useMatches(); 420 + const currentMatch = matches.find( 421 + (routeMatch) => routeMatch.pathname === location.pathname, 422 + ); 423 + const docPath = location.pathname.replace(/\.md$/, ""); 424 + const isMarkdown = location.pathname.endsWith(".md"); 425 + const doc = allDocs.find((d) => 426 + docPath.match(new RegExp(`${d._meta.path}$`)), 427 + ); 428 + 429 + if (!doc) { 430 + return <Outlet />; 431 + } 432 + 433 + const Page = pages[docPath]; 434 + 435 + if (!Page) { 436 + throw new Error(`Content not found: ${docPath}`); 437 + } 438 + 439 + const isShowcase = docPath.includes("showcase"); 440 + const toc = ( 441 + currentMatch?.loaderData as 442 + | { toc?: ComponentProps<typeof TableOfContents>["toc"] } 443 + | undefined 444 + )?.toc; 445 + 446 + if (isShowcase) { 447 + return <Page components={docMdxComponents} />; 448 + } 449 + 450 + if (isMarkdown) { 451 + return ( 452 + <MarkdownExportContext.Provider value={{ isMarkdown, docPath }}> 453 + <div data-markdown-export> 454 + <Flex direction="column" gap="7xl" style={docPageStyles.header}> 455 + <Heading1>{doc.title}</Heading1> 456 + <Text size="xl" variant="secondary"> 457 + {doc.description} 458 + </Text> 459 + </Flex> 460 + <Page 461 + components={{ 462 + code: (props) => { 463 + if (props.className?.includes("language-")) { 464 + return <UnShikiCode {...props} />; 465 + } 466 + 467 + return <code {...props} />; 468 + }, 469 + }} 470 + /> 471 + </div> 472 + </MarkdownExportContext.Provider> 473 + ); 474 + } 475 + 476 + return ( 477 + <SidebarLayout.Root> 478 + <SidebarLayout.NavigationSidebar> 479 + <DocSidebar /> 480 + </SidebarLayout.NavigationSidebar> 481 + <SidebarLayout.Page> 482 + <Suspense> 483 + <Outlet /> 484 + </Suspense> 485 + </SidebarLayout.Page> 486 + {toc && ( 487 + <SidebarLayout.InconsequentialSidebar visible="md"> 488 + <TableOfContents toc={toc} /> 489 + </SidebarLayout.InconsequentialSidebar> 490 + )} 491 + </SidebarLayout.Root> 492 + ); 9 493 } 494 + /* eslint-enable react-refresh/only-export-components */