diff --git a/lunar/.eslintrc.json b/lunar/.eslintrc.json new file mode 100644 index 000000000..bffb357a7 --- /dev/null +++ b/lunar/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "next/core-web-vitals" +} diff --git a/lunar/.gitignore b/lunar/.gitignore new file mode 100644 index 000000000..435b013b1 --- /dev/null +++ b/lunar/.gitignore @@ -0,0 +1,39 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js +.yarn/install-state.gz + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +# .env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts + +package-lock.json +pnpm-lock.yaml \ No newline at end of file diff --git a/lunar/README.md b/lunar/README.md new file mode 100644 index 000000000..9cc5ec259 --- /dev/null +++ b/lunar/README.md @@ -0,0 +1 @@ +## Lunar Module \ No newline at end of file diff --git a/lunar/next.config.js b/lunar/next.config.js new file mode 100644 index 000000000..568c27213 --- /dev/null +++ b/lunar/next.config.js @@ -0,0 +1,47 @@ +/** @type {import('next').NextConfig} */ + +const nextConfig = { + reactStrictMode: true, + transpilePackages: [ + // antd & deps + // https://github.com/vercel/next.js/issues/65707 + // https://github.com/ant-design/ant-design/issues/46053#issuecomment-1905553667 + "@ant-design", + "@rc-component", + "antd", + "rc-cascader", + "rc-checkbox", + "rc-collapse", + "rc-dialog", + "rc-drawer", + "rc-dropdown", + "rc-field-form", + "rc-image", + "rc-input", + "rc-input-number", + "rc-mentions", + "rc-menu", + "rc-motion", + "rc-notification", + "rc-pagination", + "rc-picker", + "rc-progress", + "rc-rate", + "rc-resize-observer", + "rc-segmented", + "rc-select", + "rc-slider", + "rc-steps", + "rc-switch", + "rc-table", + "rc-tabs", + "rc-textarea", + "rc-tooltip", + "rc-tree", + "rc-tree-select", + "rc-upload", + "rc-util", + ], +} + +module.exports = nextConfig diff --git a/lunar/package.json b/lunar/package.json new file mode 100644 index 000000000..cf08f9815 --- /dev/null +++ b/lunar/package.json @@ -0,0 +1,34 @@ +{ + "name": "moon", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "@ant-design/icons": "^5.3.7", + "@ant-design/nextjs-registry": "^1.0.0", + "@headlessui/react": "^2.1.2", + "@headlessui/tailwindcss": "^0.2.1", + "@heroicons/react": "^2.1.3", + "@tailwindcss/forms": "^0.5.7", + "clsx": "^2.1.1", + "framer-motion": "^11.3.2", + "next": "14.2.3", + "react": "^18", + "react-dom": "^18" + }, + "devDependencies": { + "@types/node": "^20", + "@types/react": "^18", + "@types/react-dom": "^18", + "autoprefixer": "^10.4.16", + "eslint": "^8", + "eslint-config-next": "14.0.3", + "postcss": "^8.4.31", + "typescript": "^5" + } +} diff --git a/lunar/postcss.config.js b/lunar/postcss.config.js new file mode 100644 index 000000000..12a703d90 --- /dev/null +++ b/lunar/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/lunar/public/images/mega.png b/lunar/public/images/mega.png new file mode 100644 index 000000000..4732fb3ba Binary files /dev/null and b/lunar/public/images/mega.png differ diff --git a/lunar/public/images/megaLogo.png b/lunar/public/images/megaLogo.png new file mode 100644 index 000000000..930c34d91 Binary files /dev/null and b/lunar/public/images/megaLogo.png differ diff --git a/lunar/public/images/megaTitle.png b/lunar/public/images/megaTitle.png new file mode 100644 index 000000000..8a42a49a1 Binary files /dev/null and b/lunar/public/images/megaTitle.png differ diff --git a/lunar/src/app/application-layout.tsx b/lunar/src/app/application-layout.tsx new file mode 100644 index 000000000..12458c396 --- /dev/null +++ b/lunar/src/app/application-layout.tsx @@ -0,0 +1,182 @@ +'use client' + +import { Avatar } from '@/components/catalyst/avatar' +import { + Dropdown, + DropdownButton, + DropdownDivider, + DropdownItem, + DropdownLabel, + DropdownMenu, +} from '@/components/catalyst/dropdown' +import { Navbar, NavbarItem, NavbarSection, NavbarSpacer } from '@/components/catalyst/navbar' +import { + Sidebar, + SidebarBody, + SidebarFooter, + SidebarHeader, + SidebarHeading, + SidebarItem, + SidebarLabel, + SidebarSection, + SidebarSpacer, +} from '@/components/catalyst/sidebar' +import { SidebarLayout } from '@/components/catalyst/sidebar-layout' +import { + ArrowRightStartOnRectangleIcon, + ChevronDownIcon, + ChevronUpIcon, + Cog8ToothIcon, + LightBulbIcon, + PlusIcon, + ShieldCheckIcon, + UserCircleIcon, +} from '@heroicons/react/16/solid' +import { + Cog6ToothIcon, + HomeIcon, + QuestionMarkCircleIcon, + SparklesIcon, + Square2StackIcon, + TicketIcon, +} from '@heroicons/react/20/solid' +import { usePathname } from 'next/navigation' + +function AccountDropdownMenu({ anchor }: { anchor: 'top start' | 'bottom end' }) { + return ( + + + + My account + + + + + Privacy policy + + + + Share feedback + + + + + Sign out + + + ) +} + +export function ApplicationLayout({ + // events, + children, +}: { + // events: Awaited> + children: React.ReactNode +}) { + let pathname = usePathname() + + return ( + + + + + + + + + + + + } + sidebar={ + + + + + + Mega + + + + + + Settings + + + + + Admin + + + + Big Events + + + + + New team… + + + + + + + + + + Home + + + + Issue + + + + Merge Request + + + + Settings + + + + + + + + Support + + + + Changelog + + + + + + + + + + + Admin + + Admin@mega.com + + + + + + + + + + } + > + {children} + + ) +} diff --git a/lunar/src/app/layout.tsx b/lunar/src/app/layout.tsx new file mode 100644 index 000000000..940c2af4f --- /dev/null +++ b/lunar/src/app/layout.tsx @@ -0,0 +1,24 @@ +'use client' +import '@/styles/globals.css' +import type React from 'react' +import { ApplicationLayout } from './application-layout' +import { AntdRegistry } from '@ant-design/nextjs-registry'; + + +export default function Layout({ children }: { children: React.ReactNode }) { + + return ( + + + + + + {children} + + + + ) +} diff --git a/lunar/src/app/page.tsx b/lunar/src/app/page.tsx new file mode 100644 index 000000000..40215e9e9 --- /dev/null +++ b/lunar/src/app/page.tsx @@ -0,0 +1,94 @@ +'use client' + +import React from 'react'; +import { Space, Table, Tag } from 'antd'; +import type { TableProps } from 'antd'; + +interface DataType { + key: string; + name: string; + age: number; + address: string; + tags: string[]; +} + +const columns: TableProps['columns'] = [ + { + title: 'Name', + dataIndex: 'name', + key: 'name', + render: (text) => {text}, + }, + { + title: 'Age', + dataIndex: 'age', + key: 'age', + }, + { + title: 'Address', + dataIndex: 'address', + key: 'address', + }, + { + title: 'Tags', + key: 'tags', + dataIndex: 'tags', + render: (_, { tags }) => ( + <> + {tags.map((tag) => { + let color = tag.length > 5 ? 'geekblue' : 'green'; + if (tag === 'loser') { + color = 'volcano'; + } + return ( + + {tag.toUpperCase()} + + ); + })} + + ), + }, + { + title: 'Action', + key: 'action', + render: (_, record) => ( + + Invite {record.name} + Delete + + ), + }, +]; + +const data: DataType[] = [ + { + key: '1', + name: 'John Brown', + age: 32, + address: 'New York No. 1 Lake Park', + tags: ['nice', 'developer'], + }, + { + key: '2', + name: 'Jim Green', + age: 42, + address: 'London No. 1 Lake Park', + tags: ['loser'], + }, + { + key: '3', + name: 'Joe Black', + age: 32, + address: 'Sydney No. 1 Lake Park', + tags: ['cool', 'teacher'], + }, +]; + +export default function HomePage() { + return ( +
+ + + ); +} \ No newline at end of file diff --git a/lunar/src/components/catalyst/avatar.tsx b/lunar/src/components/catalyst/avatar.tsx new file mode 100644 index 000000000..ed21e313e --- /dev/null +++ b/lunar/src/components/catalyst/avatar.tsx @@ -0,0 +1,84 @@ +import * as Headless from '@headlessui/react' +import clsx from 'clsx' +import React, { forwardRef } from 'react' +import { TouchTarget } from './button' +import { Link } from './link' + +type AvatarProps = { + src?: string | null + square?: boolean + initials?: string + alt?: string + className?: string +} + +export function Avatar({ + src = null, + square = false, + initials, + alt = '', + className, + ...props +}: AvatarProps & React.ComponentPropsWithoutRef<'span'>) { + return ( + + {initials && ( + + {alt && {alt}} + + {initials} + + + )} + {src && {alt}} + + ) +} + +export const AvatarButton = forwardRef(function AvatarButton( + { + src, + square = false, + initials, + alt, + className, + ...props + }: AvatarProps & + (Omit | Omit, 'className'>), + ref: React.ForwardedRef +) { + let classes = clsx( + className, + square ? 'rounded-[20%]' : 'rounded-full', + 'relative inline-grid focus:outline-none data-[focus]:outline data-[focus]:outline-2 data-[focus]:outline-offset-2 data-[focus]:outline-blue-500' + ) + + return 'href' in props ? ( + }> + + + + + ) : ( + + + + + + ) +}) diff --git a/lunar/src/components/catalyst/button.tsx b/lunar/src/components/catalyst/button.tsx new file mode 100644 index 000000000..215398119 --- /dev/null +++ b/lunar/src/components/catalyst/button.tsx @@ -0,0 +1,204 @@ +import * as Headless from '@headlessui/react' +import clsx from 'clsx' +import React, { forwardRef } from 'react' +import { Link } from './link' + +const styles = { + base: [ + // Base + 'relative isolate inline-flex items-center justify-center gap-x-2 rounded-lg border text-base/6 font-semibold', + // Sizing + 'px-[calc(theme(spacing[3.5])-1px)] py-[calc(theme(spacing[2.5])-1px)] sm:px-[calc(theme(spacing.3)-1px)] sm:py-[calc(theme(spacing[1.5])-1px)] sm:text-sm/6', + // Focus + 'focus:outline-none data-[focus]:outline data-[focus]:outline-2 data-[focus]:outline-offset-2 data-[focus]:outline-blue-500', + // Disabled + 'data-[disabled]:opacity-50', + // Icon + '[&>[data-slot=icon]]:-mx-0.5 [&>[data-slot=icon]]:my-0.5 [&>[data-slot=icon]]:size-5 [&>[data-slot=icon]]:shrink-0 [&>[data-slot=icon]]:text-[--btn-icon] [&>[data-slot=icon]]:sm:my-1 [&>[data-slot=icon]]:sm:size-4 forced-colors:[--btn-icon:ButtonText] forced-colors:data-[hover]:[--btn-icon:ButtonText]', + ], + solid: [ + // Optical border, implemented as the button background to avoid corner artifacts + 'border-transparent bg-[--btn-border]', + // Dark mode: border is rendered on `after` so background is set to button background + 'dark:bg-[--btn-bg]', + // Button background, implemented as foreground layer to stack on top of pseudo-border layer + 'before:absolute before:inset-0 before:-z-10 before:rounded-[calc(theme(borderRadius.lg)-1px)] before:bg-[--btn-bg]', + // Drop shadow, applied to the inset `before` layer so it blends with the border + 'before:shadow', + // Background color is moved to control and shadow is removed in dark mode so hide `before` pseudo + 'dark:before:hidden', + // Dark mode: Subtle white outline is applied using a border + 'dark:border-white/5', + // Shim/overlay, inset to match button foreground and used for hover state + highlight shadow + 'after:absolute after:inset-0 after:-z-10 after:rounded-[calc(theme(borderRadius.lg)-1px)]', + // Inner highlight shadow + 'after:shadow-[shadow:inset_0_1px_theme(colors.white/15%)]', + // White overlay on hover + 'after:data-[active]:bg-[--btn-hover-overlay] after:data-[hover]:bg-[--btn-hover-overlay]', + // Dark mode: `after` layer expands to cover entire button + 'dark:after:-inset-px dark:after:rounded-lg', + // Disabled + 'before:data-[disabled]:shadow-none after:data-[disabled]:shadow-none', + ], + outline: [ + // Base + 'border-zinc-950/10 text-zinc-950 data-[active]:bg-zinc-950/[2.5%] data-[hover]:bg-zinc-950/[2.5%]', + // Dark mode + 'dark:border-white/15 dark:text-white dark:[--btn-bg:transparent] dark:data-[active]:bg-white/5 dark:data-[hover]:bg-white/5', + // Icon + '[--btn-icon:theme(colors.zinc.500)] data-[active]:[--btn-icon:theme(colors.zinc.700)] data-[hover]:[--btn-icon:theme(colors.zinc.700)] dark:data-[active]:[--btn-icon:theme(colors.zinc.400)] dark:data-[hover]:[--btn-icon:theme(colors.zinc.400)]', + ], + plain: [ + // Base + 'border-transparent text-zinc-950 data-[active]:bg-zinc-950/5 data-[hover]:bg-zinc-950/5', + // Dark mode + 'dark:text-white dark:data-[active]:bg-white/10 dark:data-[hover]:bg-white/10', + // Icon + '[--btn-icon:theme(colors.zinc.500)] data-[active]:[--btn-icon:theme(colors.zinc.700)] data-[hover]:[--btn-icon:theme(colors.zinc.700)] dark:[--btn-icon:theme(colors.zinc.500)] dark:data-[active]:[--btn-icon:theme(colors.zinc.400)] dark:data-[hover]:[--btn-icon:theme(colors.zinc.400)]', + ], + colors: { + 'dark/zinc': [ + 'text-white [--btn-bg:theme(colors.zinc.900)] [--btn-border:theme(colors.zinc.950/90%)] [--btn-hover-overlay:theme(colors.white/10%)]', + 'dark:text-white dark:[--btn-bg:theme(colors.zinc.600)] dark:[--btn-hover-overlay:theme(colors.white/5%)]', + '[--btn-icon:theme(colors.zinc.400)] data-[active]:[--btn-icon:theme(colors.zinc.300)] data-[hover]:[--btn-icon:theme(colors.zinc.300)]', + ], + light: [ + 'text-zinc-950 [--btn-bg:white] [--btn-border:theme(colors.zinc.950/10%)] [--btn-hover-overlay:theme(colors.zinc.950/2.5%)] data-[active]:[--btn-border:theme(colors.zinc.950/15%)] data-[hover]:[--btn-border:theme(colors.zinc.950/15%)]', + 'dark:text-white dark:[--btn-hover-overlay:theme(colors.white/5%)] dark:[--btn-bg:theme(colors.zinc.800)]', + '[--btn-icon:theme(colors.zinc.500)] data-[active]:[--btn-icon:theme(colors.zinc.700)] data-[hover]:[--btn-icon:theme(colors.zinc.700)] dark:[--btn-icon:theme(colors.zinc.500)] dark:data-[active]:[--btn-icon:theme(colors.zinc.400)] dark:data-[hover]:[--btn-icon:theme(colors.zinc.400)]', + ], + 'dark/white': [ + 'text-white [--btn-bg:theme(colors.zinc.900)] [--btn-border:theme(colors.zinc.950/90%)] [--btn-hover-overlay:theme(colors.white/10%)]', + 'dark:text-zinc-950 dark:[--btn-bg:white] dark:[--btn-hover-overlay:theme(colors.zinc.950/5%)]', + '[--btn-icon:theme(colors.zinc.400)] data-[active]:[--btn-icon:theme(colors.zinc.300)] data-[hover]:[--btn-icon:theme(colors.zinc.300)] dark:[--btn-icon:theme(colors.zinc.500)] dark:data-[active]:[--btn-icon:theme(colors.zinc.400)] dark:data-[hover]:[--btn-icon:theme(colors.zinc.400)]', + ], + dark: [ + 'text-white [--btn-bg:theme(colors.zinc.900)] [--btn-border:theme(colors.zinc.950/90%)] [--btn-hover-overlay:theme(colors.white/10%)]', + 'dark:[--btn-hover-overlay:theme(colors.white/5%)] dark:[--btn-bg:theme(colors.zinc.800)]', + '[--btn-icon:theme(colors.zinc.400)] data-[active]:[--btn-icon:theme(colors.zinc.300)] data-[hover]:[--btn-icon:theme(colors.zinc.300)]', + ], + white: [ + 'text-zinc-950 [--btn-bg:white] [--btn-border:theme(colors.zinc.950/10%)] [--btn-hover-overlay:theme(colors.zinc.950/2.5%)] data-[active]:[--btn-border:theme(colors.zinc.950/15%)] data-[hover]:[--btn-border:theme(colors.zinc.950/15%)]', + 'dark:[--btn-hover-overlay:theme(colors.zinc.950/5%)]', + '[--btn-icon:theme(colors.zinc.400)] data-[active]:[--btn-icon:theme(colors.zinc.500)] data-[hover]:[--btn-icon:theme(colors.zinc.500)]', + ], + zinc: [ + 'text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.zinc.600)] [--btn-border:theme(colors.zinc.700/90%)]', + 'dark:[--btn-hover-overlay:theme(colors.white/5%)]', + '[--btn-icon:theme(colors.zinc.400)] data-[active]:[--btn-icon:theme(colors.zinc.300)] data-[hover]:[--btn-icon:theme(colors.zinc.300)]', + ], + indigo: [ + 'text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.indigo.500)] [--btn-border:theme(colors.indigo.600/90%)]', + '[--btn-icon:theme(colors.indigo.300)] data-[active]:[--btn-icon:theme(colors.indigo.200)] data-[hover]:[--btn-icon:theme(colors.indigo.200)]', + ], + cyan: [ + 'text-cyan-950 [--btn-bg:theme(colors.cyan.300)] [--btn-border:theme(colors.cyan.400/80%)] [--btn-hover-overlay:theme(colors.white/25%)]', + '[--btn-icon:theme(colors.cyan.500)]', + ], + red: [ + 'text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.red.600)] [--btn-border:theme(colors.red.700/90%)]', + '[--btn-icon:theme(colors.red.300)] data-[active]:[--btn-icon:theme(colors.red.200)] data-[hover]:[--btn-icon:theme(colors.red.200)]', + ], + orange: [ + 'text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.orange.500)] [--btn-border:theme(colors.orange.600/90%)]', + '[--btn-icon:theme(colors.orange.300)] data-[active]:[--btn-icon:theme(colors.orange.200)] data-[hover]:[--btn-icon:theme(colors.orange.200)]', + ], + amber: [ + 'text-amber-950 [--btn-hover-overlay:theme(colors.white/25%)] [--btn-bg:theme(colors.amber.400)] [--btn-border:theme(colors.amber.500/80%)]', + '[--btn-icon:theme(colors.amber.600)]', + ], + yellow: [ + 'text-yellow-950 [--btn-hover-overlay:theme(colors.white/25%)] [--btn-bg:theme(colors.yellow.300)] [--btn-border:theme(colors.yellow.400/80%)]', + '[--btn-icon:theme(colors.yellow.600)] data-[active]:[--btn-icon:theme(colors.yellow.700)] data-[hover]:[--btn-icon:theme(colors.yellow.700)]', + ], + lime: [ + 'text-lime-950 [--btn-hover-overlay:theme(colors.white/25%)] [--btn-bg:theme(colors.lime.300)] [--btn-border:theme(colors.lime.400/80%)]', + '[--btn-icon:theme(colors.lime.600)] data-[active]:[--btn-icon:theme(colors.lime.700)] data-[hover]:[--btn-icon:theme(colors.lime.700)]', + ], + green: [ + 'text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.green.600)] [--btn-border:theme(colors.green.700/90%)]', + '[--btn-icon:theme(colors.white/60%)] data-[active]:[--btn-icon:theme(colors.white/80%)] data-[hover]:[--btn-icon:theme(colors.white/80%)]', + ], + emerald: [ + 'text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.emerald.600)] [--btn-border:theme(colors.emerald.700/90%)]', + '[--btn-icon:theme(colors.white/60%)] data-[active]:[--btn-icon:theme(colors.white/80%)] data-[hover]:[--btn-icon:theme(colors.white/80%)]', + ], + teal: [ + 'text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.teal.600)] [--btn-border:theme(colors.teal.700/90%)]', + '[--btn-icon:theme(colors.white/60%)] data-[active]:[--btn-icon:theme(colors.white/80%)] data-[hover]:[--btn-icon:theme(colors.white/80%)]', + ], + sky: [ + 'text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.sky.500)] [--btn-border:theme(colors.sky.600/80%)]', + '[--btn-icon:theme(colors.white/60%)] data-[active]:[--btn-icon:theme(colors.white/80%)] data-[hover]:[--btn-icon:theme(colors.white/80%)]', + ], + blue: [ + 'text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.blue.600)] [--btn-border:theme(colors.blue.700/90%)]', + '[--btn-icon:theme(colors.blue.400)] data-[active]:[--btn-icon:theme(colors.blue.300)] data-[hover]:[--btn-icon:theme(colors.blue.300)]', + ], + violet: [ + 'text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.violet.500)] [--btn-border:theme(colors.violet.600/90%)]', + '[--btn-icon:theme(colors.violet.300)] data-[active]:[--btn-icon:theme(colors.violet.200)] data-[hover]:[--btn-icon:theme(colors.violet.200)]', + ], + purple: [ + 'text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.purple.500)] [--btn-border:theme(colors.purple.600/90%)]', + '[--btn-icon:theme(colors.purple.300)] data-[active]:[--btn-icon:theme(colors.purple.200)] data-[hover]:[--btn-icon:theme(colors.purple.200)]', + ], + fuchsia: [ + 'text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.fuchsia.500)] [--btn-border:theme(colors.fuchsia.600/90%)]', + '[--btn-icon:theme(colors.fuchsia.300)] data-[active]:[--btn-icon:theme(colors.fuchsia.200)] data-[hover]:[--btn-icon:theme(colors.fuchsia.200)]', + ], + pink: [ + 'text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.pink.500)] [--btn-border:theme(colors.pink.600/90%)]', + '[--btn-icon:theme(colors.pink.300)] data-[active]:[--btn-icon:theme(colors.pink.200)] data-[hover]:[--btn-icon:theme(colors.pink.200)]', + ], + rose: [ + 'text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.rose.500)] [--btn-border:theme(colors.rose.600/90%)]', + '[--btn-icon:theme(colors.rose.300)] data-[active]:[--btn-icon:theme(colors.rose.200)] data-[hover]:[--btn-icon:theme(colors.rose.200)]', + ], + }, +} + +type ButtonProps = ( + | { color?: keyof typeof styles.colors; outline?: never; plain?: never } + | { color?: never; outline: true; plain?: never } + | { color?: never; outline?: never; plain: true } +) & { className?: string; children: React.ReactNode } & ( + | Omit + | Omit, 'className'> + ) + +export const Button = forwardRef(function Button( + { color, outline, plain, className, children, ...props }: ButtonProps, + ref: React.ForwardedRef +) { + let classes = clsx( + className, + styles.base, + outline ? styles.outline : plain ? styles.plain : clsx(styles.solid, styles.colors[color ?? 'dark/zinc']) + ) + + return 'href' in props ? ( + }> + {children} + + ) : ( + + {children} + + ) +}) + +/** + * Expand the hit area to at least 44×44px on touch devices + */ +export function TouchTarget({ children }: { children: React.ReactNode }) { + return ( + <> +