[data-slot=section]+[data-slot=section]]:mt-8'
+ )}
+ />
+ )
+}
+
+export function SidebarFooter({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {
+ return (
+
[data-slot=section]+[data-slot=section]]:mt-2.5'
+ )}
+ />
+ )
+}
+
+export function SidebarSection({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {
+ let id = useId()
+
+ return (
+
+
+
+ )
+}
+
+export function SidebarDivider({ className, ...props }: React.ComponentPropsWithoutRef<'hr'>) {
+ return
+}
+
+export function SidebarSpacer({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {
+ return
+}
+
+export function SidebarHeading({ className, ...props }: React.ComponentPropsWithoutRef<'h3'>) {
+ return (
+
+ )
+}
+
+export const SidebarItem = forwardRef(function SidebarItem(
+ {
+ current,
+ className,
+ children,
+ ...props
+ }: { current?: boolean; className?: string; children: React.ReactNode } & (
+ | Omit
+ | Omit, 'type' | 'className'>
+ ),
+ ref: React.ForwardedRef
+) {
+ let classes = clsx(
+ // Base
+ 'flex w-full items-center gap-3 rounded-lg px-2 py-2.5 text-left text-base/6 font-medium text-zinc-950 sm:py-2 sm:text-sm/5',
+ // Leading icon/icon-only
+ 'data-[slot=icon]:*:size-6 data-[slot=icon]:*:shrink-0 data-[slot=icon]:*:fill-zinc-500 sm:data-[slot=icon]:*:size-5',
+ // Trailing icon (down chevron or similar)
+ 'data-[slot=icon]:last:*:ml-auto data-[slot=icon]:last:*:size-5 sm:data-[slot=icon]:last:*:size-4',
+ // Avatar
+ 'data-[slot=avatar]:*:-m-0.5 data-[slot=avatar]:*:size-7 data-[slot=avatar]:*:[--ring-opacity:10%] sm:data-[slot=avatar]:*:size-6',
+ // Hover
+ 'data-[hover]:bg-zinc-950/5 data-[slot=icon]:*:data-[hover]:fill-zinc-950',
+ // Active
+ 'data-[active]:bg-zinc-950/5 data-[slot=icon]:*:data-[active]:fill-zinc-950',
+ // Current
+ 'data-[slot=icon]:*:data-[current]:fill-zinc-950',
+ // Dark mode
+ 'dark:text-white dark:data-[slot=icon]:*:fill-zinc-400',
+ 'dark:data-[hover]:bg-white/5 dark:data-[slot=icon]:*:data-[hover]:fill-white',
+ 'dark:data-[active]:bg-white/5 dark:data-[slot=icon]:*:data-[active]:fill-white',
+ 'dark:data-[slot=icon]:*:data-[current]:fill-white'
+ )
+
+ return (
+
+ {current && (
+
+ )}
+ {'href' in props ? (
+
+
+ {children}
+
+
+ ) : (
+
+ {children}
+
+ )}
+
+ )
+})
+
+export function SidebarLabel({ className, ...props }: React.ComponentPropsWithoutRef<'span'>) {
+ return
+}
diff --git a/src/components/ui/stacked-layout.tsx b/src/components/ui/stacked-layout.tsx
new file mode 100644
index 0000000..9f2149c
--- /dev/null
+++ b/src/components/ui/stacked-layout.tsx
@@ -0,0 +1,79 @@
+'use client'
+
+import * as Headless from '@headlessui/react'
+import React, { useState } from 'react'
+import { NavbarItem } from './navbar'
+
+function OpenMenuIcon() {
+ return (
+
+ )
+}
+
+function CloseMenuIcon() {
+ return (
+
+ )
+}
+
+function MobileSidebar({ open, close, children }: React.PropsWithChildren<{ open: boolean; close: () => void }>) {
+ return (
+
+
+
+
+
+
+ )
+}
+
+export function StackedLayout({
+ navbar,
+ sidebar,
+ children,
+}: React.PropsWithChildren<{ navbar: React.ReactNode; sidebar: React.ReactNode }>) {
+ let [showSidebar, setShowSidebar] = useState(false)
+
+ return (
+
+ {/* Sidebar on mobile */}
+
setShowSidebar(false)}>
+ {sidebar}
+
+
+ {/* Navbar */}
+
+
+ {/* Content */}
+
+
+
+
+ )
+}
diff --git a/src/components/ui/switch.tsx b/src/components/ui/switch.tsx
new file mode 100644
index 0000000..0f9fc1b
--- /dev/null
+++ b/src/components/ui/switch.tsx
@@ -0,0 +1,192 @@
+import * as Headless from '@headlessui/react'
+import clsx from 'clsx'
+import type React from 'react'
+
+export function SwitchGroup({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {
+ return (
+
+ )
+}
+
+export function SwitchField({ className, ...props }: { className?: string } & Omit) {
+ return (
+ [data-slot=control]]:col-start-2 [&>[data-slot=control]]:self-center',
+ // Label layout
+ '[&>[data-slot=label]]:col-start-1 [&>[data-slot=label]]:row-start-1 [&>[data-slot=label]]:justify-self-start',
+ // Description layout
+ '[&>[data-slot=description]]:col-start-1 [&>[data-slot=description]]:row-start-2',
+ // With description
+ '[&_[data-slot=label]]:has-[[data-slot=description]]:font-medium'
+ )}
+ />
+ )
+}
+
+const colors = {
+ 'dark/zinc': [
+ '[--switch-bg-ring:theme(colors.zinc.950/90%)] [--switch-bg:theme(colors.zinc.900)] dark:[--switch-bg-ring:transparent] dark:[--switch-bg:theme(colors.white/25%)]',
+ '[--switch-ring:theme(colors.zinc.950/90%)] [--switch-shadow:theme(colors.black/10%)] [--switch:white] dark:[--switch-ring:theme(colors.zinc.700/90%)]',
+ ],
+ 'dark/white': [
+ '[--switch-bg-ring:theme(colors.zinc.950/90%)] [--switch-bg:theme(colors.zinc.900)] dark:[--switch-bg-ring:transparent] dark:[--switch-bg:theme(colors.white)]',
+ '[--switch-ring:theme(colors.zinc.950/90%)] [--switch-shadow:theme(colors.black/10%)] [--switch:white] dark:[--switch-ring:transparent] dark:[--switch:theme(colors.zinc.900)]',
+ ],
+ dark: [
+ '[--switch-bg-ring:theme(colors.zinc.950/90%)] [--switch-bg:theme(colors.zinc.900)] dark:[--switch-bg-ring:theme(colors.white/15%)]',
+ '[--switch-ring:theme(colors.zinc.950/90%)] [--switch-shadow:theme(colors.black/10%)] [--switch:white]',
+ ],
+ zinc: [
+ '[--switch-bg-ring:theme(colors.zinc.700/90%)] [--switch-bg:theme(colors.zinc.600)] dark:[--switch-bg-ring:transparent]',
+ '[--switch-shadow:theme(colors.black/10%)] [--switch:white] [--switch-ring:theme(colors.zinc.700/90%)]',
+ ],
+ white: [
+ '[--switch-bg-ring:theme(colors.black/15%)] [--switch-bg:white] dark:[--switch-bg-ring:transparent]',
+ '[--switch-shadow:theme(colors.black/10%)] [--switch-ring:transparent] [--switch:theme(colors.zinc.950)]',
+ ],
+ red: [
+ '[--switch-bg-ring:theme(colors.red.700/90%)] [--switch-bg:theme(colors.red.600)] dark:[--switch-bg-ring:transparent]',
+ '[--switch:white] [--switch-ring:theme(colors.red.700/90%)] [--switch-shadow:theme(colors.red.900/20%)]',
+ ],
+ orange: [
+ '[--switch-bg-ring:theme(colors.orange.600/90%)] [--switch-bg:theme(colors.orange.500)] dark:[--switch-bg-ring:transparent]',
+ '[--switch:white] [--switch-ring:theme(colors.orange.600/90%)] [--switch-shadow:theme(colors.orange.900/20%)]',
+ ],
+ amber: [
+ '[--switch-bg-ring:theme(colors.amber.500/80%)] [--switch-bg:theme(colors.amber.400)] dark:[--switch-bg-ring:transparent]',
+ '[--switch-ring:transparent] [--switch-shadow:transparent] [--switch:theme(colors.amber.950)]',
+ ],
+ yellow: [
+ '[--switch-bg-ring:theme(colors.yellow.400/80%)] [--switch-bg:theme(colors.yellow.300)] dark:[--switch-bg-ring:transparent]',
+ '[--switch-ring:transparent] [--switch-shadow:transparent] [--switch:theme(colors.yellow.950)]',
+ ],
+ lime: [
+ '[--switch-bg-ring:theme(colors.lime.400/80%)] [--switch-bg:theme(colors.lime.300)] dark:[--switch-bg-ring:transparent]',
+ '[--switch-ring:transparent] [--switch-shadow:transparent] [--switch:theme(colors.lime.950)]',
+ ],
+ green: [
+ '[--switch-bg-ring:theme(colors.green.700/90%)] [--switch-bg:theme(colors.green.600)] dark:[--switch-bg-ring:transparent]',
+ '[--switch:white] [--switch-ring:theme(colors.green.700/90%)] [--switch-shadow:theme(colors.green.900/20%)]',
+ ],
+ emerald: [
+ '[--switch-bg-ring:theme(colors.emerald.600/90%)] [--switch-bg:theme(colors.emerald.500)] dark:[--switch-bg-ring:transparent]',
+ '[--switch:white] [--switch-ring:theme(colors.emerald.600/90%)] [--switch-shadow:theme(colors.emerald.900/20%)]',
+ ],
+ teal: [
+ '[--switch-bg-ring:theme(colors.teal.700/90%)] [--switch-bg:theme(colors.teal.600)] dark:[--switch-bg-ring:transparent]',
+ '[--switch:white] [--switch-ring:theme(colors.teal.700/90%)] [--switch-shadow:theme(colors.teal.900/20%)]',
+ ],
+ cyan: [
+ '[--switch-bg-ring:theme(colors.cyan.400/80%)] [--switch-bg:theme(colors.cyan.300)] dark:[--switch-bg-ring:transparent]',
+ '[--switch-ring:transparent] [--switch-shadow:transparent] [--switch:theme(colors.cyan.950)]',
+ ],
+ sky: [
+ '[--switch-bg-ring:theme(colors.sky.600/80%)] [--switch-bg:theme(colors.sky.500)] dark:[--switch-bg-ring:transparent]',
+ '[--switch:white] [--switch-ring:theme(colors.sky.600/80%)] [--switch-shadow:theme(colors.sky.900/20%)]',
+ ],
+ blue: [
+ '[--switch-bg-ring:theme(colors.blue.700/90%)] [--switch-bg:theme(colors.blue.600)] dark:[--switch-bg-ring:transparent]',
+ '[--switch:white] [--switch-ring:theme(colors.blue.700/90%)] [--switch-shadow:theme(colors.blue.900/20%)]',
+ ],
+ indigo: [
+ '[--switch-bg-ring:theme(colors.indigo.600/90%)] [--switch-bg:theme(colors.indigo.500)] dark:[--switch-bg-ring:transparent]',
+ '[--switch:white] [--switch-ring:theme(colors.indigo.600/90%)] [--switch-shadow:theme(colors.indigo.900/20%)]',
+ ],
+ violet: [
+ '[--switch-bg-ring:theme(colors.violet.600/90%)] [--switch-bg:theme(colors.violet.500)] dark:[--switch-bg-ring:transparent]',
+ '[--switch:white] [--switch-ring:theme(colors.violet.600/90%)] [--switch-shadow:theme(colors.violet.900/20%)]',
+ ],
+ purple: [
+ '[--switch-bg-ring:theme(colors.purple.600/90%)] [--switch-bg:theme(colors.purple.500)] dark:[--switch-bg-ring:transparent]',
+ '[--switch:white] [--switch-ring:theme(colors.purple.600/90%)] [--switch-shadow:theme(colors.purple.900/20%)]',
+ ],
+ fuchsia: [
+ '[--switch-bg-ring:theme(colors.fuchsia.600/90%)] [--switch-bg:theme(colors.fuchsia.500)] dark:[--switch-bg-ring:transparent]',
+ '[--switch:white] [--switch-ring:theme(colors.fuchsia.600/90%)] [--switch-shadow:theme(colors.fuchsia.900/20%)]',
+ ],
+ pink: [
+ '[--switch-bg-ring:theme(colors.pink.600/90%)] [--switch-bg:theme(colors.pink.500)] dark:[--switch-bg-ring:transparent]',
+ '[--switch:white] [--switch-ring:theme(colors.pink.600/90%)] [--switch-shadow:theme(colors.pink.900/20%)]',
+ ],
+ rose: [
+ '[--switch-bg-ring:theme(colors.rose.600/90%)] [--switch-bg:theme(colors.rose.500)] dark:[--switch-bg-ring:transparent]',
+ '[--switch:white] [--switch-ring:theme(colors.rose.600/90%)] [--switch-shadow:theme(colors.rose.900/20%)]',
+ ],
+}
+
+type Color = keyof typeof colors
+
+export function Switch({
+ color = 'dark/zinc',
+ className,
+ ...props
+}: {
+ color?: Color
+ className?: string
+} & Omit) {
+ return (
+
+
+
+ )
+}
diff --git a/src/components/ui/table.tsx b/src/components/ui/table.tsx
new file mode 100644
index 0000000..7d98812
--- /dev/null
+++ b/src/components/ui/table.tsx
@@ -0,0 +1,124 @@
+'use client'
+
+import clsx from 'clsx'
+import type React from 'react'
+import { createContext, useContext, useState } from 'react'
+import { Link } from './link'
+
+const TableContext = createContext<{ bleed: boolean; dense: boolean; grid: boolean; striped: boolean }>({
+ bleed: false,
+ dense: false,
+ grid: false,
+ striped: false,
+})
+
+export function Table({
+ bleed = false,
+ dense = false,
+ grid = false,
+ striped = false,
+ className,
+ children,
+ ...props
+}: { bleed?: boolean; dense?: boolean; grid?: boolean; striped?: boolean } & React.ComponentPropsWithoutRef<'div'>) {
+ return (
+ }>
+
+
+ )
+}
+
+export function TableHead({ className, ...props }: React.ComponentPropsWithoutRef<'thead'>) {
+ return
+}
+
+export function TableBody(props: React.ComponentPropsWithoutRef<'tbody'>) {
+ return
+}
+
+const TableRowContext = createContext<{ href?: string; target?: string; title?: string }>({
+ href: undefined,
+ target: undefined,
+ title: undefined,
+})
+
+export function TableRow({
+ href,
+ target,
+ title,
+ className,
+ ...props
+}: { href?: string; target?: string; title?: string } & React.ComponentPropsWithoutRef<'tr'>) {
+ let { striped } = useContext(TableContext)
+
+ return (
+ }>
+
+
+ )
+}
+
+export function TableHeader({ className, ...props }: React.ComponentPropsWithoutRef<'th'>) {
+ let { bleed, grid } = useContext(TableContext)
+
+ return (
+ |
+ )
+}
+
+export function TableCell({ className, children, ...props }: React.ComponentPropsWithoutRef<'td'>) {
+ let { bleed, dense, grid, striped } = useContext(TableContext)
+ let { href, target, title } = useContext(TableRowContext)
+ let [cellRef, setCellRef] = useState(null)
+
+ return (
+
+ {href && (
+
+ )}
+ {children}
+ |
+ )
+}
diff --git a/src/components/ui/text.tsx b/src/components/ui/text.tsx
new file mode 100644
index 0000000..906f7a6
--- /dev/null
+++ b/src/components/ui/text.tsx
@@ -0,0 +1,40 @@
+import clsx from 'clsx'
+import { Link } from './link'
+
+export function Text({ className, ...props }: React.ComponentPropsWithoutRef<'p'>) {
+ return (
+
+ )
+}
+
+export function TextLink({ className, ...props }: React.ComponentPropsWithoutRef) {
+ return (
+
+ )
+}
+
+export function Strong({ className, ...props }: React.ComponentPropsWithoutRef<'strong'>) {
+ return
+}
+
+export function Code({ className, ...props }: React.ComponentPropsWithoutRef<'code'>) {
+ return (
+
+ )
+}
diff --git a/src/components/ui/textarea.tsx b/src/components/ui/textarea.tsx
new file mode 100644
index 0000000..7ca2593
--- /dev/null
+++ b/src/components/ui/textarea.tsx
@@ -0,0 +1,54 @@
+import * as Headless from '@headlessui/react'
+import clsx from 'clsx'
+import React, { forwardRef } from 'react'
+
+export const Textarea = forwardRef(function Textarea(
+ {
+ className,
+ resizable = true,
+ ...props
+ }: { className?: string; resizable?: boolean } & Omit,
+ ref: React.ForwardedRef
+) {
+ return (
+
+
+
+ )
+})
diff --git a/src/index.css b/src/index.css
index 6119ad9..b5c61c9 100644
--- a/src/index.css
+++ b/src/index.css
@@ -1,68 +1,3 @@
-:root {
- font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
- line-height: 1.5;
- font-weight: 400;
-
- color-scheme: light dark;
- color: rgba(255, 255, 255, 0.87);
- background-color: #242424;
-
- font-synthesis: none;
- text-rendering: optimizeLegibility;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
-}
-
-a {
- font-weight: 500;
- color: #646cff;
- text-decoration: inherit;
-}
-a:hover {
- color: #535bf2;
-}
-
-body {
- margin: 0;
- display: flex;
- place-items: center;
- min-width: 320px;
- min-height: 100vh;
-}
-
-h1 {
- font-size: 3.2em;
- line-height: 1.1;
-}
-
-button {
- border-radius: 8px;
- border: 1px solid transparent;
- padding: 0.6em 1.2em;
- font-size: 1em;
- font-weight: 500;
- font-family: inherit;
- background-color: #1a1a1a;
- cursor: pointer;
- transition: border-color 0.25s;
-}
-button:hover {
- border-color: #646cff;
-}
-button:focus,
-button:focus-visible {
- outline: 4px auto -webkit-focus-ring-color;
-}
-
-@media (prefers-color-scheme: light) {
- :root {
- color: #213547;
- background-color: #ffffff;
- }
- a:hover {
- color: #747bff;
- }
- button {
- background-color: #f9f9f9;
- }
-}
+@tailwind base;
+@tailwind components;
+@tailwind utilities;