diff --git a/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/api-keys/page-client.tsx b/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/api-keys/page-client.tsx index 09f2b927d..568e711a0 100644 --- a/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/api-keys/page-client.tsx +++ b/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/api-keys/page-client.tsx @@ -11,6 +11,7 @@ import { runAsynchronously } from "@stackframe/stack-shared/dist/utils/promises" import EnvKeys from "@/components/env-keys"; import { SmartLink } from "@/components/smart-link"; import { ApiKeySetFirstView } from "@stackframe/stack"; +import { PageLayout } from "../page-layout"; export default function ApiKeysDashboardClient() { @@ -20,22 +21,15 @@ export default function ApiKeysDashboardClient() { const [isNewApiKeyDialogOpen, setIsNewApiKeyDialogOpen] = useState(false); return ( - <> - - - - API Keys - - setIsNewApiKeyDialogOpen(true)}> - Create new - - - - - - Please note that API keys cannot be viewed anymore after they have been created. If you lose them, you will have to create new ones. - - + setIsNewApiKeyDialogOpen(true)}> + Create new + + } + > setIsNewApiKeyDialogOpen(false)} /> - > + ); } diff --git a/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/auth/route.tsx b/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/auth/route.tsx deleted file mode 100644 index 9f86b91c2..000000000 --- a/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/auth/route.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import { redirectHandler } from "@/route-handlers/redirect-handler"; - -export const GET = redirectHandler("users"); diff --git a/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/environment/page-client.tsx b/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/environment/page-client.tsx index 264df001e..5bddc9d6a 100644 --- a/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/environment/page-client.tsx +++ b/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/environment/page-client.tsx @@ -10,6 +10,7 @@ import { SmartLink } from "@/components/smart-link"; import { SmartSwitch } from "@/components/smart-switch"; import { SimpleCard } from "@/components/simple-card"; import { useAdminApp } from "../use-admin-app"; +import { PageLayout } from "../page-layout"; export default function EnvironmentClient() { const stackAdminApp = useAdminApp(); @@ -21,12 +22,7 @@ export default function EnvironmentClient() { return ( - <> - - Environment - - - + )} - > + ); } diff --git a/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/header.tsx b/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/header.tsx deleted file mode 100644 index 1166ca4f4..000000000 --- a/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/header.tsx +++ /dev/null @@ -1,157 +0,0 @@ -"use client"; - -import { Sheet, SheetProps, Select, Option, SelectOption, Stack, Typography } from "@mui/joy"; -import * as React from 'react'; -import Box from '@mui/joy/Box'; -import IconButton from '@mui/joy/IconButton'; -import { useAdminApp } from './use-admin-app'; -import { redirect, usePathname } from 'next/navigation'; -import { useUser } from '@stackframe/stack'; -import { Icon } from '@/components/icon'; -import Breadcrumbs from '@mui/joy/Breadcrumbs'; -import { NavigationItem } from "./navigation-data"; - - -function ProjectSwitchItem({ label }: { label: string }) { - return ( - - - - {label?.slice(0,1)?.toUpperCase()} - - - {label} - - ); -} - -function ProjectSwitch() { - const stackAdminApp = useAdminApp(); - const user = useUser({ or: 'redirect', projectIdMustMatch: "internal" }); - const projects = user.useOwnedProjects(); - const project = projects.find((project) => project.id === stackAdminApp.projectId); - - const renderValue = (option: SelectOption | null) => { - if (!option || typeof option.label !== 'string') { - return null; - } - - return ( - - - - ); - }; - - return ( - { - redirect(`/projects/${newProjectId}/users`); - }} - > - {projects.map((projectInfo) => ( - - - - ))} - - ); -} - -export function Header(props: SheetProps & { - headerHeight: number, - isCompactMediaQuery: string, - onShowSidebar: () => void, - navigationItems: NavigationItem[], -}) { - const { isCompactMediaQuery, onShowSidebar, navigationItems, headerHeight, ...sheetProps } = props; - const pathname = usePathname(); - - const selectedItem = React.useMemo(() => { - return navigationItems.find((item) => { - if (item.type === 'label') { - return false; - } - return item.regex.test(pathname); - }); - }, [pathname, navigationItems]); - - return ( - - - - - - - - - {selectedItem ? ( - typeof selectedItem.name === 'string' ? ( - {selectedItem.name} - ) : selectedItem.name.map((name, index) => ( - {name} - )) - ) : null} - - - - - - ); -} diff --git a/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/navigation-data.tsx b/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/navigation-data.tsx deleted file mode 100644 index 4ffe4f624..000000000 --- a/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/navigation-data.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import { Icon } from "@/components/icon"; - -type Label = { - name: string, - type: 'label', -}; - -type Item = { - name: string, - href: string, - icon: JSX.Element, - regex: RegExp, - type: 'item', -}; - -type Hidden = { - name: string | string[], - regex: RegExp, - type: 'hidden', -}; - -export type NavigationItem = Label | Item | Hidden; - -export const navigationItems: (Label | Item | Hidden)[] = [ - { - name: "Users", - type: 'label' - }, - { - name: "Users", - href: "/users", - regex: /^\/projects\/[^\/]+\/user\/[^\/]+$/, - icon: , - type: 'item' - }, - { - name: "Auth Methods", - href: "/providers", - regex: /^\/projects\/[^\/]+\/providers$/, - icon: , - type: 'item' - }, - { - name: "Teams", - type: 'label' - }, - { - name: "Teams", - href: "/teams", - regex: /^\/projects\/[^\/]+\/teams$/, - icon: , - type: 'item' - }, - { - name: ["Team", "Members"], - regex: /^\/projects\/[^\/]+\/teams\/[^\/]+$/, - type: "hidden", - }, - { - name: "Permissions", - href: "/team-permissions", - regex: /^\/projects\/[^\/]+\/team-permissions$/, - icon: , - type: 'item' - }, - { - name: "Settings", - type: 'label' - }, - { - name: "Domains & Handlers", - href: "/urls-and-callbacks", - regex: /^\/projects\/[^\/]+\/urls-and-callbacks$/, - icon: , - type: 'item' - }, - { - name: "Team Settings", - href: "/team-settings", - regex: /^\/projects\/[^\/]+\/team-settings$/, - icon: , - type: 'item' - }, - { - name: "Environment", - href: "/environment", - regex: /^\/projects\/[^\/]+\/environment$/, - icon: , - type: 'item' - }, - { - name: "API Keys", - href: "/api-keys", - regex: /^\/projects\/[^\/]+\/api-keys$/, - icon: , - type: 'item' - }, -]; \ No newline at end of file diff --git a/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/page-layout.tsx b/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/page-layout.tsx new file mode 100644 index 000000000..aeb35ddb5 --- /dev/null +++ b/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/page-layout.tsx @@ -0,0 +1,29 @@ +import Typography from "@/components/ui/typography"; + +export function PageLayout(props: { + children: React.ReactNode, + title: string, + description?: string, + actions?: React.ReactNode, +}) { + return ( + + + + + {props.title} + + {props.description && ( + + {props.description} + + )} + + {props.actions} + + + {props.children} + + + ); +} \ No newline at end of file diff --git a/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/providers/page-client.tsx b/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/providers/page-client.tsx index 15c519f2a..e76696aba 100644 --- a/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/providers/page-client.tsx +++ b/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/providers/page-client.tsx @@ -7,6 +7,7 @@ import { SimpleCard } from "@/components/simple-card"; import { useAdminApp } from "../use-admin-app"; import { ProviderAccordion, availableProviders } from "./provider-accordion"; import { OAuthProviderConfigJson } from "@stackframe/stack-shared"; +import { PageLayout } from "../page-layout"; export default function ProvidersClient() { const stackAdminApp = useAdminApp(); @@ -14,10 +15,7 @@ export default function ProvidersClient() { const oauthProviders = project.evaluatedConfig.oauthProviders; return ( - <> - - Auth Methods - + @@ -88,6 +86,6 @@ export default function ProvidersClient() { In order to add a new provider, you can choose to use shared credentials created by us, or create your own OAuth client on the provider's website. - > + ); } diff --git a/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout-old.tsx b/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout-old.tsx deleted file mode 100644 index 3460d72dd..000000000 --- a/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout-old.tsx +++ /dev/null @@ -1,62 +0,0 @@ -"use client";; -import { Drawer, Stack, useTheme } from "@mui/joy"; -import { useState } from "react"; -import { Sidebar } from "./sidebar"; -import { Header } from "./header"; -import { navigationItems } from "./navigation-data"; - -export default function SidebarLayout(props: { children: React.ReactNode, params: { projectId: string } }) { - const theme = useTheme(); - const [isSidebarOpen, setIsSidebarOpen] = useState(false); - - const isCompactMediaQuery = theme.breakpoints.down("md"); - - const headerHeight = 50; - - return ( - <> - - - - setIsSidebarOpen(true)} - /> - - - {props.children} - - - - - setIsSidebarOpen(false)} - > - - - > - ); -} diff --git a/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx b/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx index 689f76bb6..a7dd3f2db 100644 --- a/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx +++ b/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx @@ -34,6 +34,7 @@ import { ProjectSwitcher } from "@/components/project-switcher"; import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet"; import { DropdownMenu } from "@radix-ui/react-dropdown-menu"; import { DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; +import Typography from "@/components/ui/typography"; type Label = { name: string, @@ -163,9 +164,9 @@ export function SidebarContent({ projectId, onNavigate }: { projectId: string, o {navigationItems.map((item, index) => { if (item.type === 'label') { - return + return {item.name} - ; + ; } else if (item.type === 'item') { return @@ -190,8 +191,7 @@ export function SidebarContent({ projectId, onNavigate }: { projectId: string, o /> - {/* */} - + setMode(mode === 'light' ? 'dark' : 'light')} /> @@ -319,7 +319,7 @@ export default function SidebarLayout(props: { projectId: string, children?: Rea Feedback - + {props.children} diff --git a/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/sidebar.tsx b/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/sidebar.tsx deleted file mode 100644 index acddf2b2b..000000000 --- a/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/sidebar.tsx +++ /dev/null @@ -1,157 +0,0 @@ -'use client';; -import * as React from 'react'; -import NextLink from 'next/link'; -import Box from '@mui/joy/Box'; -import Divider from '@mui/joy/Divider'; -import List from '@mui/joy/List'; -import ListItem from '@mui/joy/ListItem'; -import ListItemButton from '@mui/joy/ListItemButton'; -import ListItemContent from '@mui/joy/ListItemContent'; -import Typography from '@mui/joy/Typography'; -import { useAdminApp } from './use-admin-app'; -import { usePathname } from 'next/navigation'; -import { UserButton } from '@stackframe/stack'; -import { useColorScheme, Stack, Sheet } from '@mui/joy'; -import { Icon } from '@/components/icon'; -import { Logo } from '@/components/logo'; -import { NavigationItem } from './navigation-data'; - - -function SidebarItem({ - title, - icon, - href, - target, -}: { - title: string, - icon: React.ReactNode, - href: string, - target?: string, -}) { - const pathname = usePathname(); - const selected = React.useMemo(() => { - return pathname === new URL(href, "https://example.com").pathname; - }, [href, pathname]); - - return ( - theme.vars.radius.sm, - }} - > - - {icon} - - {title} - - {target === "_blank" ? : null} - - - ); -} - -export function Sidebar(props: { - isCompactMediaQuery: string, - headerHeight: number, - navigationItems: NavigationItem[], - mode: 'compact' | 'full', -}) { - const { mode, setMode } = useColorScheme(); - const stackAdminApp = useAdminApp(); - const basePath = `/projects/${stackAdminApp.projectId}`; - - const { headerHeight, navigationItems, ...sheetProps} = props; - return ( - - - - - - - - - - - - {navigationItems.map((item) => ( - item.type === 'item' ? - : - item.type === 'label' ? - - {item.name} - : null - ))} - - } - href={process.env.NEXT_PUBLIC_DOC_URL || ''} - target="_blank" - /> - - - - - - - setMode(mode === 'light' ? 'dark' : 'light')} /> - - - - - ); -} diff --git a/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/site-search.tsx b/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/site-search.tsx deleted file mode 100644 index 42e8b0ebd..000000000 --- a/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/site-search.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { Button, Input, InputProps } from "@mui/joy"; -import { useCallback, useState } from "react"; -import { Icon } from "@/components/icon"; - -export function SiteSearch(props: InputProps) { - const [searchText, setSearchText] = useState(''); - - const { ...inputProps } = props; - - const openSearch = useCallback(() => { - const baseUrl = new URL(process.env.__NEXT_ROUTER_BASEPATH || "", window.location.origin); - // Let's strip away all information but the necessary (Google search doesn't support eg. port number) - const baseSite = `${baseUrl.hostname}${baseUrl.pathname}`; - - window.open(`https://www.google.com/search?q=${encodeURIComponent(`${searchText} site:${baseSite}`)}`, '_blank'); - }, [searchText]); - - return ( - setSearchText(e.target.value)} - placeholder="Search" - startDecorator={ theme.palette.primary[500], - }} />} - endDecorator={ - openSearch()}> - Go - - } - {...inputProps} - sx={{ - '&:not(:focus-within) button:not(:active)': { - display: 'none', - }, - ...inputProps.sx ?? {}, - }} - onKeyDown={e => { - if (e.key === 'Enter') { - openSearch(); - } - }} - /> - ); -} diff --git a/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/team-permissions/page-client.tsx b/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/team-permissions/page-client.tsx index 7cf76977b..0794bba1d 100644 --- a/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/team-permissions/page-client.tsx +++ b/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/team-permissions/page-client.tsx @@ -21,6 +21,7 @@ import { import { Icon } from "@/components/icon"; import { runAsynchronously } from "@stackframe/stack-shared/dist/utils/promises"; import { PermissionGraph, PermissionList } from "./permission-list"; +import { PageLayout } from "../page-layout"; export default function ClientPage() { @@ -29,16 +30,14 @@ export default function ClientPage() { const [createPermissionModalOpen, setCreatePermissionModalOpen] = React.useState(false); return ( - <> - - Team Permissions - - - + setCreatePermissionModalOpen(true)}> Create Permission - + }> @@ -46,7 +45,7 @@ export default function ClientPage() { open={createPermissionModalOpen} onClose={() => setCreatePermissionModalOpen(false)} /> - > + ); } diff --git a/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/team-settings/page-client.tsx b/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/team-settings/page-client.tsx index 41928f16a..446fa420f 100644 --- a/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/team-settings/page-client.tsx +++ b/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/team-settings/page-client.tsx @@ -4,18 +4,14 @@ import { Paragraph } from "@/components/paragraph"; import { SmartSwitch } from "@/components/smart-switch"; import { SimpleCard } from "@/components/simple-card"; import { useAdminApp } from "../use-admin-app"; +import { PageLayout } from "../page-layout"; export default function TeamSettingsClient() { const stackAdminApp = useAdminApp(); const project = stackAdminApp.useProjectAdmin(); return ( - <> - - Environment - - - + - > + ); } diff --git a/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/teams/[teamId]/page-client.tsx b/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/teams/[teamId]/page-client.tsx index 901e4d181..d639cd592 100644 --- a/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/teams/[teamId]/page-client.tsx +++ b/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/teams/[teamId]/page-client.tsx @@ -6,6 +6,7 @@ import { MemberTable } from './member-table'; import { useAdminApp } from '../../use-admin-app'; import { notFound } from 'next/navigation'; import { SmartSwitch } from '@/components/smart-switch'; +import { PageLayout } from '../../page-layout'; export default function ClientPage(props: { teamId: string }) { @@ -18,14 +19,11 @@ export default function ClientPage(props: { teamId: string }) { } return ( - <> - - {team.displayName} - + - > + ); } diff --git a/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/teams/page-client.tsx b/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/teams/page-client.tsx index 4165724a8..e44b32b55 100644 --- a/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/teams/page-client.tsx +++ b/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/teams/page-client.tsx @@ -2,6 +2,7 @@ import { Paragraph } from "@/components/paragraph"; import { TeamTable } from "./team-table"; import { useAdminApp } from "../use-admin-app"; +import { PageLayout } from "../page-layout"; export default function ClientPage() { @@ -9,12 +10,8 @@ export default function ClientPage() { const teams = stackAdminApp.useTeams(); return ( - <> - - Teams - - + - > + ); } diff --git a/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/urls-and-callbacks/page-client.tsx b/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/urls-and-callbacks/page-client.tsx index 7a668a016..b20a3ed69 100644 --- a/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/urls-and-callbacks/page-client.tsx +++ b/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/urls-and-callbacks/page-client.tsx @@ -11,6 +11,7 @@ import { useAdminApp } from "../use-admin-app"; import { SmartSwitch } from "@/components/smart-switch"; import { Project } from "@stackframe/stack"; import { DomainConfigJson } from "@stackframe/stack-shared/dist/interface/clientInterface"; +import { PageLayout } from "../page-layout"; function isValidUrl(urlString: string) { try { @@ -155,10 +156,7 @@ export default function UrlsAndCallbacksClient() { return ( - <> - - Domains & Handlers - + @@ -273,6 +271,6 @@ export default function UrlsAndCallbacksClient() { Your project will no longer be able to receive callbacks from this domain. - > + ); } diff --git a/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/users/page-client.tsx b/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/users/page-client.tsx index 9001754e9..1c93fc7ed 100644 --- a/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/users/page-client.tsx +++ b/packages/stack-server/src/app/(main)/(protected)/projects/[projectId]/users/page-client.tsx @@ -3,8 +3,9 @@ import { Paragraph } from "@/components/paragraph"; import { UsersTable } from "./users-table"; import { useAdminApp } from "../use-admin-app"; -import { Alert } from "@mui/joy"; import { SmartLink } from "@/components/smart-link"; +import { PageLayout } from "../page-layout"; +import { Alert } from "@/components/ui/alert"; export default function UsersDashboardClient() { @@ -12,15 +13,10 @@ export default function UsersDashboardClient() { const allUsers = stackAdminApp.useServerUsers(); return ( - <> - - Users - - - + {allUsers.length > 0 ? null : ( - + Congratulations on starting your project! Check the documentation to add your first users. @@ -29,6 +25,6 @@ export default function UsersDashboardClient() { - > + ); } diff --git a/packages/stack-server/src/components/navbar.tsx b/packages/stack-server/src/components/navbar.tsx index 44e8d69ab..957702de7 100644 --- a/packages/stack-server/src/components/navbar.tsx +++ b/packages/stack-server/src/components/navbar.tsx @@ -1,10 +1,11 @@ 'use client'; -import { Text, UserButton } from "@stackframe/stack"; +import { UserButton } from "@stackframe/stack"; import { Logo } from "./logo"; import { Separator } from "./ui/separator"; import Link from "next/link"; import { useColorScheme } from "@mui/joy"; +import Typography from "./ui/typography"; export function Navbar({ ...props }) { const { mode, setMode } = useColorScheme(); @@ -20,10 +21,10 @@ export function Navbar({ ...props }) { - Feedback + Feedback - Docs + Docs setMode(mode === 'light' ? 'dark' : 'light')}/> diff --git a/packages/stack-server/src/components/project-card.tsx b/packages/stack-server/src/components/project-card.tsx index 3bda92cb6..36e646604 100644 --- a/packages/stack-server/src/components/project-card.tsx +++ b/packages/stack-server/src/components/project-card.tsx @@ -1,9 +1,9 @@ 'use client';; import { CardDescription, CardFooter, CardHeader, CardTitle, ClickableCard } from './ui/card'; -import { CardContent } from '@mui/joy'; import { Project } from '@stackframe/stack'; import { useFromNow } from '@/hooks/use-from-now'; import { useRouter } from 'next/navigation'; +import Typography from './ui/typography'; export function ProjectCard({ project }: { project: Project }) { const createdAt = useFromNow(project.createdAt); @@ -15,15 +15,13 @@ export function ProjectCard({ project }: { project: Project }) { {project.displayName} {project.description} - - - + {project.userCount} users - - + + {createdAt} - + ); diff --git a/packages/stack-server/src/components/ui/typography.tsx b/packages/stack-server/src/components/ui/typography.tsx new file mode 100644 index 000000000..1a8cc3f29 --- /dev/null +++ b/packages/stack-server/src/components/ui/typography.tsx @@ -0,0 +1,47 @@ +import { cn } from "@/lib/utils"; +import { cva, type VariantProps } from "class-variance-authority"; +import React from "react"; + +export const typographyVariants = cva("text-md", { + variants: { + type: { + h1: "text-4xl font-bold", + h2: "text-3xl font-bold", + h3: "text-2xl font-semibold", + h4: "text-xl", + p: "text-md", + label: "text-sm", + footnote: "text-xs", + }, + variant: { + primary: "text-black dark:text-white", + secondary: "text-zinc-500", + destructive: "text-red-500", + success: "text-green-500", + }, + }, + defaultVariants: { + type: "p", + variant: "primary", + }, +}); + +export interface TypographyProps + extends React.HTMLAttributes, + VariantProps {} + +const Typography = React.forwardRef( + ({ className, type, variant, ...props }, ref) => { + const Comp = (type === 'footnote' || type === 'label' ? 'p' : type) || 'p'; + return ( + + ); + }, +); +Typography.displayName = "Typography"; + +export default Typography; \ No newline at end of file
+ {project.userCount} users -
+ + {createdAt} -