refactor: remove useless folders
This commit is contained in:
parent
2844e1fc32
commit
408d211f27
@ -1,29 +0,0 @@
|
||||
# Server
|
||||
AUTH_TYPE="basic" #
|
||||
|
||||
BASIC_USER="konobangu"
|
||||
BASIC_PASSWORD="konobangu"
|
||||
|
||||
OIDC_PROVIDER_ENDPOINT="https://some-oidc-auth.com/oidc/.well-known/openid-configuration"
|
||||
OIDC_CLIENT_ID=""
|
||||
OIDC_CLIENT_SECRET=""
|
||||
OIDC_ISSUER="https://some-oidc-auth.com/oidc"
|
||||
OIDC_AUDIENCE="https://konobangu.com/api"
|
||||
OIDC_ICON_URL=""
|
||||
OIDC_EXTRA_SCOPE_REGEX=""
|
||||
OIDC_EXTRA_CLAIM_KEY=""
|
||||
OIDC_EXTRA_CLAIM_VALUE=""
|
||||
|
||||
DATABASE_URL="postgres://konobangu:konobangu@127.0.0.1:5432/konobangu"
|
||||
BETTERSTACK_API_KEY=""
|
||||
BETTERSTACK_URL=""
|
||||
FLAGS_SECRET=""
|
||||
ARCJET_KEY=""
|
||||
SVIX_TOKEN=""
|
||||
LIVEBLOCKS_SECRET=""
|
||||
|
||||
# Client
|
||||
NEXT_PUBLIC_APP_URL="http://localhost:5000"
|
||||
NEXT_PUBLIC_WEB_URL="http://localhost:5001"
|
||||
NEXT_PUBLIC_DOCS_URL="http://localhost:5004"
|
||||
NEXT_PUBLIC_VERCEL_PROJECT_PRODUCTION_URL="https://konobangu.com"
|
@ -1,29 +0,0 @@
|
||||
# AUTH
|
||||
AUTH_TYPE="basic"
|
||||
|
||||
NEXT_PUBLIC_OIDC_PROVIDER_ENDPOINT="https://some-oidc-auth.com/oidc/.well-known/openid-configuration"
|
||||
NEXT_PUBLIC_OIDC_CLIENT_ID=""
|
||||
NEXT_PUBLIC_OIDC_CLIENT_SECRET=""
|
||||
NEXT_PUBLIC_OIDC_ICON_URL=""
|
||||
OIDC_ISSUER="https://some-oidc-auth.com/oidc"
|
||||
OIDC_AUDIENCE="https://konobangu.com/api"
|
||||
OIDC_EXTRA_SCOPES="" # 如 "read:konobangu,write:konobangu"
|
||||
OIDC_EXTRA_CLAIM_KEY=""
|
||||
OIDC_EXTRA_CLAIM_VALUE=""
|
||||
|
||||
# DATABASE
|
||||
DATABASE_URL="postgres://konobangu:konobangu@127.0.0.1:5432/konobangu"
|
||||
|
||||
# SERVER MISC
|
||||
BETTERSTACK_API_KEY=""
|
||||
BETTERSTACK_URL=""
|
||||
FLAGS_SECRET=""
|
||||
ARCJET_KEY=""
|
||||
SVIX_TOKEN=""
|
||||
LIVEBLOCKS_SECRET=""
|
||||
|
||||
# WEBUI
|
||||
NEXT_PUBLIC_APP_URL="http://localhost:5000"
|
||||
NEXT_PUBLIC_WEB_URL="http://localhost:5001"
|
||||
NEXT_PUBLIC_DOCS_URL="http://localhost:5004"
|
||||
NEXT_PUBLIC_VERCEL_PROJECT_PRODUCTION_URL="https://konobangu.com"
|
45
apps/app/.gitignore
vendored
45
apps/app/.gitignore
vendored
@ -1,45 +0,0 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
# prisma
|
||||
.env
|
||||
|
||||
# react.email
|
||||
.react-email
|
||||
|
||||
# Sentry
|
||||
.sentryclirc
|
@ -1,13 +0,0 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { expect, test } from 'vitest';
|
||||
import Page from '../app/(unauthenticated)/sign-in/[[...sign-in]]/page';
|
||||
|
||||
test('Sign In Page', () => {
|
||||
render(<Page />);
|
||||
expect(
|
||||
screen.getByRole('heading', {
|
||||
level: 1,
|
||||
name: 'Welcome back',
|
||||
})
|
||||
).toBeDefined();
|
||||
});
|
@ -1,13 +0,0 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { expect, test } from 'vitest';
|
||||
import Page from '../app/(unauthenticated)/sign-up/[[...sign-up]]/page';
|
||||
|
||||
test('Sign Up Page', () => {
|
||||
render(<Page />);
|
||||
expect(
|
||||
screen.getByRole('heading', {
|
||||
level: 1,
|
||||
name: 'Create an account',
|
||||
})
|
||||
).toBeDefined();
|
||||
});
|
@ -1,59 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { useOthers, useSelf } from '@konobangu/collaboration/hooks';
|
||||
import {
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
AvatarImage,
|
||||
} from '@konobangu/design-system/components/ui/avatar';
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from '@konobangu/design-system/components/ui/tooltip';
|
||||
import { tailwind } from '@konobangu/tailwind-config';
|
||||
|
||||
type PresenceAvatarProps = {
|
||||
info?: Liveblocks['UserMeta']['info'];
|
||||
};
|
||||
|
||||
const PresenceAvatar = ({ info }: PresenceAvatarProps) => (
|
||||
<Tooltip delayDuration={0}>
|
||||
<TooltipTrigger>
|
||||
<Avatar className="h-7 w-7 bg-secondary ring-1 ring-background">
|
||||
<AvatarImage src={info?.avatar} alt={info?.name} />
|
||||
<AvatarFallback className="text-xs">
|
||||
{info?.name?.slice(0, 2)}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent collisionPadding={4}>
|
||||
<p>{info?.name ?? 'Unknown'}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
export const AvatarStack = () => {
|
||||
const others = useOthers();
|
||||
const self = useSelf();
|
||||
const hasMoreUsers = others.length > 3;
|
||||
|
||||
return (
|
||||
<div className="-space-x-1 flex items-center px-4">
|
||||
{others.slice(0, 3).map(({ connectionId, info }) => (
|
||||
<PresenceAvatar key={connectionId} info={info} />
|
||||
))}
|
||||
|
||||
{hasMoreUsers && (
|
||||
<PresenceAvatar
|
||||
info={{
|
||||
name: `+${others.length - 3}`,
|
||||
color: tailwind.theme.colors.gray[500],
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{self && <PresenceAvatar info={self.info} />}
|
||||
</div>
|
||||
);
|
||||
};
|
@ -1,48 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { getUsers } from '@/app/actions/users/get';
|
||||
import { searchUsers } from '@/app/actions/users/search';
|
||||
import { Room } from '@konobangu/collaboration/room';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export const CollaborationProvider = ({
|
||||
orgId,
|
||||
children,
|
||||
}: {
|
||||
orgId: string;
|
||||
children: ReactNode;
|
||||
}) => {
|
||||
const resolveUsers = async ({ userIds }: { userIds: string[] }) => {
|
||||
const response = await getUsers(userIds);
|
||||
|
||||
if ('error' in response) {
|
||||
throw new Error('Problem resolving users');
|
||||
}
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
const resolveMentionSuggestions = async ({ text }: { text: string }) => {
|
||||
const response = await searchUsers(text);
|
||||
|
||||
if ('error' in response) {
|
||||
throw new Error('Problem resolving mention suggestions');
|
||||
}
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
return (
|
||||
<Room
|
||||
id={`${orgId}:presence`}
|
||||
authEndpoint="/api/collaboration/auth"
|
||||
fallback={
|
||||
<div className="px-3 text-muted-foreground text-xs">Loading...</div>
|
||||
}
|
||||
resolveUsers={resolveUsers}
|
||||
resolveMentionSuggestions={resolveMentionSuggestions}
|
||||
>
|
||||
{children}
|
||||
</Room>
|
||||
);
|
||||
};
|
@ -1,106 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { useMyPresence, useOthers } from '@konobangu/collaboration/hooks';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
const Cursor = ({
|
||||
name,
|
||||
color,
|
||||
x,
|
||||
y,
|
||||
}: {
|
||||
name: string | undefined;
|
||||
color: string;
|
||||
x: number;
|
||||
y: number;
|
||||
}) => (
|
||||
<div
|
||||
className="pointer-events-none absolute top-0 left-0 z-[999] select-none transition-transform duration-100"
|
||||
style={{
|
||||
transform: `translateX(${x}px) translateY(${y}px)`,
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
className="absolute top-0 left-0"
|
||||
width="24"
|
||||
height="36"
|
||||
viewBox="0 0 24 36"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<title>Cursor</title>
|
||||
<path
|
||||
d="M5.65376 12.3673H5.46026L5.31717 12.4976L0.500002 16.8829L0.500002 1.19841L11.7841 12.3673H5.65376Z"
|
||||
fill={color}
|
||||
/>
|
||||
</svg>
|
||||
<div
|
||||
className="absolute top-4 left-1.5 whitespace-nowrap rounded-full px-2 py-0.5 text-white text-xs"
|
||||
style={{
|
||||
backgroundColor: color,
|
||||
}}
|
||||
>
|
||||
{name}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export const Cursors = () => {
|
||||
/**
|
||||
* useMyPresence returns the presence of the current user and a function to update it.
|
||||
* updateMyPresence is different than the setState function returned by the useState hook from React.
|
||||
* You don't need to pass the full presence object to update it.
|
||||
* See https://liveblocks.io/docs/api-reference/liveblocks-react#useMyPresence for more information
|
||||
*/
|
||||
const [_cursor, updateMyPresence] = useMyPresence();
|
||||
|
||||
/**
|
||||
* Return all the other users in the room and their presence (a cursor position in this case)
|
||||
*/
|
||||
const others = useOthers();
|
||||
|
||||
useEffect(() => {
|
||||
const onPointerMove = (event: PointerEvent) => {
|
||||
// Update the user cursor position on every pointer move
|
||||
updateMyPresence({
|
||||
cursor: {
|
||||
x: Math.round(event.clientX),
|
||||
y: Math.round(event.clientY),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const onPointerLeave = () => {
|
||||
// When the pointer goes out, set cursor to null
|
||||
updateMyPresence({
|
||||
cursor: null,
|
||||
});
|
||||
};
|
||||
|
||||
document.body.addEventListener('pointermove', onPointerMove);
|
||||
document.body.addEventListener('pointerleave', onPointerLeave);
|
||||
|
||||
return () => {
|
||||
document.body.removeEventListener('pointermove', onPointerMove);
|
||||
document.body.removeEventListener('pointerleave', onPointerLeave);
|
||||
};
|
||||
}, [updateMyPresence]);
|
||||
|
||||
return others.map(({ connectionId, presence, info }) => {
|
||||
if (!presence.cursor) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Cursor
|
||||
key={`cursor-${connectionId}`}
|
||||
// connectionId is an integer that is incremented at every new connections
|
||||
// Assigning a color with a modulo makes sure that a specific user has the same colors on every clients
|
||||
color={info.color}
|
||||
x={presence.cursor.x}
|
||||
y={presence.cursor.y}
|
||||
name={info?.name}
|
||||
/>
|
||||
);
|
||||
});
|
||||
};
|
@ -1,43 +0,0 @@
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbList,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
} from '@konobangu/design-system/components/ui/breadcrumb';
|
||||
import { Separator } from '@konobangu/design-system/components/ui/separator';
|
||||
import { SidebarTrigger } from '@konobangu/design-system/components/ui/sidebar';
|
||||
import { Fragment, type ReactNode } from 'react';
|
||||
|
||||
type HeaderProps = {
|
||||
pages: string[];
|
||||
page: string;
|
||||
children?: ReactNode;
|
||||
};
|
||||
|
||||
export const Header = ({ pages, page, children }: HeaderProps) => (
|
||||
<header className="flex h-16 shrink-0 items-center justify-between gap-2">
|
||||
<div className="flex items-center gap-2 px-4">
|
||||
<SidebarTrigger className="-ml-1" />
|
||||
<Separator orientation="vertical" className="mr-2 h-4" />
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
{pages.map((page, index) => (
|
||||
<Fragment key={page}>
|
||||
{index > 0 && <BreadcrumbSeparator className="hidden md:block" />}
|
||||
<BreadcrumbItem className="hidden md:block">
|
||||
<BreadcrumbLink href="#">{page}</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
</Fragment>
|
||||
))}
|
||||
<BreadcrumbSeparator className="hidden md:block" />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage>{page}</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
</div>
|
||||
{children}
|
||||
</header>
|
||||
);
|
@ -1,44 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { analytics } from '@konobangu/analytics/client';
|
||||
import { useSession } from '@konobangu/auth/client';
|
||||
import { usePathname, useSearchParams } from 'next/navigation';
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
export const PostHogIdentifier = () => {
|
||||
const session = useSession();
|
||||
const user = session?.data?.user;
|
||||
const identified = useRef(false);
|
||||
const pathname = usePathname();
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
useEffect(() => {
|
||||
// Track pageviews
|
||||
if (pathname && analytics) {
|
||||
let url = window.origin + pathname;
|
||||
if (searchParams.toString()) {
|
||||
url = `${url}?${searchParams.toString()}`;
|
||||
}
|
||||
analytics.capture('$pageview', {
|
||||
$current_url: url,
|
||||
});
|
||||
}
|
||||
}, [pathname, searchParams]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!user || identified.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
analytics.identify(user.id, {
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
createdAt: user.createdAt,
|
||||
avatar: user.image,
|
||||
});
|
||||
|
||||
identified.current = true;
|
||||
}, [user]);
|
||||
|
||||
return null;
|
||||
};
|
@ -1,342 +0,0 @@
|
||||
'use client';
|
||||
|
||||
// import { OrganizationSwitcher, UserButton } from '@konobangu/auth/client';
|
||||
import { ModeToggle } from '@konobangu/design-system/components/mode-toggle';
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from '@konobangu/design-system/components/ui/collapsible';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@konobangu/design-system/components/ui/dropdown-menu';
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarContent,
|
||||
SidebarFooter,
|
||||
SidebarGroup,
|
||||
SidebarGroupContent,
|
||||
SidebarGroupLabel,
|
||||
SidebarHeader,
|
||||
SidebarInset,
|
||||
SidebarMenu,
|
||||
SidebarMenuAction,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
SidebarMenuSub,
|
||||
SidebarMenuSubButton,
|
||||
SidebarMenuSubItem,
|
||||
useSidebar,
|
||||
} from '@konobangu/design-system/components/ui/sidebar';
|
||||
import { cn } from '@konobangu/design-system/lib/utils';
|
||||
import {
|
||||
AnchorIcon,
|
||||
BookOpenIcon,
|
||||
BotIcon,
|
||||
ChevronRightIcon,
|
||||
FolderIcon,
|
||||
FrameIcon,
|
||||
LifeBuoyIcon,
|
||||
MapIcon,
|
||||
MoreHorizontalIcon,
|
||||
PieChartIcon,
|
||||
SendIcon,
|
||||
Settings2Icon,
|
||||
ShareIcon,
|
||||
SquareTerminalIcon,
|
||||
Trash2Icon,
|
||||
} from 'lucide-react';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
type GlobalSidebarProperties = {
|
||||
readonly children: ReactNode;
|
||||
};
|
||||
|
||||
const data = {
|
||||
user: {
|
||||
name: 'shadcn',
|
||||
email: 'm@example.com',
|
||||
avatar: '/avatars/shadcn.jpg',
|
||||
},
|
||||
navMain: [
|
||||
{
|
||||
title: 'Playground',
|
||||
url: '#',
|
||||
icon: SquareTerminalIcon,
|
||||
isActive: true,
|
||||
items: [
|
||||
{
|
||||
title: 'History',
|
||||
url: '#',
|
||||
},
|
||||
{
|
||||
title: 'Starred',
|
||||
url: '#',
|
||||
},
|
||||
{
|
||||
title: 'Settings',
|
||||
url: '#',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Models',
|
||||
url: '#',
|
||||
icon: BotIcon,
|
||||
items: [
|
||||
{
|
||||
title: 'Genesis',
|
||||
url: '#',
|
||||
},
|
||||
{
|
||||
title: 'Explorer',
|
||||
url: '#',
|
||||
},
|
||||
{
|
||||
title: 'Quantum',
|
||||
url: '#',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Documentation',
|
||||
url: '#',
|
||||
icon: BookOpenIcon,
|
||||
items: [
|
||||
{
|
||||
title: 'Introduction',
|
||||
url: '#',
|
||||
},
|
||||
{
|
||||
title: 'Get Started',
|
||||
url: '#',
|
||||
},
|
||||
{
|
||||
title: 'Tutorials',
|
||||
url: '#',
|
||||
},
|
||||
{
|
||||
title: 'Changelog',
|
||||
url: '#',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Settings',
|
||||
url: '#',
|
||||
icon: Settings2Icon,
|
||||
items: [
|
||||
{
|
||||
title: 'General',
|
||||
url: '#',
|
||||
},
|
||||
{
|
||||
title: 'Team',
|
||||
url: '#',
|
||||
},
|
||||
{
|
||||
title: 'Billing',
|
||||
url: '#',
|
||||
},
|
||||
{
|
||||
title: 'Limits',
|
||||
url: '#',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
navSecondary: [
|
||||
{
|
||||
title: 'Webhooks',
|
||||
url: '/webhooks',
|
||||
icon: AnchorIcon,
|
||||
},
|
||||
{
|
||||
title: 'Support',
|
||||
url: '#',
|
||||
icon: LifeBuoyIcon,
|
||||
},
|
||||
{
|
||||
title: 'Feedback',
|
||||
url: '#',
|
||||
icon: SendIcon,
|
||||
},
|
||||
],
|
||||
projects: [
|
||||
{
|
||||
name: 'Design Engineering',
|
||||
url: '#',
|
||||
icon: FrameIcon,
|
||||
},
|
||||
{
|
||||
name: 'Sales & Marketing',
|
||||
url: '#',
|
||||
icon: PieChartIcon,
|
||||
},
|
||||
{
|
||||
name: 'Travel',
|
||||
url: '#',
|
||||
icon: MapIcon,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const GlobalSidebar = ({ children }: GlobalSidebarProperties) => {
|
||||
const sidebar = useSidebar();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Sidebar variant="inset">
|
||||
<SidebarHeader>
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<div
|
||||
className={cn(
|
||||
'h-[36px] overflow-hidden transition-all [&>div]:w-full',
|
||||
sidebar.open ? '' : '-mx-1'
|
||||
)}
|
||||
>
|
||||
{/* <OrganizationSwitcher
|
||||
hidePersonal
|
||||
afterSelectOrganizationUrl="/"
|
||||
/> */}
|
||||
</div>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</SidebarHeader>
|
||||
<SidebarContent>
|
||||
<SidebarGroup>
|
||||
<SidebarGroupLabel>Platform</SidebarGroupLabel>
|
||||
<SidebarMenu>
|
||||
{data.navMain.map((item) => (
|
||||
<Collapsible
|
||||
key={item.title}
|
||||
asChild
|
||||
defaultOpen={item.isActive}
|
||||
>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton asChild tooltip={item.title}>
|
||||
<a href={item.url}>
|
||||
<item.icon />
|
||||
<span>{item.title}</span>
|
||||
</a>
|
||||
</SidebarMenuButton>
|
||||
{item.items?.length ? (
|
||||
<>
|
||||
<CollapsibleTrigger asChild>
|
||||
<SidebarMenuAction className="data-[state=open]:rotate-90">
|
||||
<ChevronRightIcon />
|
||||
<span className="sr-only">Toggle</span>
|
||||
</SidebarMenuAction>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<SidebarMenuSub>
|
||||
{item.items?.map((subItem) => (
|
||||
<SidebarMenuSubItem key={subItem.title}>
|
||||
<SidebarMenuSubButton asChild>
|
||||
<a href={subItem.url}>
|
||||
<span>{subItem.title}</span>
|
||||
</a>
|
||||
</SidebarMenuSubButton>
|
||||
</SidebarMenuSubItem>
|
||||
))}
|
||||
</SidebarMenuSub>
|
||||
</CollapsibleContent>
|
||||
</>
|
||||
) : null}
|
||||
</SidebarMenuItem>
|
||||
</Collapsible>
|
||||
))}
|
||||
</SidebarMenu>
|
||||
</SidebarGroup>
|
||||
<SidebarGroup className="group-data-[collapsible=icon]:hidden">
|
||||
<SidebarGroupLabel>Projects</SidebarGroupLabel>
|
||||
<SidebarMenu>
|
||||
{data.projects.map((item) => (
|
||||
<SidebarMenuItem key={item.name}>
|
||||
<SidebarMenuButton asChild>
|
||||
<a href={item.url}>
|
||||
<item.icon />
|
||||
<span>{item.name}</span>
|
||||
</a>
|
||||
</SidebarMenuButton>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<SidebarMenuAction showOnHover>
|
||||
<MoreHorizontalIcon />
|
||||
<span className="sr-only">More</span>
|
||||
</SidebarMenuAction>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
className="w-48"
|
||||
side="bottom"
|
||||
align="end"
|
||||
>
|
||||
<DropdownMenuItem>
|
||||
<FolderIcon className="text-muted-foreground" />
|
||||
<span>View Project</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<ShareIcon className="text-muted-foreground" />
|
||||
<span>Share Project</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>
|
||||
<Trash2Icon className="text-muted-foreground" />
|
||||
<span>Delete Project</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</SidebarMenuItem>
|
||||
))}
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton>
|
||||
<MoreHorizontalIcon />
|
||||
<span>More</span>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</SidebarGroup>
|
||||
<SidebarGroup className="mt-auto">
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu>
|
||||
{data.navSecondary.map((item) => (
|
||||
<SidebarMenuItem key={item.title}>
|
||||
<SidebarMenuButton asChild>
|
||||
<a href={item.url}>
|
||||
<item.icon />
|
||||
<span>{item.title}</span>
|
||||
</a>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
))}
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
</SidebarContent>
|
||||
<SidebarFooter>
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem className="flex items-center gap-2">
|
||||
{/* <UserButton
|
||||
showName
|
||||
appearance={{
|
||||
elements: {
|
||||
rootBox: 'flex overflow-hidden w-full',
|
||||
userButtonBox: 'flex-row-reverse',
|
||||
userButtonOuterIdentifier: 'truncate pl-0',
|
||||
},
|
||||
}}
|
||||
/> */}
|
||||
<ModeToggle />
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</SidebarFooter>
|
||||
</Sidebar>
|
||||
<SidebarInset>{children}</SidebarInset>
|
||||
</>
|
||||
);
|
||||
};
|
@ -1,42 +0,0 @@
|
||||
import { getSessionFromHeaders } from '@konobangu/auth/server';
|
||||
import { SidebarProvider } from '@konobangu/design-system/components/ui/sidebar';
|
||||
import { env } from '@konobangu/env';
|
||||
import { showBetaFeature } from '@konobangu/feature-flags';
|
||||
import { secure } from '@konobangu/security';
|
||||
import { redirect } from 'next/navigation';
|
||||
import type { ReactNode } from 'react';
|
||||
import { PostHogIdentifier } from './components/posthog-identifier';
|
||||
import { GlobalSidebar } from './components/sidebar';
|
||||
|
||||
type AppLayoutProperties = {
|
||||
readonly children: ReactNode;
|
||||
};
|
||||
|
||||
const AppLayout = async ({ children }: AppLayoutProperties) => {
|
||||
if (env.ARCJET_KEY) {
|
||||
await secure(['CATEGORY:PREVIEW']);
|
||||
}
|
||||
|
||||
const { user } = await getSessionFromHeaders();
|
||||
|
||||
if (!user) {
|
||||
return redirect('/sign-in'); // from next/navigation
|
||||
}
|
||||
const betaFeature = await showBetaFeature();
|
||||
|
||||
return (
|
||||
<SidebarProvider>
|
||||
<GlobalSidebar>
|
||||
{betaFeature && (
|
||||
<div className="m-4 rounded-full bg-success p-1.5 text-center text-sm text-success-foreground">
|
||||
Beta feature now available
|
||||
</div>
|
||||
)}
|
||||
{children}
|
||||
</GlobalSidebar>
|
||||
<PostHogIdentifier />
|
||||
</SidebarProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default AppLayout;
|
@ -1,57 +0,0 @@
|
||||
import { getSessionFromHeaders } from '@konobangu/auth/server';
|
||||
import { database } from '@konobangu/database';
|
||||
import { env } from '@konobangu/env';
|
||||
import type { Metadata } from 'next';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { notFound } from 'next/navigation';
|
||||
import { AvatarStack } from './components/avatar-stack';
|
||||
import { Cursors } from './components/cursors';
|
||||
import { Header } from './components/header';
|
||||
|
||||
const title = 'Acme Inc';
|
||||
const description = 'My application.';
|
||||
|
||||
const CollaborationProvider = dynamic(() =>
|
||||
import('./components/collaboration-provider').then(
|
||||
(mod) => mod.CollaborationProvider
|
||||
)
|
||||
);
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title,
|
||||
description,
|
||||
};
|
||||
|
||||
const App = async () => {
|
||||
const pages = await database.selectFrom('page').selectAll().execute();
|
||||
const { orgId } = await getSessionFromHeaders();
|
||||
|
||||
if (!orgId) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header pages={['Building Your Application']} page="Data Fetching">
|
||||
{env.LIVEBLOCKS_SECRET && (
|
||||
<CollaborationProvider orgId={orgId}>
|
||||
<AvatarStack />
|
||||
<Cursors />
|
||||
</CollaborationProvider>
|
||||
)}
|
||||
</Header>
|
||||
<div className="flex flex-1 flex-col gap-4 p-4 pt-0">
|
||||
<div className="grid auto-rows-min gap-4 md:grid-cols-3">
|
||||
{pages.map((page) => (
|
||||
<div key={page.id} className="aspect-video rounded-xl bg-muted/50">
|
||||
{page.name}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="min-h-[100vh] flex-1 rounded-xl bg-muted/50 md:min-h-min" />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
@ -1,29 +0,0 @@
|
||||
import { webhooks } from '@konobangu/webhooks';
|
||||
import { notFound } from 'next/navigation';
|
||||
|
||||
export const metadata = {
|
||||
title: 'Webhooks',
|
||||
description: 'Send webhooks to your users.',
|
||||
};
|
||||
|
||||
const WebhooksPage = async () => {
|
||||
const response = await webhooks.getAppPortal();
|
||||
|
||||
if (!response?.url) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-full w-full overflow-hidden">
|
||||
<iframe
|
||||
title="Webhooks"
|
||||
src={response.url}
|
||||
className="h-full w-full border-none"
|
||||
allow="clipboard-write"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default WebhooksPage;
|
@ -1,58 +0,0 @@
|
||||
import { ModeToggle } from '@konobangu/design-system/components/mode-toggle';
|
||||
import { env } from '@konobangu/env';
|
||||
import { CommandIcon } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
type AuthLayoutProps = {
|
||||
readonly children: ReactNode;
|
||||
};
|
||||
|
||||
const AuthLayout = ({ children }: AuthLayoutProps) => (
|
||||
<div className="container relative grid h-dvh flex-col items-center justify-center lg:max-w-none lg:grid-cols-2 lg:px-0">
|
||||
<div className="relative hidden h-full flex-col bg-muted p-10 text-white lg:flex dark:border-r">
|
||||
<div className="absolute inset-0 bg-zinc-900" />
|
||||
<div className="relative z-20 flex items-center font-medium text-lg">
|
||||
<CommandIcon className="mr-2 h-6 w-6" />
|
||||
Acme Inc
|
||||
</div>
|
||||
<div className="absolute top-4 right-4">
|
||||
<ModeToggle />
|
||||
</div>
|
||||
<div className="relative z-20 mt-auto">
|
||||
<blockquote className="space-y-2">
|
||||
<p className="text-lg">
|
||||
“This library has saved me countless hours of work and helped
|
||||
me deliver stunning designs to my clients faster than ever
|
||||
before.”
|
||||
</p>
|
||||
<footer className="text-sm">Sofia Davis</footer>
|
||||
</blockquote>
|
||||
</div>
|
||||
</div>
|
||||
<div className="lg:p-8">
|
||||
<div className="mx-auto flex w-full max-w-[400px] flex-col justify-center space-y-6">
|
||||
{children}
|
||||
<p className="px-8 text-center text-muted-foreground text-sm">
|
||||
By clicking continue, you agree to our{' '}
|
||||
<Link
|
||||
href={new URL('/legal/terms', env.NEXT_PUBLIC_WEB_URL).toString()}
|
||||
className="underline underline-offset-4 hover:text-primary"
|
||||
>
|
||||
Terms of Service
|
||||
</Link>{' '}
|
||||
and{' '}
|
||||
<Link
|
||||
href={new URL('/legal/privacy', env.NEXT_PUBLIC_WEB_URL).toString()}
|
||||
className="underline underline-offset-4 hover:text-primary"
|
||||
>
|
||||
Privacy Policy
|
||||
</Link>
|
||||
.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default AuthLayout;
|
@ -1,23 +0,0 @@
|
||||
import { createMetadata } from '@konobangu/seo/metadata';
|
||||
import type { Metadata } from 'next';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
const title = 'Welcome back';
|
||||
const description = 'Enter your details to sign in.';
|
||||
const SignIn = dynamic(() =>
|
||||
import('@konobangu/auth/components/sign-in').then((mod) => mod.SignIn)
|
||||
);
|
||||
|
||||
export const metadata: Metadata = createMetadata({ title, description });
|
||||
|
||||
const SignInPage = () => (
|
||||
<>
|
||||
<div className="flex flex-col space-y-2 text-center">
|
||||
<h1 className="font-semibold text-2xl tracking-tight">{title}</h1>
|
||||
<p className="text-muted-foreground text-sm">{description}</p>
|
||||
</div>
|
||||
<SignIn />
|
||||
</>
|
||||
);
|
||||
|
||||
export default SignInPage;
|
@ -1,23 +0,0 @@
|
||||
import { createMetadata } from '@konobangu/seo/metadata';
|
||||
import type { Metadata } from 'next';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
const title = 'Create an account';
|
||||
const description = 'Enter your details to get started.';
|
||||
const SignUp = dynamic(() =>
|
||||
import('@konobangu/auth/components/sign-up').then((mod) => mod.SignUp)
|
||||
);
|
||||
|
||||
export const metadata: Metadata = createMetadata({ title, description });
|
||||
|
||||
const SignUpPage = () => (
|
||||
<>
|
||||
<div className="flex flex-col space-y-2 text-center">
|
||||
<h1 className="font-semibold text-2xl tracking-tight">{title}</h1>
|
||||
<p className="text-muted-foreground text-sm">{description}</p>
|
||||
</div>
|
||||
<SignUp />
|
||||
</>
|
||||
);
|
||||
|
||||
export default SignUpPage;
|
@ -1,3 +0,0 @@
|
||||
import { getFlags } from '@konobangu/feature-flags/access';
|
||||
|
||||
export const GET = getFlags;
|
@ -1,63 +0,0 @@
|
||||
'use server';
|
||||
|
||||
import {
|
||||
getFullOrganizationFromSession,
|
||||
getSessionFromHeaders,
|
||||
} from '@konobangu/auth/server';
|
||||
import { tailwind } from '@konobangu/tailwind-config';
|
||||
|
||||
const colors = [
|
||||
tailwind.theme.colors.red[500],
|
||||
tailwind.theme.colors.orange[500],
|
||||
tailwind.theme.colors.amber[500],
|
||||
tailwind.theme.colors.yellow[500],
|
||||
tailwind.theme.colors.lime[500],
|
||||
tailwind.theme.colors.green[500],
|
||||
tailwind.theme.colors.emerald[500],
|
||||
tailwind.theme.colors.teal[500],
|
||||
tailwind.theme.colors.cyan[500],
|
||||
tailwind.theme.colors.sky[500],
|
||||
tailwind.theme.colors.blue[500],
|
||||
tailwind.theme.colors.indigo[500],
|
||||
tailwind.theme.colors.violet[500],
|
||||
tailwind.theme.colors.purple[500],
|
||||
tailwind.theme.colors.fuchsia[500],
|
||||
tailwind.theme.colors.pink[500],
|
||||
tailwind.theme.colors.rose[500],
|
||||
];
|
||||
|
||||
export const getUsers = async (
|
||||
userIds: string[]
|
||||
): Promise<
|
||||
| {
|
||||
data: Liveblocks['UserMeta']['info'][];
|
||||
}
|
||||
| {
|
||||
error: unknown;
|
||||
}
|
||||
> => {
|
||||
try {
|
||||
const session = await getSessionFromHeaders();
|
||||
const { orgId } = session;
|
||||
|
||||
if (!orgId) {
|
||||
throw new Error('Not logged in');
|
||||
}
|
||||
|
||||
const { fullOrganization } = await getFullOrganizationFromSession(session);
|
||||
|
||||
const members = fullOrganization?.members || [];
|
||||
|
||||
const data: Liveblocks['UserMeta']['info'][] = members
|
||||
.filter((user) => user?.userId && userIds.includes(user?.userId))
|
||||
.map((user) => ({
|
||||
name: user.user.name ?? user.user.email ?? 'Unknown user',
|
||||
picture: user.user.image,
|
||||
color: colors[Math.floor(Math.random() * colors.length)],
|
||||
}));
|
||||
|
||||
return { data };
|
||||
} catch (error) {
|
||||
return { error };
|
||||
}
|
||||
};
|
@ -1,50 +0,0 @@
|
||||
'use server';
|
||||
|
||||
import {
|
||||
getFullOrganizationFromSession,
|
||||
getSessionFromHeaders,
|
||||
} from '@konobangu/auth/server';
|
||||
import Fuse from 'fuse.js';
|
||||
|
||||
export const searchUsers = async (
|
||||
query: string
|
||||
): Promise<
|
||||
| {
|
||||
data: string[];
|
||||
}
|
||||
| {
|
||||
error: unknown;
|
||||
}
|
||||
> => {
|
||||
try {
|
||||
const session = await getSessionFromHeaders();
|
||||
const { orgId } = session;
|
||||
|
||||
if (!orgId) {
|
||||
throw new Error('Not logged in');
|
||||
}
|
||||
|
||||
const { fullOrganization } = await getFullOrganizationFromSession(session);
|
||||
|
||||
const members = fullOrganization?.members || [];
|
||||
|
||||
const users = members.map((user) => ({
|
||||
id: user.id,
|
||||
name: user.user.name ?? user.user.email ?? 'Unknown user',
|
||||
imageUrl: user.user.image,
|
||||
}));
|
||||
|
||||
const fuse = new Fuse(users, {
|
||||
keys: ['name'],
|
||||
minMatchCharLength: 1,
|
||||
threshold: 0.3,
|
||||
});
|
||||
|
||||
const results = fuse.search(query);
|
||||
const data = results.map((result) => result.item.id);
|
||||
|
||||
return { data };
|
||||
} catch (error) {
|
||||
return { error };
|
||||
}
|
||||
};
|
@ -1,42 +0,0 @@
|
||||
import { getSessionFromHeaders } from '@konobangu/auth/server';
|
||||
import { authenticate } from '@konobangu/collaboration/auth';
|
||||
import { tailwind } from '@konobangu/tailwind-config';
|
||||
|
||||
const COLORS = [
|
||||
tailwind.theme.colors.red[500],
|
||||
tailwind.theme.colors.orange[500],
|
||||
tailwind.theme.colors.amber[500],
|
||||
tailwind.theme.colors.yellow[500],
|
||||
tailwind.theme.colors.lime[500],
|
||||
tailwind.theme.colors.green[500],
|
||||
tailwind.theme.colors.emerald[500],
|
||||
tailwind.theme.colors.teal[500],
|
||||
tailwind.theme.colors.cyan[500],
|
||||
tailwind.theme.colors.sky[500],
|
||||
tailwind.theme.colors.blue[500],
|
||||
tailwind.theme.colors.indigo[500],
|
||||
tailwind.theme.colors.violet[500],
|
||||
tailwind.theme.colors.purple[500],
|
||||
tailwind.theme.colors.fuchsia[500],
|
||||
tailwind.theme.colors.pink[500],
|
||||
tailwind.theme.colors.rose[500],
|
||||
];
|
||||
|
||||
export const POST = async () => {
|
||||
const session = await getSessionFromHeaders();
|
||||
const { orgId, user } = session;
|
||||
|
||||
if (!user || !orgId) {
|
||||
return new Response('Unauthorized', { status: 401 });
|
||||
}
|
||||
|
||||
return authenticate({
|
||||
userId: user.id,
|
||||
orgId,
|
||||
userInfo: {
|
||||
name: user.name ?? user.email ?? undefined,
|
||||
avatar: user.image ?? undefined,
|
||||
color: COLORS[Math.floor(Math.random() * COLORS.length)],
|
||||
},
|
||||
});
|
||||
};
|
Binary file not shown.
Before Width: | Height: | Size: 216 B |
@ -1,17 +0,0 @@
|
||||
import { database } from '@konobangu/database';
|
||||
|
||||
export const POST = async () => {
|
||||
const newPage = await database
|
||||
.insertInto('page')
|
||||
.values([
|
||||
{
|
||||
name: 'cron-temp',
|
||||
},
|
||||
])
|
||||
.returning('id')
|
||||
.executeTakeFirstOrThrow();
|
||||
|
||||
await database.deleteFrom('page').where('id', '=', newPage.id);
|
||||
|
||||
return new Response('OK', { status: 200 });
|
||||
};
|
@ -1,29 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { Button } from '@konobangu/design-system/components/ui/button';
|
||||
import { fonts } from '@konobangu/design-system/lib/fonts';
|
||||
import { captureException } from '@sentry/nextjs';
|
||||
import type NextError from 'next/error';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
type GlobalErrorProperties = {
|
||||
readonly error: NextError & { digest?: string };
|
||||
readonly reset: () => void;
|
||||
};
|
||||
|
||||
const GlobalError = ({ error, reset }: GlobalErrorProperties) => {
|
||||
useEffect(() => {
|
||||
captureException(error);
|
||||
}, [error]);
|
||||
|
||||
return (
|
||||
<html lang="en" className={fonts}>
|
||||
<body>
|
||||
<h1>Oops, something went wrong</h1>
|
||||
<Button onClick={() => reset()}>Try again</Button>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
};
|
||||
|
||||
export default GlobalError;
|
@ -1,3 +0,0 @@
|
||||
export const runtime = 'edge';
|
||||
|
||||
export const GET = (): Response => new Response('OK', { status: 200 });
|
Binary file not shown.
Before Width: | Height: | Size: 96 B |
@ -1,18 +0,0 @@
|
||||
import '@konobangu/design-system/styles/globals.css';
|
||||
import { DesignSystemProvider } from '@konobangu/design-system';
|
||||
import { fonts } from '@konobangu/design-system/lib/fonts';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
type RootLayoutProperties = {
|
||||
readonly children: ReactNode;
|
||||
};
|
||||
|
||||
const RootLayout = ({ children }: RootLayoutProperties) => (
|
||||
<html lang="en" className={fonts} suppressHydrationWarning>
|
||||
<body>
|
||||
<DesignSystemProvider>{children}</DesignSystemProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
export default RootLayout;
|
Binary file not shown.
Before Width: | Height: | Size: 57 KiB |
@ -1,3 +0,0 @@
|
||||
import { initializeSentry } from '@konobangu/next-config/instrumentation';
|
||||
|
||||
export const register = initializeSentry();
|
@ -1 +0,0 @@
|
||||
export * from '@konobangu/collaboration/config';
|
@ -1,22 +0,0 @@
|
||||
import { authMiddleware } from '@konobangu/auth/middleware';
|
||||
import {
|
||||
noseconeConfig,
|
||||
noseconeMiddleware,
|
||||
} from '@konobangu/security/middleware';
|
||||
import type { NextRequest } from 'next/server';
|
||||
|
||||
const securityHeaders = noseconeMiddleware(noseconeConfig);
|
||||
|
||||
export async function middleware(_request: NextRequest) {
|
||||
const response = await securityHeaders();
|
||||
return authMiddleware(response as any);
|
||||
}
|
||||
|
||||
export const config = {
|
||||
matcher: [
|
||||
// Skip Next.js internals and all static files, unless found in search params
|
||||
'/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
|
||||
// Always run for API routes
|
||||
'/(api|trpc)(.*)',
|
||||
],
|
||||
};
|
@ -1,15 +0,0 @@
|
||||
import { env } from '@konobangu/env';
|
||||
import { config, withAnalyzer, withSentry } from '@konobangu/next-config';
|
||||
import type { NextConfig } from 'next';
|
||||
|
||||
let nextConfig: NextConfig = { ...config };
|
||||
|
||||
if (env.VERCEL) {
|
||||
nextConfig = withSentry(nextConfig);
|
||||
}
|
||||
|
||||
if (env.ANALYZE === 'true') {
|
||||
nextConfig = withAnalyzer(nextConfig);
|
||||
}
|
||||
|
||||
export default nextConfig;
|
@ -1,51 +0,0 @@
|
||||
{
|
||||
"name": "app",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev -p 5000 --turbopack",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"analyze": "ANALYZE=true pnpm build",
|
||||
"test": "vitest run",
|
||||
"clean": "git clean -xdf .cache .turbo dist node_modules",
|
||||
"typecheck": "tsc --noEmit --emitDeclarationOnly false"
|
||||
},
|
||||
"dependencies": {
|
||||
"@konobangu/analytics": "workspace:*",
|
||||
"@konobangu/auth": "workspace:*",
|
||||
"@konobangu/collaboration": "workspace:*",
|
||||
"@konobangu/database": "workspace:*",
|
||||
"@konobangu/design-system": "workspace:*",
|
||||
"@konobangu/env": "workspace:*",
|
||||
"@konobangu/feature-flags": "workspace:*",
|
||||
"@konobangu/migrate": "workspace:*",
|
||||
"@konobangu/next-config": "workspace:*",
|
||||
"@konobangu/security": "workspace:*",
|
||||
"@konobangu/seo": "workspace:*",
|
||||
"@konobangu/tailwind-config": "workspace:*",
|
||||
"@konobangu/webhooks": "workspace:*",
|
||||
"@prisma/client": "6.0.1",
|
||||
"@sentry/nextjs": "^8.48.0",
|
||||
"fuse.js": "^7.0.0",
|
||||
"import-in-the-middle": "^1.12.0",
|
||||
"lucide-react": "^0.468.0",
|
||||
"next": "^15.1.4",
|
||||
"next-themes": "^0.4.4",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"require-in-the-middle": "^7.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@konobangu/testing": "workspace:*",
|
||||
"@konobangu/typescript-config": "workspace:*",
|
||||
"@testing-library/dom": "^10.4.0",
|
||||
"@testing-library/react": "^16.1.0",
|
||||
"@types/node": "22.10.1",
|
||||
"@types/react": "19.0.1",
|
||||
"@types/react-dom": "19.0.2",
|
||||
"jsdom": "^25.0.1",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "^5.7.3",
|
||||
"vitest": "^2.1.8"
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
export { default } from '@konobangu/design-system/postcss.config.mjs';
|
@ -1,34 +0,0 @@
|
||||
/*
|
||||
* This file configures the initialization of Sentry on the client.
|
||||
* The config you add here will be used whenever a users loads a page in their browser.
|
||||
* https://docs.sentry.io/platforms/javascript/guides/nextjs/
|
||||
*/
|
||||
|
||||
import { init, replayIntegration } from '@sentry/nextjs';
|
||||
|
||||
init({
|
||||
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
|
||||
|
||||
// Adjust this value in production, or use tracesSampler for greater control
|
||||
tracesSampleRate: 1,
|
||||
|
||||
// Setting this option to true will print useful information to the console while you're setting up Sentry.
|
||||
debug: false,
|
||||
|
||||
replaysOnErrorSampleRate: 1,
|
||||
|
||||
/*
|
||||
* This sets the sample rate to be 10%. You may want this to be 100% while
|
||||
* in development and sample at a lower rate in production
|
||||
*/
|
||||
replaysSessionSampleRate: 0.1,
|
||||
|
||||
// You can remove this option if you're not planning to use the Sentry Session Replay feature:
|
||||
integrations: [
|
||||
replayIntegration({
|
||||
// Additional Replay configuration goes in here, for example:
|
||||
maskAllText: true,
|
||||
blockAllMedia: true,
|
||||
}),
|
||||
],
|
||||
});
|
@ -1 +0,0 @@
|
||||
export { config as default } from '@konobangu/tailwind-config/config';
|
@ -1,17 +0,0 @@
|
||||
{
|
||||
"extends": "@konobangu/typescript-config/nextjs.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./*"],
|
||||
"@konobangu/*": ["../../packages/*"]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"next.config.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts"
|
||||
]
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
{
|
||||
"crons": [
|
||||
{
|
||||
"path": "/cron/keep-alive",
|
||||
"schedule": "0 1 * * *"
|
||||
}
|
||||
]
|
||||
}
|
@ -1 +0,0 @@
|
||||
export { default } from '@konobangu/testing';
|
@ -4,8 +4,5 @@
|
||||
"scripts": {
|
||||
"dev": "npx --yes mintlify dev --port 5004",
|
||||
"lint": "npx --yes mintlify broken-links"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.7.3"
|
||||
}
|
||||
}
|
||||
|
@ -10,15 +10,11 @@
|
||||
"typecheck": "tsc --noEmit --emitDeclarationOnly false"
|
||||
},
|
||||
"dependencies": {
|
||||
"@konobangu/email": "workspace:*",
|
||||
"@react-email/components": "0.0.31",
|
||||
"react": "^19.0.0",
|
||||
"react-email": "3.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@konobangu/typescript-config": "workspace:*",
|
||||
"@types/node": "22.10.1",
|
||||
"@types/react": "19.0.1",
|
||||
"typescript": "^5.7.3"
|
||||
"@types/react": "19.0.1"
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,9 @@
|
||||
{
|
||||
"extends": "@konobangu/typescript-config/nextjs.json",
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": ["**/*.ts", "**/*.tsx"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
@ -36,7 +36,6 @@ sea-orm = { version = "1.1", features = [
|
||||
"debug-print",
|
||||
] }
|
||||
figment = { version = "0.10", features = ["toml", "json", "env", "yaml"] }
|
||||
|
||||
axum = "0.8"
|
||||
uuid = { version = "1.6.0", features = ["v4"] }
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] }
|
||||
|
@ -8,30 +8,17 @@
|
||||
"preview": "rsbuild preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@abraham/reflection": "^0.12.0",
|
||||
"@graphiql/react": "^0.28.2",
|
||||
"@graphiql/toolkit": "^0.11.1",
|
||||
"@konobangu/design-system": "workspace:*",
|
||||
"@konobangu/tailwind-config": "workspace:*",
|
||||
"@outposts/injection-js": "^2.5.1",
|
||||
"@tanstack/react-router": "^1.95.6",
|
||||
"@tanstack/router-devtools": "^1.95.6",
|
||||
"graphiql": "^3.8.3",
|
||||
"graphql-ws": "^5.16.2",
|
||||
"graphql-ws": "^6.0.4",
|
||||
"observable-hooks": "^4.2.4",
|
||||
"oidc-client-rx": "0.1.0-alpha.6",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"rxjs": "^7.8.1"
|
||||
"react-dom": "^19.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@konobangu/typescript-config": "workspace:*",
|
||||
"@rsbuild/core": "1.1.3",
|
||||
"@rsbuild/plugin-react": "^1.1.1",
|
||||
"@tanstack/router-plugin": "^1.95.6",
|
||||
"@types/react": "^19.0.7",
|
||||
"@types/react-dom": "^19.0.3",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "^5.7.3"
|
||||
"@types/react-dom": "^19.0.3"
|
||||
}
|
||||
}
|
||||
|
5
apps/recorder/postcss.config.mjs
Normal file
5
apps/recorder/postcss.config.mjs
Normal file
@ -0,0 +1,5 @@
|
||||
export default {
|
||||
plugins: {
|
||||
'@tailwindcss/postcss': {},
|
||||
},
|
||||
};
|
103
apps/recorder/recorder.config.toml
Normal file
103
apps/recorder/recorder.config.toml
Normal file
@ -0,0 +1,103 @@
|
||||
# Application logging configuration
|
||||
[logger]
|
||||
# Enable or disable logging.
|
||||
enable = true
|
||||
# Enable pretty backtrace (sets RUST_BACKTRACE=1)
|
||||
pretty_backtrace = true
|
||||
# Log level, options: trace, debug, info, warn or error.
|
||||
level = "debug"
|
||||
# Define the logging format. options: compact, pretty or Json
|
||||
format = "compact"
|
||||
# By default the logger has filtering only logs that came from your code or logs that came from `loco` framework. to see all third party libraries
|
||||
# Uncomment the line below to override to see all third party libraries you can enable this config and override the logger filters.
|
||||
# override_filter: trace
|
||||
|
||||
# Web server configuration
|
||||
[server]
|
||||
# Port on which the server will listen. the server binding is 0.0.0.0:{PORT}
|
||||
port = 5001
|
||||
binding = "0.0.0.0"
|
||||
# The UI hostname or IP address that mailers will point to.
|
||||
host = '{{ get_env(name="HOST", default="localhost") }}'
|
||||
# Out of the box middleware configuration. to disable middleware you can changed the `enable` field to `false` of comment the middleware block
|
||||
|
||||
# Enable Etag cache header middleware
|
||||
[server.middlewares.etag]
|
||||
enable = true
|
||||
|
||||
# Generating a unique request ID and enhancing logging with additional information such as the start and completion of request processing, latency, status code, and other request details.
|
||||
[server.middleware.request_id]
|
||||
enable = true
|
||||
|
||||
[server.middleware.logger]
|
||||
enable = true
|
||||
|
||||
# when your code is panicked, the request still returns 500 status code.
|
||||
[server.middleware.catch_panic]
|
||||
enable = true
|
||||
|
||||
# Timeout for incoming requests middleware. requests that take more time from the configuration will cute and 408 status code will returned.
|
||||
[server.middleware.timeout_request]
|
||||
enable = false
|
||||
# Duration time in milliseconds.
|
||||
timeout = 5000
|
||||
|
||||
# Set the value of the [`Access-Control-Allow-Origin`][mdn] header
|
||||
# allow_origins:
|
||||
# - https://loco.rs
|
||||
# Set the value of the [`Access-Control-Allow-Headers`][mdn] header
|
||||
# allow_headers:
|
||||
# - Content-Type
|
||||
# Set the value of the [`Access-Control-Allow-Methods`][mdn] header
|
||||
# allow_methods:
|
||||
# - POST
|
||||
# Set the value of the [`Access-Control-Max-Age`][mdn] header in seconds
|
||||
# max_age: 3600
|
||||
[server.middleware.cors]
|
||||
enable = true
|
||||
|
||||
# Database Configuration
|
||||
[database]
|
||||
# Database connection URI
|
||||
uri = '{{ get_env(name="DATABASE_URL", default="postgres://konobangu:konobangu@localhost:5432/konobangu") }}'
|
||||
# When enabled, the sql query will be logged.
|
||||
enable_logging = true
|
||||
# Set the timeout duration when acquiring a connection.
|
||||
connect_timeout = 500
|
||||
# Set the idle duration before closing a connection.
|
||||
idle_timeout = 500
|
||||
# Minimum number of connections for a pool.
|
||||
min_connections = 1
|
||||
# Maximum number of connections for a pool.
|
||||
max_connections = 10
|
||||
# Run migration up when application loaded
|
||||
auto_migrate = true
|
||||
|
||||
[storage]
|
||||
data_dir = '{{ get_env(name="STORAGE_DATA_DIR", default="./data") }}'
|
||||
|
||||
[mikan]
|
||||
base_url = "https://mikanani.me/"
|
||||
|
||||
[mikan.http_client]
|
||||
exponential_backoff_max_retries = 3
|
||||
leaky_bucket_max_tokens = 2
|
||||
leaky_bucket_initial_tokens = 1
|
||||
leaky_bucket_refill_tokens = 1
|
||||
leaky_bucket_refill_interval = 500
|
||||
|
||||
[auth]
|
||||
auth_type = '{{ get_env(name="AUTH_TYPE", default = "basic") }}'
|
||||
basic_user = '{{ get_env(name="BASIC_USER", default = "konobangu") }}'
|
||||
basic_password = '{{ get_env(name="BASIC_PASSWORD", default = "konobangu") }}'
|
||||
oidc_issuer = '{{ get_env(name="OIDC_ISSUER", default = "") }}'
|
||||
oidc_audience = '{{ get_env(name="OIDC_AUDIENCE", default = "") }}'
|
||||
oidc_client_id = '{{ get_env(name="OIDC_CLIENT_ID", default = "") }}'
|
||||
oidc_client_secret = '{{ get_env(name="OIDC_CLIENT_SECRET", default = "") }}'
|
||||
oidc_extra_scopes = '{{ get_env(name="OIDC_EXTRA_SCOPES", default = "") }}'
|
||||
oidc_extra_claim_key = '{{ get_env(name="OIDC_EXTRA_CLAIM_KEY", default = "") }}'
|
||||
oidc_extra_claim_value = '{{ get_env(name="OIDC_EXTRA_CLAIM_VALUE", default = "") }}'
|
||||
|
||||
[graphql]
|
||||
# depth_limit = inf
|
||||
# complexity_limit = inf
|
@ -1,101 +0,0 @@
|
||||
# Loco configuration file documentation
|
||||
|
||||
# Application logging configuration
|
||||
logger:
|
||||
# Enable or disable logging.
|
||||
enable: true
|
||||
# Enable pretty backtrace (sets RUST_BACKTRACE=1)
|
||||
pretty_backtrace: true
|
||||
# Log level, options: trace, debug, info, warn or error.
|
||||
level: debug
|
||||
# Define the logging format. options: compact, pretty or Json
|
||||
format: compact
|
||||
# By default the logger has filtering only logs that came from your code or logs that came from `loco` framework. to see all third party libraries
|
||||
# Uncomment the line below to override to see all third party libraries you can enable this config and override the logger filters.
|
||||
# override_filter: trace
|
||||
|
||||
# Web server configuration
|
||||
server:
|
||||
# Port on which the server will listen. the server binding is 0.0.0.0:{PORT}
|
||||
port: 5001
|
||||
binding: "0.0.0.0"
|
||||
# The UI hostname or IP address that mailers will point to.
|
||||
host: '{{ get_env(name="HOST", default="localhost") }}'
|
||||
# Out of the box middleware configuration. to disable middleware you can changed the `enable` field to `false` of comment the middleware block
|
||||
middlewares:
|
||||
# Enable Etag cache header middleware
|
||||
etag:
|
||||
enable: true
|
||||
# Generating a unique request ID and enhancing logging with additional information such as the start and completion of request processing, latency, status code, and other request details.
|
||||
logger:
|
||||
# Enable/Disable the middleware.
|
||||
enable: true
|
||||
# when your code is panicked, the request still returns 500 status code.
|
||||
catch_panic:
|
||||
# Enable/Disable the middleware.
|
||||
enable: true
|
||||
# Timeout for incoming requests middleware. requests that take more time from the configuration will cute and 408 status code will returned.
|
||||
timeout_request:
|
||||
# Enable/Disable the middleware.
|
||||
enable: false
|
||||
# Duration time in milliseconds.
|
||||
timeout: 5000
|
||||
|
||||
cors:
|
||||
enable: true
|
||||
# Set the value of the [`Access-Control-Allow-Origin`][mdn] header
|
||||
# allow_origins:
|
||||
# - https://loco.rs
|
||||
# Set the value of the [`Access-Control-Allow-Headers`][mdn] header
|
||||
# allow_headers:
|
||||
# - Content-Type
|
||||
# Set the value of the [`Access-Control-Allow-Methods`][mdn] header
|
||||
# allow_methods:
|
||||
# - POST
|
||||
# Set the value of the [`Access-Control-Max-Age`][mdn] header in seconds
|
||||
# max_age: 3600
|
||||
|
||||
# Database Configuration
|
||||
database:
|
||||
# Database connection URI
|
||||
uri: '{{ get_env(name="DATABASE_URL", default="postgres://konobangu:konobangu@localhost:5432/konobangu") }}'
|
||||
# When enabled, the sql query will be logged.
|
||||
enable_logging: true
|
||||
# Set the timeout duration when acquiring a connection.
|
||||
connect_timeout: 500
|
||||
# Set the idle duration before closing a connection.
|
||||
idle_timeout: 500
|
||||
# Minimum number of connections for a pool.
|
||||
min_connections: 1
|
||||
# Maximum number of connections for a pool.
|
||||
max_connections: 1
|
||||
# Run migration up when application loaded
|
||||
auto_migrate: true
|
||||
|
||||
storage:
|
||||
data_dir: '{{ get_env(name="STORAGE_DATA_DIR", default="./data") }}'
|
||||
|
||||
mikan:
|
||||
base_url: "https://mikanani.me/"
|
||||
http_client:
|
||||
exponential_backoff_max_retries: 3
|
||||
leaky_bucket_max_tokens: 2
|
||||
leaky_bucket_initial_tokens: 0
|
||||
leaky_bucket_refill_tokens: 1
|
||||
leaky_bucket_refill_interval: 500
|
||||
|
||||
auth:
|
||||
auth_type: '{{ get_env(name="AUTH_TYPE", default = "basic") }}'
|
||||
basic_user: '{{ get_env(name="BASIC_USER", default = "konobangu") }}'
|
||||
basic_password: '{{ get_env(name="BASIC_PASSWORD", default = "konobangu") }}'
|
||||
oidc_issuer: '{{ get_env(name="OIDC_ISSUER", default = "") }}'
|
||||
oidc_audience: '{{ get_env(name="OIDC_AUDIENCE", default = "") }}'
|
||||
oidc_client_id: '{{ get_env(name="OIDC_CLIENT_ID", default = "") }}'
|
||||
oidc_client_secret: '{{ get_env(name="OIDC_CLIENT_SECRET", default = "") }}'
|
||||
oidc_extra_scopes: '{{ get_env(name="OIDC_EXTRA_SCOPES", default = "") }}'
|
||||
oidc_extra_claim_key: '{{ get_env(name="OIDC_EXTRA_CLAIM_KEY", default = "") }}'
|
||||
oidc_extra_claim_value: '{{ get_env(name="OIDC_EXTRA_CLAIM_VALUE", default = "") }}'
|
||||
|
||||
graphql:
|
||||
depth_limit: null
|
||||
complexity_limit: null
|
@ -1,7 +1,75 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use core::f64;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct GraphQLConfig {
|
||||
pub depth_limit: Option<usize>,
|
||||
pub complexity_limit: Option<usize>,
|
||||
use serde::{
|
||||
Deserialize, Deserializer, Serialize, Serializer,
|
||||
de::{self, Unexpected},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct OnlyInfOrNaN(f64);
|
||||
|
||||
impl OnlyInfOrNaN {
|
||||
pub fn inf() -> Self {
|
||||
OnlyInfOrNaN(f64::INFINITY)
|
||||
}
|
||||
|
||||
pub fn nan() -> Self {
|
||||
OnlyInfOrNaN(f64::NAN)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<OnlyInfOrNaN> for Option<usize> {
|
||||
fn from(_: OnlyInfOrNaN) -> Self {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for OnlyInfOrNaN {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_f64(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for OnlyInfOrNaN {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let value = f64::deserialize(deserializer)?;
|
||||
if value.is_nan() {
|
||||
Ok(Self::nan())
|
||||
} else if value.is_infinite() {
|
||||
Ok(Self::inf())
|
||||
} else {
|
||||
Err(de::Error::invalid_value(
|
||||
Unexpected::Float(value),
|
||||
&"a NaN or a Inf",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum GraphQLLimitNum {
|
||||
Num(usize),
|
||||
Adhoc(OnlyInfOrNaN),
|
||||
}
|
||||
|
||||
impl From<GraphQLLimitNum> for Option<usize> {
|
||||
fn from(value: GraphQLLimitNum) -> Self {
|
||||
match value {
|
||||
GraphQLLimitNum::Adhoc(v) => v.into(),
|
||||
GraphQLLimitNum::Num(v) => Some(v),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct GraphQLConfig {
|
||||
pub depth_limit: Option<GraphQLLimitNum>,
|
||||
pub complexity_limit: Option<GraphQLLimitNum>,
|
||||
}
|
||||
|
@ -14,7 +14,11 @@ impl GraphQLService {
|
||||
config: GraphQLConfig,
|
||||
db: DatabaseConnection,
|
||||
) -> RResult<Self> {
|
||||
let schema = schema_root::schema(db, config.depth_limit, config.complexity_limit)?;
|
||||
let schema = schema_root::schema(
|
||||
db,
|
||||
config.depth_limit.and_then(|l| l.into()),
|
||||
config.complexity_limit.and_then(|l| l.into()),
|
||||
)?;
|
||||
Ok(Self { schema })
|
||||
}
|
||||
}
|
||||
|
@ -24,5 +24,5 @@ pub mod sync;
|
||||
pub mod tasks;
|
||||
#[cfg(test)]
|
||||
pub mod test_utils;
|
||||
pub mod views;
|
||||
pub mod utils;
|
||||
pub mod web;
|
||||
|
@ -1,3 +1 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@import "tailwindcss";
|
||||
|
1
apps/recorder/src/utils/mod.rs
Normal file
1
apps/recorder/src/utils/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
|
@ -1 +0,0 @@
|
||||
pub mod subscribers;
|
@ -1,13 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::models::subscribers;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct CurrentResponse {}
|
||||
|
||||
impl CurrentResponse {
|
||||
#[must_use]
|
||||
pub fn new(_user: &subscribers::Model) -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
@ -2,10 +2,10 @@ import { type Fetcher, createGraphiQLFetcher } from '@graphiql/toolkit';
|
||||
import { createFileRoute } from '@tanstack/react-router';
|
||||
import GraphiQL from 'graphiql';
|
||||
import { useMemo } from 'react';
|
||||
import { beforeLoadGuard } from '../../../auth/guard';
|
||||
import 'graphiql/graphiql.css';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { beforeLoadGuard } from '../../../auth/guard';
|
||||
import { useAuth } from '../../../auth/hooks';
|
||||
import 'graphiql/graphiql.css';
|
||||
|
||||
export const Route = createFileRoute('/graphql/')({
|
||||
component: RouteComponent,
|
||||
@ -32,5 +32,5 @@ function RouteComponent() {
|
||||
[oidcSecurityService]
|
||||
);
|
||||
|
||||
return <GraphiQL fetcher={fetcher} className="h-svh" />;
|
||||
return <GraphiQL fetcher={fetcher} className="!h-svh" />;
|
||||
}
|
||||
|
@ -1 +0,0 @@
|
||||
export { config as default } from '@konobangu/tailwind-config/config';
|
@ -1,12 +1,11 @@
|
||||
{
|
||||
"extends": "@konobangu/typescript-config/base.json",
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"lib": ["DOM", "ES2024", "DOM.AsyncIterable", "DOM.Iterable"],
|
||||
"rootDir": ".",
|
||||
"composite": true,
|
||||
"jsx": "react-jsx",
|
||||
"noEmit": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"strict": true,
|
||||
|
45
apps/storybook/.gitignore
vendored
45
apps/storybook/.gitignore
vendored
@ -1,45 +0,0 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/versions
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# env files (can opt-in for commiting if needed)
|
||||
.env*
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
*storybook.log
|
||||
|
||||
# storybook
|
||||
storybook-static/
|
@ -1,30 +0,0 @@
|
||||
import { dirname, join } from 'node:path';
|
||||
import type { StorybookConfig } from '@storybook/nextjs';
|
||||
|
||||
/**
|
||||
* This function is used to resolve the absolute path of a package.
|
||||
* It is needed in projects that use Yarn PnP or are set up within a monorepo.
|
||||
*/
|
||||
const getAbsolutePath = (value: string) =>
|
||||
dirname(require.resolve(join(value, 'package.json')));
|
||||
|
||||
const config: StorybookConfig = {
|
||||
stories: [
|
||||
'../stories/**/*.mdx',
|
||||
'../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)',
|
||||
],
|
||||
addons: [
|
||||
getAbsolutePath('@storybook/addon-onboarding'),
|
||||
getAbsolutePath('@storybook/addon-essentials'),
|
||||
getAbsolutePath('@chromatic-com/storybook'),
|
||||
getAbsolutePath('@storybook/addon-interactions'),
|
||||
getAbsolutePath('@storybook/addon-themes'),
|
||||
],
|
||||
framework: {
|
||||
name: getAbsolutePath('@storybook/nextjs'),
|
||||
options: {},
|
||||
},
|
||||
staticDirs: ['../public'],
|
||||
};
|
||||
|
||||
export default config;
|
@ -1,17 +0,0 @@
|
||||
<!-- https://github.com/vercel/geist-font/issues/72 -->
|
||||
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Geist+Mono:wght@100..900&family=Geist:wght@100..900&display=swap" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--font-geist-sans: "Geist", sans-serif;
|
||||
--font-geist-mono: "Geist Mono", monospace;
|
||||
}
|
||||
body {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
touch-action: manipulation;
|
||||
}
|
||||
</style>
|
@ -1,53 +0,0 @@
|
||||
import { Toaster } from '@konobangu/design-system/components/ui/sonner';
|
||||
import { TooltipProvider } from '@konobangu/design-system/components/ui/tooltip';
|
||||
import { ThemeProvider } from '@konobangu/design-system/providers/theme';
|
||||
import { withThemeByClassName } from '@storybook/addon-themes';
|
||||
import type { Preview } from '@storybook/react';
|
||||
|
||||
import '@konobangu/design-system/styles/globals.css';
|
||||
|
||||
const preview: Preview = {
|
||||
parameters: {
|
||||
controls: {
|
||||
matchers: {
|
||||
color: /(background|color)$/i,
|
||||
date: /Date$/i,
|
||||
},
|
||||
},
|
||||
chromatic: {
|
||||
modes: {
|
||||
light: {
|
||||
theme: 'light',
|
||||
className: 'light',
|
||||
},
|
||||
dark: {
|
||||
theme: 'dark',
|
||||
className: 'dark',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
decorators: [
|
||||
withThemeByClassName({
|
||||
themes: {
|
||||
light: 'light',
|
||||
dark: 'dark',
|
||||
},
|
||||
defaultTheme: 'light',
|
||||
}),
|
||||
(Story) => {
|
||||
return (
|
||||
<div className="bg-background">
|
||||
<ThemeProvider>
|
||||
<TooltipProvider>
|
||||
<Story />
|
||||
</TooltipProvider>
|
||||
<Toaster />
|
||||
</ThemeProvider>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default preview;
|
@ -1,40 +0,0 @@
|
||||
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/pages/api-reference/create-next-app).
|
||||
|
||||
## Getting Started
|
||||
|
||||
First, run the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
# or
|
||||
pnpm dev
|
||||
# or
|
||||
bun dev
|
||||
```
|
||||
|
||||
Open [http://localhost:5000](http://localhost:5000) with your browser to see the result.
|
||||
|
||||
You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file.
|
||||
|
||||
[API routes](https://nextjs.org/docs/pages/building-your-application/routing/api-routes) can be accessed on [http://localhost:5000/api/hello](http://localhost:5000/api/hello). This endpoint can be edited in `pages/api/hello.ts`.
|
||||
|
||||
The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/pages/building-your-application/routing/api-routes) instead of React pages.
|
||||
|
||||
This project uses [`next/font`](https://nextjs.org/docs/pages/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Next.js, take a look at the following resources:
|
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn-pages-router) - an interactive Next.js tutorial.
|
||||
|
||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
||||
|
||||
## Deploy on Vercel
|
||||
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/pages/building-your-application/deploying) for more details.
|
@ -1,7 +0,0 @@
|
||||
import type { NextConfig } from 'next';
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
reactStrictMode: true,
|
||||
};
|
||||
|
||||
export default nextConfig;
|
@ -1,39 +0,0 @@
|
||||
{
|
||||
"name": "storybook",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "storybook dev -p 6006",
|
||||
"build-storybook": "storybook build",
|
||||
"chromatic": "chromatic --exit-zero-on-changes",
|
||||
"clean": "git clean -xdf .cache .turbo dist node_modules",
|
||||
"typecheck": "tsc --noEmit --emitDeclarationOnly false"
|
||||
},
|
||||
"dependencies": {
|
||||
"@konobangu/design-system": "workspace:*",
|
||||
"lucide-react": "^0.468.0",
|
||||
"next": "^15.1.4",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@chromatic-com/storybook": "^3.2.3",
|
||||
"@konobangu/typescript-config": "workspace:*",
|
||||
"@storybook/addon-essentials": "^8.4.7",
|
||||
"@storybook/addon-interactions": "^8.4.7",
|
||||
"@storybook/addon-onboarding": "^8.4.7",
|
||||
"@storybook/addon-themes": "^8.4.7",
|
||||
"@storybook/blocks": "^8.4.7",
|
||||
"@storybook/nextjs": "^8.4.7",
|
||||
"@storybook/react": "^8.4.7",
|
||||
"@storybook/test": "^8.4.7",
|
||||
"@types/node": "^22.10.6",
|
||||
"@types/react": "^19.0.7",
|
||||
"@types/react-dom": "^19.0.3",
|
||||
"chromatic": "^11.23.0",
|
||||
"postcss": "^8.5.1",
|
||||
"storybook": "^8.4.7",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "^5.7.3"
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
/** @type {import('postcss-load-config').Config} */
|
||||
const config = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
Binary file not shown.
Before Width: | Height: | Size: 25 KiB |
@ -1,60 +0,0 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from '@konobangu/design-system/components/ui/accordion';
|
||||
|
||||
/**
|
||||
* A vertically stacked set of interactive headings that each reveal a section
|
||||
* of content.
|
||||
*/
|
||||
const meta = {
|
||||
title: 'ui/Accordion',
|
||||
component: Accordion,
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
type: {
|
||||
options: ['single', 'multiple'],
|
||||
control: { type: 'radio' },
|
||||
},
|
||||
},
|
||||
args: {
|
||||
type: 'single',
|
||||
collapsible: true,
|
||||
},
|
||||
render: (args) => (
|
||||
<Accordion {...args}>
|
||||
<AccordionItem value="item-1">
|
||||
<AccordionTrigger>Is it accessible?</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Yes. It adheres to the WAI-ARIA design pattern.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="item-2">
|
||||
<AccordionTrigger>Is it styled?</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Yes. It comes with default styles that matches the other components'
|
||||
aesthetic.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="item-3">
|
||||
<AccordionTrigger>Is it animated?</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Yes. It's animated by default, but you can disable it if you prefer.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
),
|
||||
} satisfies Meta<typeof Accordion>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
/**
|
||||
* The default behavior of the accordion allows only one item to be open.
|
||||
*/
|
||||
export const Default: Story = {};
|
@ -1,54 +0,0 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
AlertDialogTrigger,
|
||||
} from '@konobangu/design-system/components/ui/alert-dialog';
|
||||
|
||||
/**
|
||||
* A modal dialog that interrupts the user with important content and expects
|
||||
* a response.
|
||||
*/
|
||||
const meta = {
|
||||
title: 'ui/AlertDialog',
|
||||
component: AlertDialog,
|
||||
tags: ['autodocs'],
|
||||
argTypes: {},
|
||||
render: (args) => (
|
||||
<AlertDialog {...args}>
|
||||
<AlertDialogTrigger>Open</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Are you sure absolutely sure?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This action cannot be undone. This will permanently delete your
|
||||
account and remove your data from our servers.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction>Continue</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
),
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
} satisfies Meta<typeof AlertDialog>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
/**
|
||||
* The default form of the alert dialog.
|
||||
*/
|
||||
export const Default: Story = {};
|
@ -1,60 +0,0 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { AlertCircle } from 'lucide-react';
|
||||
|
||||
import {
|
||||
Alert,
|
||||
AlertDescription,
|
||||
AlertTitle,
|
||||
} from '@konobangu/design-system/components/ui/alert';
|
||||
|
||||
/**
|
||||
* Displays a callout for user attention.
|
||||
*/
|
||||
const meta = {
|
||||
title: 'ui/Alert',
|
||||
component: Alert,
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
variant: {
|
||||
options: ['default', 'destructive'],
|
||||
control: { type: 'radio' },
|
||||
},
|
||||
},
|
||||
args: {
|
||||
variant: 'default',
|
||||
},
|
||||
render: (args) => (
|
||||
<Alert {...args}>
|
||||
<AlertTitle>Heads up!</AlertTitle>
|
||||
<AlertDescription>
|
||||
You can add components to your app using the cli.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
),
|
||||
} satisfies Meta<typeof Alert>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
/**
|
||||
* The default form of the alert.
|
||||
*/
|
||||
export const Default: Story = {};
|
||||
|
||||
/**
|
||||
* Use the `destructive` alert to indicate a destructive action.
|
||||
*/
|
||||
export const Destructive: Story = {
|
||||
render: (args) => (
|
||||
<Alert {...args}>
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertTitle>Error</AlertTitle>
|
||||
<AlertDescription>
|
||||
Your session has expired. Please log in again.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
),
|
||||
args: {
|
||||
variant: 'destructive',
|
||||
},
|
||||
};
|
@ -1,71 +0,0 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import Image from 'next/image';
|
||||
|
||||
import { AspectRatio } from '@konobangu/design-system/components/ui/aspect-ratio';
|
||||
|
||||
/**
|
||||
* Displays content within a desired ratio.
|
||||
*/
|
||||
const meta: Meta<typeof AspectRatio> = {
|
||||
title: 'ui/AspectRatio',
|
||||
component: AspectRatio,
|
||||
tags: ['autodocs'],
|
||||
argTypes: {},
|
||||
render: (args) => (
|
||||
<AspectRatio {...args} className="bg-slate-50 dark:bg-slate-800">
|
||||
<Image
|
||||
src="https://images.unsplash.com/photo-1576075796033-848c2a5f3696?w=800&dpr=2&q=80"
|
||||
alt="Photo by Alvaro Pinot"
|
||||
fill
|
||||
className="rounded-md object-cover"
|
||||
/>
|
||||
</AspectRatio>
|
||||
),
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<div className="w-1/2">
|
||||
<Story />
|
||||
</div>
|
||||
),
|
||||
],
|
||||
} satisfies Meta<typeof AspectRatio>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
/**
|
||||
* The default form of the aspect ratio.
|
||||
*/
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
ratio: 16 / 9,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Use the `1:1` aspect ratio to display a square image.
|
||||
*/
|
||||
export const Square: Story = {
|
||||
args: {
|
||||
ratio: 1,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Use the `4:3` aspect ratio to display a landscape image.
|
||||
*/
|
||||
export const Landscape: Story = {
|
||||
args: {
|
||||
ratio: 4 / 3,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Use the `2.35:1` aspect ratio to display a cinemascope image.
|
||||
*/
|
||||
export const Cinemascope: Story = {
|
||||
args: {
|
||||
ratio: 2.35 / 1,
|
||||
},
|
||||
};
|
@ -1,35 +0,0 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import {
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
AvatarImage,
|
||||
} from '@konobangu/design-system/components/ui/avatar';
|
||||
|
||||
/**
|
||||
* An image element with a fallback for representing the user.
|
||||
*/
|
||||
const meta = {
|
||||
title: 'ui/Avatar',
|
||||
component: Avatar,
|
||||
tags: ['autodocs'],
|
||||
argTypes: {},
|
||||
render: (args) => (
|
||||
<Avatar {...args}>
|
||||
<AvatarImage src="https://github.com/shadcn.png" />
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
</Avatar>
|
||||
),
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
} satisfies Meta<typeof Avatar>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
/**
|
||||
* The default form of the avatar.
|
||||
*/
|
||||
export const Default: Story = {};
|
@ -1,62 +0,0 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { Badge } from '@konobangu/design-system/components/ui/badge';
|
||||
|
||||
/**
|
||||
* Displays a badge or a component that looks like a badge.
|
||||
*/
|
||||
const meta = {
|
||||
title: 'ui/Badge',
|
||||
component: Badge,
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
children: {
|
||||
control: 'text',
|
||||
},
|
||||
},
|
||||
args: {
|
||||
children: 'Badge',
|
||||
},
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
} satisfies Meta<typeof Badge>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
/**
|
||||
* The default form of the badge.
|
||||
*/
|
||||
export const Default: Story = {};
|
||||
|
||||
/**
|
||||
* Use the `secondary` badge to call for less urgent information, blending
|
||||
* into the interface while still signaling minor updates or statuses.
|
||||
*/
|
||||
export const Secondary: Story = {
|
||||
args: {
|
||||
variant: 'secondary',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Use the `destructive` badge to indicate errors, alerts, or the need for
|
||||
* immediate attention.
|
||||
*/
|
||||
export const Destructive: Story = {
|
||||
args: {
|
||||
variant: 'destructive',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Use the `outline` badge for overlaying without obscuring interface details,
|
||||
* emphasizing clarity and subtlety..
|
||||
*/
|
||||
export const Outline: Story = {
|
||||
args: {
|
||||
variant: 'outline',
|
||||
},
|
||||
};
|
@ -1,78 +0,0 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { ArrowRightSquare } from 'lucide-react';
|
||||
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbList,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
} from '@konobangu/design-system/components/ui/breadcrumb';
|
||||
|
||||
/**
|
||||
* Displays the path to the current resource using a hierarchy of links.
|
||||
*/
|
||||
const meta = {
|
||||
title: 'ui/Breadcrumb',
|
||||
component: Breadcrumb,
|
||||
tags: ['autodocs'],
|
||||
argTypes: {},
|
||||
args: {},
|
||||
render: (args) => (
|
||||
<Breadcrumb {...args}>
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink>Home</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink>Components</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage>Breadcrumb</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
),
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
} satisfies Meta<typeof Breadcrumb>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
/**
|
||||
* Displays the path of links to the current resource.
|
||||
*/
|
||||
export const Default: Story = {};
|
||||
|
||||
/**
|
||||
* Displays the path with a custom icon for the separator.
|
||||
*/
|
||||
export const WithCustomSeparator: Story = {
|
||||
render: (args) => (
|
||||
<Breadcrumb {...args}>
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink>Home</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator>
|
||||
<ArrowRightSquare />
|
||||
</BreadcrumbSeparator>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink>Components</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator>
|
||||
<ArrowRightSquare />
|
||||
</BreadcrumbSeparator>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage>Breadcrumb</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
),
|
||||
};
|
@ -1,157 +0,0 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { Loader2, Mail } from 'lucide-react';
|
||||
|
||||
import { Button } from '@konobangu/design-system/components/ui/button';
|
||||
|
||||
/**
|
||||
* Displays a button or a component that looks like a button.
|
||||
*/
|
||||
const meta = {
|
||||
title: 'ui/Button',
|
||||
component: Button,
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
children: {
|
||||
control: 'text',
|
||||
},
|
||||
},
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
args: {
|
||||
variant: 'default',
|
||||
size: 'default',
|
||||
children: 'Button',
|
||||
},
|
||||
} satisfies Meta<typeof Button>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
/**
|
||||
* The default form of the button, used for primary actions and commands.
|
||||
*/
|
||||
export const Default: Story = {};
|
||||
|
||||
/**
|
||||
* Use the `outline` button to reduce emphasis on secondary actions, such as
|
||||
* canceling or dismissing a dialog.
|
||||
*/
|
||||
export const Outline: Story = {
|
||||
args: {
|
||||
variant: 'outline',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Use the `ghost` button is minimalistic and subtle, for less intrusive
|
||||
* actions.
|
||||
*/
|
||||
export const Ghost: Story = {
|
||||
args: {
|
||||
variant: 'ghost',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Use the `secondary` button to call for less emphasized actions, styled to
|
||||
* complement the primary button while being less conspicuous.
|
||||
*/
|
||||
export const Secondary: Story = {
|
||||
args: {
|
||||
variant: 'secondary',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Use the `destructive` button to indicate errors, alerts, or the need for
|
||||
* immediate attention.
|
||||
*/
|
||||
export const Destructive: Story = {
|
||||
args: {
|
||||
variant: 'destructive',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Use the `link` button to reduce emphasis on tertiary actions, such as
|
||||
* hyperlink or navigation, providing a text-only interactive element.
|
||||
*/
|
||||
export const Link: Story = {
|
||||
args: {
|
||||
variant: 'link',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Add the `disabled` prop to a button to prevent interactions and add a
|
||||
* loading indicator, such as a spinner, to signify an in-progress action.
|
||||
*/
|
||||
export const Loading: Story = {
|
||||
render: (args) => (
|
||||
<Button {...args}>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
Button
|
||||
</Button>
|
||||
),
|
||||
args: {
|
||||
...Outline.args,
|
||||
disabled: true,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Add an icon element to a button to enhance visual communication and
|
||||
* providing additional context for the action.
|
||||
*/
|
||||
export const WithIcon: Story = {
|
||||
render: (args) => (
|
||||
<Button {...args}>
|
||||
<Mail className="mr-2 h-4 w-4" /> Login with Email Button
|
||||
</Button>
|
||||
),
|
||||
args: {
|
||||
...Secondary.args,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Use the `sm` size for a smaller button, suitable for interfaces needing
|
||||
* compact elements without sacrificing usability.
|
||||
*/
|
||||
export const Small: Story = {
|
||||
args: {
|
||||
size: 'sm',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Use the `lg` size for a larger button, offering better visibility and
|
||||
* easier interaction for users.
|
||||
*/
|
||||
export const Large: Story = {
|
||||
args: {
|
||||
size: 'lg',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Use the "icon" size for a button with only an icon.
|
||||
*/
|
||||
export const Icon: Story = {
|
||||
args: {
|
||||
...Secondary.args,
|
||||
size: 'icon',
|
||||
children: <Mail />,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Add the `disabled` prop to prevent interactions with the button.
|
||||
*/
|
||||
export const Disabled: Story = {
|
||||
args: {
|
||||
disabled: true,
|
||||
},
|
||||
};
|
@ -1,81 +0,0 @@
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { addDays } from 'date-fns';
|
||||
|
||||
import { Calendar } from '@konobangu/design-system/components/ui/calendar';
|
||||
|
||||
/**
|
||||
* A date field component that allows users to enter and edit date.
|
||||
*/
|
||||
const meta = {
|
||||
title: 'ui/Calendar',
|
||||
component: Calendar,
|
||||
tags: ['autodocs'],
|
||||
argTypes: {},
|
||||
args: {
|
||||
mode: 'single',
|
||||
selected: new Date(),
|
||||
onSelect: action('onDayClick'),
|
||||
className: 'rounded-md border w-fit',
|
||||
},
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
} satisfies Meta<typeof Calendar>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
/**
|
||||
* The default form of the calendar.
|
||||
*/
|
||||
export const Default: Story = {};
|
||||
|
||||
/**
|
||||
* Use the `multiple` mode to select multiple dates.
|
||||
*/
|
||||
export const Multiple: Story = {
|
||||
args: {
|
||||
min: 1,
|
||||
selected: [new Date(), addDays(new Date(), 2), addDays(new Date(), 8)],
|
||||
mode: 'multiple',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Use the `range` mode to select a range of dates.
|
||||
*/
|
||||
export const Range: Story = {
|
||||
args: {
|
||||
selected: {
|
||||
from: new Date(),
|
||||
to: addDays(new Date(), 7),
|
||||
},
|
||||
mode: 'range',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Use the `disabled` prop to disable specific dates.
|
||||
*/
|
||||
export const Disabled: Story = {
|
||||
args: {
|
||||
disabled: [
|
||||
addDays(new Date(), 1),
|
||||
addDays(new Date(), 2),
|
||||
addDays(new Date(), 3),
|
||||
addDays(new Date(), 5),
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Use the `numberOfMonths` prop to display multiple months.
|
||||
*/
|
||||
export const MultipleMonths: Story = {
|
||||
args: {
|
||||
numberOfMonths: 2,
|
||||
showOutsideDays: false,
|
||||
},
|
||||
};
|
@ -1,75 +0,0 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { BellRing } from 'lucide-react';
|
||||
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@konobangu/design-system/components/ui/card';
|
||||
|
||||
const notifications = [
|
||||
{
|
||||
title: 'Your call has been confirmed.',
|
||||
description: '1 hour ago',
|
||||
},
|
||||
{
|
||||
title: 'You have a new message!',
|
||||
description: '1 hour ago',
|
||||
},
|
||||
{
|
||||
title: 'Your subscription is expiring soon!',
|
||||
description: '2 hours ago',
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Displays a card with header, content, and footer.
|
||||
*/
|
||||
const meta = {
|
||||
title: 'ui/Card',
|
||||
component: Card,
|
||||
tags: ['autodocs'],
|
||||
argTypes: {},
|
||||
args: {
|
||||
className: 'w-96',
|
||||
},
|
||||
render: (args) => (
|
||||
<Card {...args}>
|
||||
<CardHeader>
|
||||
<CardTitle>Notifications</CardTitle>
|
||||
<CardDescription>You have 3 unread messages.</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="grid gap-4">
|
||||
{notifications.map((notification, index) => (
|
||||
<div key={index} className="flex items-center gap-4">
|
||||
<BellRing className="size-6" />
|
||||
<div>
|
||||
<p>{notification.title}</p>
|
||||
<p className="text-foreground/50">{notification.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<button type="button" className="hover:underline">
|
||||
Close
|
||||
</button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
),
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
} satisfies Meta<typeof Card>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
/**
|
||||
* The default form of the card.
|
||||
*/
|
||||
export const Default: Story = {};
|
@ -1,73 +0,0 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import {
|
||||
Carousel,
|
||||
CarouselContent,
|
||||
CarouselItem,
|
||||
CarouselNext,
|
||||
CarouselPrevious,
|
||||
} from '@konobangu/design-system/components/ui/carousel';
|
||||
|
||||
/**
|
||||
* A carousel with motion and swipe built using Embla.
|
||||
*/
|
||||
const meta: Meta<typeof Carousel> = {
|
||||
title: 'ui/Carousel',
|
||||
component: Carousel,
|
||||
tags: ['autodocs'],
|
||||
argTypes: {},
|
||||
args: {
|
||||
className: 'w-full max-w-xs',
|
||||
},
|
||||
render: (args) => (
|
||||
<Carousel {...args}>
|
||||
<CarouselContent>
|
||||
{Array.from({ length: 5 }).map((_, index) => (
|
||||
<CarouselItem key={index}>
|
||||
<div className="flex aspect-square items-center justify-center rounded border bg-card p-6">
|
||||
<span className="font-semibold text-4xl">{index + 1}</span>
|
||||
</div>
|
||||
</CarouselItem>
|
||||
))}
|
||||
</CarouselContent>
|
||||
<CarouselPrevious />
|
||||
<CarouselNext />
|
||||
</Carousel>
|
||||
),
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
} satisfies Meta<typeof Carousel>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
/**
|
||||
* The default form of the carousel.
|
||||
*/
|
||||
export const Default: Story = {};
|
||||
|
||||
/**
|
||||
* Use the `basis` utility class to change the size of the carousel.
|
||||
*/
|
||||
export const Size: Story = {
|
||||
render: (args) => (
|
||||
<Carousel {...args} className="mx-12 w-full max-w-xs">
|
||||
<CarouselContent>
|
||||
{Array.from({ length: 5 }).map((_, index) => (
|
||||
<CarouselItem key={index} className="basis-1/3">
|
||||
<div className="flex aspect-square items-center justify-center rounded border bg-card p-6">
|
||||
<span className="font-semibold text-4xl">{index + 1}</span>
|
||||
</div>
|
||||
</CarouselItem>
|
||||
))}
|
||||
</CarouselContent>
|
||||
<CarouselPrevious />
|
||||
<CarouselNext />
|
||||
</Carousel>
|
||||
),
|
||||
args: {
|
||||
className: 'mx-12 w-full max-w-xs',
|
||||
},
|
||||
};
|
@ -1,271 +0,0 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { useMemo } from 'react';
|
||||
import {
|
||||
Area,
|
||||
AreaChart,
|
||||
Bar,
|
||||
BarChart,
|
||||
CartesianGrid,
|
||||
Label,
|
||||
Line,
|
||||
LineChart,
|
||||
Pie,
|
||||
PieChart,
|
||||
XAxis,
|
||||
} from 'recharts';
|
||||
|
||||
import {
|
||||
type ChartConfig,
|
||||
ChartContainer,
|
||||
ChartTooltip,
|
||||
ChartTooltipContent,
|
||||
} from '@konobangu/design-system/components/ui/chart';
|
||||
|
||||
const multiSeriesData = [
|
||||
{ month: 'January', desktop: 186, mobile: 80 },
|
||||
{ month: 'February', desktop: 305, mobile: 200 },
|
||||
{ month: 'March', desktop: 237, mobile: 120 },
|
||||
{ month: 'April', desktop: 73, mobile: 190 },
|
||||
{ month: 'May', desktop: 209, mobile: 130 },
|
||||
{ month: 'June', desktop: 214, mobile: 140 },
|
||||
];
|
||||
|
||||
const multiSeriesConfig = {
|
||||
desktop: {
|
||||
label: 'Desktop',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
mobile: {
|
||||
label: 'Mobile',
|
||||
color: 'hsl(var(--chart-2))',
|
||||
},
|
||||
} satisfies ChartConfig;
|
||||
|
||||
const singleSeriesData = [
|
||||
{ browser: 'chrome', visitors: 275, fill: 'var(--color-chrome)' },
|
||||
{ browser: 'safari', visitors: 200, fill: 'var(--color-safari)' },
|
||||
{ browser: 'other', visitors: 190, fill: 'var(--color-other)' },
|
||||
];
|
||||
|
||||
const singleSeriesConfig = {
|
||||
visitors: {
|
||||
label: 'Visitors',
|
||||
},
|
||||
chrome: {
|
||||
label: 'Chrome',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
safari: {
|
||||
label: 'Safari',
|
||||
color: 'hsl(var(--chart-2))',
|
||||
},
|
||||
other: {
|
||||
label: 'Other',
|
||||
color: 'hsl(var(--chart-5))',
|
||||
},
|
||||
} satisfies ChartConfig;
|
||||
|
||||
/**
|
||||
* Beautiful charts. Built using Recharts. Copy and paste into your apps.
|
||||
*/
|
||||
const meta = {
|
||||
title: 'ui/Chart',
|
||||
component: ChartContainer,
|
||||
tags: ['autodocs'],
|
||||
argTypes: {},
|
||||
args: {
|
||||
children: <div />,
|
||||
},
|
||||
} satisfies Meta<typeof ChartContainer>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
/**
|
||||
* Combine multiple Area components to create a stacked area chart.
|
||||
*/
|
||||
export const StackedAreaChart: Story = {
|
||||
args: {
|
||||
config: multiSeriesConfig,
|
||||
},
|
||||
render: (args) => (
|
||||
<ChartContainer {...args}>
|
||||
<AreaChart
|
||||
accessibilityLayer
|
||||
data={multiSeriesData}
|
||||
margin={{
|
||||
left: 12,
|
||||
right: 12,
|
||||
}}
|
||||
>
|
||||
<CartesianGrid vertical={false} />
|
||||
<XAxis
|
||||
dataKey="month"
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickMargin={8}
|
||||
tickFormatter={(value) => value.slice(0, 3)}
|
||||
/>
|
||||
<ChartTooltip
|
||||
cursor={false}
|
||||
content={<ChartTooltipContent indicator="dot" />}
|
||||
/>
|
||||
<Area
|
||||
dataKey="mobile"
|
||||
type="natural"
|
||||
fill="var(--color-mobile)"
|
||||
fillOpacity={0.4}
|
||||
stroke="var(--color-mobile)"
|
||||
stackId="a"
|
||||
/>
|
||||
<Area
|
||||
dataKey="desktop"
|
||||
type="natural"
|
||||
fill="var(--color-desktop)"
|
||||
fillOpacity={0.4}
|
||||
stroke="var(--color-desktop)"
|
||||
stackId="a"
|
||||
/>
|
||||
</AreaChart>
|
||||
</ChartContainer>
|
||||
),
|
||||
};
|
||||
|
||||
/**
|
||||
* Combine multiple Bar components to create a stacked bar chart.
|
||||
*/
|
||||
export const StackedBarChart: Story = {
|
||||
args: {
|
||||
config: multiSeriesConfig,
|
||||
},
|
||||
render: (args) => (
|
||||
<ChartContainer {...args}>
|
||||
<BarChart accessibilityLayer data={multiSeriesData}>
|
||||
<CartesianGrid vertical={false} />
|
||||
<XAxis
|
||||
dataKey="month"
|
||||
tickLine={false}
|
||||
tickMargin={10}
|
||||
axisLine={false}
|
||||
tickFormatter={(value) => value.slice(0, 3)}
|
||||
/>
|
||||
<ChartTooltip
|
||||
cursor={false}
|
||||
content={<ChartTooltipContent indicator="dashed" />}
|
||||
/>
|
||||
<Bar dataKey="desktop" fill="var(--color-desktop)" radius={4} />
|
||||
<Bar dataKey="mobile" fill="var(--color-mobile)" radius={4} />
|
||||
</BarChart>
|
||||
</ChartContainer>
|
||||
),
|
||||
};
|
||||
|
||||
/**
|
||||
* Combine multiple Line components to create a single line chart.
|
||||
*/
|
||||
export const MultiLineChart: Story = {
|
||||
args: {
|
||||
config: multiSeriesConfig,
|
||||
},
|
||||
render: (args) => (
|
||||
<ChartContainer {...args}>
|
||||
<LineChart
|
||||
accessibilityLayer
|
||||
data={multiSeriesData}
|
||||
margin={{
|
||||
left: 12,
|
||||
right: 12,
|
||||
}}
|
||||
>
|
||||
<CartesianGrid vertical={false} />
|
||||
<XAxis
|
||||
dataKey="month"
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickMargin={8}
|
||||
tickFormatter={(value) => value.slice(0, 3)}
|
||||
/>
|
||||
<ChartTooltip
|
||||
cursor={false}
|
||||
content={<ChartTooltipContent hideLabel />}
|
||||
/>
|
||||
<Line
|
||||
dataKey="desktop"
|
||||
type="natural"
|
||||
stroke="var(--color-desktop)"
|
||||
strokeWidth={2}
|
||||
dot={false}
|
||||
/>
|
||||
<Line
|
||||
dataKey="mobile"
|
||||
type="natural"
|
||||
stroke="var(--color-mobile)"
|
||||
strokeWidth={2}
|
||||
dot={false}
|
||||
/>
|
||||
</LineChart>
|
||||
</ChartContainer>
|
||||
),
|
||||
};
|
||||
|
||||
/**
|
||||
* Combine Pie and Label components to create a doughnut chart.
|
||||
*/
|
||||
export const DoughnutChart: Story = {
|
||||
args: {
|
||||
config: singleSeriesConfig,
|
||||
},
|
||||
render: (args) => {
|
||||
const totalVisitors = useMemo(() => {
|
||||
return singleSeriesData.reduce((acc, curr) => acc + curr.visitors, 0);
|
||||
}, []);
|
||||
return (
|
||||
<ChartContainer {...args}>
|
||||
<PieChart>
|
||||
<ChartTooltip
|
||||
cursor={false}
|
||||
content={<ChartTooltipContent hideLabel />}
|
||||
/>
|
||||
<Pie
|
||||
data={singleSeriesData}
|
||||
dataKey="visitors"
|
||||
nameKey="browser"
|
||||
innerRadius={48}
|
||||
strokeWidth={5}
|
||||
>
|
||||
<Label
|
||||
content={({ viewBox }) => {
|
||||
if (viewBox && 'cx' in viewBox && 'cy' in viewBox) {
|
||||
return (
|
||||
<text
|
||||
x={viewBox.cx}
|
||||
y={viewBox.cy}
|
||||
textAnchor="middle"
|
||||
dominantBaseline="middle"
|
||||
>
|
||||
<tspan
|
||||
x={viewBox.cx}
|
||||
y={viewBox.cy}
|
||||
className="fill-foreground font-bold text-3xl"
|
||||
>
|
||||
{totalVisitors.toLocaleString()}
|
||||
</tspan>
|
||||
<tspan
|
||||
x={viewBox.cx}
|
||||
y={(viewBox.cy || 0) + 24}
|
||||
className="fill-muted-foreground"
|
||||
>
|
||||
Visitors
|
||||
</tspan>
|
||||
</text>
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Pie>
|
||||
</PieChart>
|
||||
</ChartContainer>
|
||||
);
|
||||
},
|
||||
};
|
@ -1,50 +0,0 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { Checkbox } from '@konobangu/design-system/components/ui/checkbox';
|
||||
|
||||
/**
|
||||
* A control that allows the user to toggle between checked and not checked.
|
||||
*/
|
||||
const meta: Meta<typeof Checkbox> = {
|
||||
title: 'ui/Checkbox',
|
||||
component: Checkbox,
|
||||
tags: ['autodocs'],
|
||||
argTypes: {},
|
||||
args: {
|
||||
id: 'terms',
|
||||
disabled: false,
|
||||
},
|
||||
render: (args) => (
|
||||
<div className="flex space-x-2">
|
||||
<Checkbox {...args} />
|
||||
<label
|
||||
htmlFor={args.id}
|
||||
className="font-medium text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50"
|
||||
>
|
||||
Accept terms and conditions
|
||||
</label>
|
||||
</div>
|
||||
),
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
} satisfies Meta<typeof Checkbox>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
/**
|
||||
* The default form of the checkbox.
|
||||
*/
|
||||
export const Default: Story = {};
|
||||
|
||||
/**
|
||||
* Use the `disabled` prop to disable the checkbox.
|
||||
*/
|
||||
export const Disabled: Story = {
|
||||
args: {
|
||||
id: 'disabled-terms',
|
||||
disabled: true,
|
||||
},
|
||||
};
|
@ -1,55 +0,0 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { Info } from 'lucide-react';
|
||||
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from '@konobangu/design-system/components/ui/collapsible';
|
||||
|
||||
/**
|
||||
* An interactive component which expands/collapses a panel.
|
||||
*/
|
||||
const meta = {
|
||||
title: 'ui/Collapsible',
|
||||
component: Collapsible,
|
||||
tags: ['autodocs'],
|
||||
argTypes: {},
|
||||
args: {
|
||||
className: 'w-96',
|
||||
disabled: false,
|
||||
},
|
||||
render: (args) => (
|
||||
<Collapsible {...args}>
|
||||
<CollapsibleTrigger className="flex gap-2">
|
||||
<h3 className="font-semibold">Can I use this in my project?</h3>
|
||||
<Info className="size-6" />
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
Yes. Free to use for personal and commercial projects. No attribution
|
||||
required.
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
),
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
} satisfies Meta<typeof Collapsible>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
/**
|
||||
* The default form of the collapsible.
|
||||
*/
|
||||
export const Default: Story = {};
|
||||
|
||||
/**
|
||||
* Use the `disabled` prop to disable the interaction.
|
||||
*/
|
||||
export const Disabled: Story = {
|
||||
args: {
|
||||
disabled: true,
|
||||
},
|
||||
};
|
@ -1,55 +0,0 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { CommandSeparator } from 'cmdk';
|
||||
|
||||
import {
|
||||
Command,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
CommandList,
|
||||
} from '@konobangu/design-system/components/ui/command';
|
||||
|
||||
/**
|
||||
* Fast, composable, unstyled command menu for React.
|
||||
*/
|
||||
const meta = {
|
||||
title: 'ui/Command',
|
||||
component: Command,
|
||||
tags: ['autodocs'],
|
||||
argTypes: {},
|
||||
args: {
|
||||
className: 'rounded-lg w-96 border shadow-md',
|
||||
},
|
||||
render: (args) => (
|
||||
<Command {...args}>
|
||||
<CommandInput placeholder="Type a command or search..." />
|
||||
<CommandList>
|
||||
<CommandEmpty>No results found.</CommandEmpty>
|
||||
<CommandGroup heading="Suggestions">
|
||||
<CommandItem>Calendar</CommandItem>
|
||||
<CommandItem>Search Emoji</CommandItem>
|
||||
<CommandItem>Calculator</CommandItem>
|
||||
</CommandGroup>
|
||||
<CommandSeparator />
|
||||
<CommandGroup heading="Settings">
|
||||
<CommandItem>Profile</CommandItem>
|
||||
<CommandItem>Billing</CommandItem>
|
||||
<CommandItem>Settings</CommandItem>
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
),
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
} satisfies Meta<typeof Command>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
/**
|
||||
* The default form of the command.
|
||||
*/
|
||||
export const Default: Story = {};
|
@ -1,153 +0,0 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import {
|
||||
ContextMenu,
|
||||
ContextMenuCheckboxItem,
|
||||
ContextMenuContent,
|
||||
ContextMenuItem,
|
||||
ContextMenuLabel,
|
||||
ContextMenuRadioGroup,
|
||||
ContextMenuRadioItem,
|
||||
ContextMenuSeparator,
|
||||
ContextMenuShortcut,
|
||||
ContextMenuSub,
|
||||
ContextMenuSubContent,
|
||||
ContextMenuSubTrigger,
|
||||
ContextMenuTrigger,
|
||||
} from '@konobangu/design-system/components/ui/context-menu';
|
||||
|
||||
/**
|
||||
* Displays a menu to the user — such as a set of actions or functions —
|
||||
* triggered by a button.
|
||||
*/
|
||||
const meta = {
|
||||
title: 'ui/ContextMenu',
|
||||
component: ContextMenu,
|
||||
tags: ['autodocs'],
|
||||
argTypes: {},
|
||||
args: {},
|
||||
render: (args) => (
|
||||
<ContextMenu {...args}>
|
||||
<ContextMenuTrigger className="flex h-48 w-96 items-center justify-center rounded-md border border-dashed bg-accent text-sm">
|
||||
Right click here
|
||||
</ContextMenuTrigger>
|
||||
<ContextMenuContent className="w-32">
|
||||
<ContextMenuItem>Profile</ContextMenuItem>
|
||||
<ContextMenuItem>Billing</ContextMenuItem>
|
||||
<ContextMenuItem>Team</ContextMenuItem>
|
||||
<ContextMenuItem>Subscription</ContextMenuItem>
|
||||
</ContextMenuContent>
|
||||
</ContextMenu>
|
||||
),
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
} satisfies Meta<typeof ContextMenu>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
/**
|
||||
* The default form of the context menu.
|
||||
*/
|
||||
export const Default: Story = {};
|
||||
|
||||
/**
|
||||
* A context menu with shortcuts.
|
||||
*/
|
||||
export const WithShortcuts: Story = {
|
||||
render: (args) => (
|
||||
<ContextMenu {...args}>
|
||||
<ContextMenuTrigger className="flex h-48 w-96 items-center justify-center rounded-md border border-dashed bg-accent text-sm">
|
||||
Right click here
|
||||
</ContextMenuTrigger>
|
||||
<ContextMenuContent className="w-32">
|
||||
<ContextMenuItem>
|
||||
Back
|
||||
<ContextMenuShortcut>⌘[</ContextMenuShortcut>
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem disabled>
|
||||
Forward
|
||||
<ContextMenuShortcut>⌘]</ContextMenuShortcut>
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem>
|
||||
Reload
|
||||
<ContextMenuShortcut>⌘R</ContextMenuShortcut>
|
||||
</ContextMenuItem>
|
||||
</ContextMenuContent>
|
||||
</ContextMenu>
|
||||
),
|
||||
};
|
||||
|
||||
/**
|
||||
* A context menu with a submenu.
|
||||
*/
|
||||
export const WithSubmenu: Story = {
|
||||
render: (args) => (
|
||||
<ContextMenu {...args}>
|
||||
<ContextMenuTrigger className="flex h-48 w-96 items-center justify-center rounded-md border border-dashed bg-accent text-sm">
|
||||
Right click here
|
||||
</ContextMenuTrigger>
|
||||
<ContextMenuContent className="w-32">
|
||||
<ContextMenuItem>
|
||||
New Tab
|
||||
<ContextMenuShortcut>⌘N</ContextMenuShortcut>
|
||||
</ContextMenuItem>
|
||||
<ContextMenuSub>
|
||||
<ContextMenuSubTrigger>More Tools</ContextMenuSubTrigger>
|
||||
<ContextMenuSubContent>
|
||||
<ContextMenuItem>
|
||||
Save Page As...
|
||||
<ContextMenuShortcut>⇧⌘S</ContextMenuShortcut>
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem>Create Shortcut...</ContextMenuItem>
|
||||
<ContextMenuItem>Name Window...</ContextMenuItem>
|
||||
<ContextMenuSeparator />
|
||||
<ContextMenuItem>Developer Tools</ContextMenuItem>
|
||||
</ContextMenuSubContent>
|
||||
</ContextMenuSub>
|
||||
</ContextMenuContent>
|
||||
</ContextMenu>
|
||||
),
|
||||
};
|
||||
|
||||
/**
|
||||
* A context menu with checkboxes.
|
||||
*/
|
||||
export const WithCheckboxes: Story = {
|
||||
render: (args) => (
|
||||
<ContextMenu {...args}>
|
||||
<ContextMenuTrigger className="flex h-48 w-96 items-center justify-center rounded-md border border-dashed bg-accent text-sm">
|
||||
Right click here
|
||||
</ContextMenuTrigger>
|
||||
<ContextMenuContent className="w-64">
|
||||
<ContextMenuCheckboxItem checked>
|
||||
Show Comments
|
||||
<ContextMenuShortcut>⌘⇧C</ContextMenuShortcut>
|
||||
</ContextMenuCheckboxItem>
|
||||
<ContextMenuCheckboxItem>Show Preview</ContextMenuCheckboxItem>
|
||||
</ContextMenuContent>
|
||||
</ContextMenu>
|
||||
),
|
||||
};
|
||||
|
||||
/**
|
||||
* A context menu with a radio group.
|
||||
*/
|
||||
export const WithRadioGroup: Story = {
|
||||
render: (args) => (
|
||||
<ContextMenu {...args}>
|
||||
<ContextMenuTrigger className="flex h-48 w-96 items-center justify-center rounded-md border border-dashed bg-accent text-sm">
|
||||
Right click here
|
||||
</ContextMenuTrigger>
|
||||
<ContextMenuContent className="w-64">
|
||||
<ContextMenuRadioGroup value="light">
|
||||
<ContextMenuLabel inset>Theme</ContextMenuLabel>
|
||||
<ContextMenuRadioItem value="light">Light</ContextMenuRadioItem>
|
||||
<ContextMenuRadioItem value="dark">Dark</ContextMenuRadioItem>
|
||||
</ContextMenuRadioGroup>
|
||||
</ContextMenuContent>
|
||||
</ContextMenu>
|
||||
),
|
||||
};
|
@ -1,62 +0,0 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '@konobangu/design-system/components/ui/dialog';
|
||||
|
||||
/**
|
||||
* A window overlaid on either the primary window or another dialog window,
|
||||
* rendering the content underneath inert.
|
||||
*/
|
||||
const meta = {
|
||||
title: 'ui/Dialog',
|
||||
component: Dialog,
|
||||
tags: ['autodocs'],
|
||||
argTypes: {},
|
||||
render: (args) => (
|
||||
<Dialog {...args}>
|
||||
<DialogTrigger>Open</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Are you absolutely sure?</DialogTitle>
|
||||
<DialogDescription>
|
||||
This action cannot be undone. This will permanently delete your
|
||||
account and remove your data from our servers.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter className="gap-4">
|
||||
<button type="button" className="hover:underline">
|
||||
Cancel
|
||||
</button>
|
||||
<DialogClose>
|
||||
<button
|
||||
type="button"
|
||||
className="rounded bg-primary px-4 py-2 text-primary-foreground"
|
||||
>
|
||||
Continue
|
||||
</button>
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
),
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
} satisfies Meta<typeof Dialog>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
/**
|
||||
* The default form of the dialog.
|
||||
*/
|
||||
export const Default: Story = {};
|
@ -1,58 +0,0 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import {
|
||||
Drawer,
|
||||
DrawerClose,
|
||||
DrawerContent,
|
||||
DrawerDescription,
|
||||
DrawerFooter,
|
||||
DrawerHeader,
|
||||
DrawerTitle,
|
||||
DrawerTrigger,
|
||||
} from '@konobangu/design-system/components/ui/drawer';
|
||||
|
||||
/**
|
||||
* A drawer component for React.
|
||||
*/
|
||||
const meta: Meta<typeof Drawer> = {
|
||||
title: 'ui/Drawer',
|
||||
component: Drawer,
|
||||
tags: ['autodocs'],
|
||||
argTypes: {},
|
||||
render: (args) => (
|
||||
<Drawer {...args}>
|
||||
<DrawerTrigger>Open</DrawerTrigger>
|
||||
<DrawerContent>
|
||||
<DrawerHeader>
|
||||
<DrawerTitle>Are you sure absolutely sure?</DrawerTitle>
|
||||
<DrawerDescription>This action cannot be undone.</DrawerDescription>
|
||||
</DrawerHeader>
|
||||
<DrawerFooter>
|
||||
<button
|
||||
type="button"
|
||||
className="rounded bg-primary px-4 py-2 text-primary-foreground"
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
<DrawerClose>
|
||||
<button type="button" className="hover:underline">
|
||||
Cancel
|
||||
</button>
|
||||
</DrawerClose>
|
||||
</DrawerFooter>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
),
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
/**
|
||||
* The default form of the drawer.
|
||||
*/
|
||||
export const Default: Story = {};
|
@ -1,159 +0,0 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { Mail, Plus, PlusCircle, Search, UserPlus } from 'lucide-react';
|
||||
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuPortal,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuShortcut,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubContent,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuTrigger,
|
||||
} from '@konobangu/design-system/components/ui/dropdown-menu';
|
||||
|
||||
/**
|
||||
* Displays a menu to the user — such as a set of actions or functions —
|
||||
* triggered by a button.
|
||||
*/
|
||||
const meta = {
|
||||
title: 'ui/DropdownMenu',
|
||||
component: DropdownMenu,
|
||||
tags: ['autodocs'],
|
||||
argTypes: {},
|
||||
render: (args) => (
|
||||
<DropdownMenu {...args}>
|
||||
<DropdownMenuTrigger>Open</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-44">
|
||||
<DropdownMenuLabel>My Account</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>Profile</DropdownMenuItem>
|
||||
<DropdownMenuItem>Billing</DropdownMenuItem>
|
||||
<DropdownMenuItem>Team</DropdownMenuItem>
|
||||
<DropdownMenuItem>Subscription</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
),
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
} satisfies Meta<typeof DropdownMenu>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
/**
|
||||
* The default form of the dropdown menu.
|
||||
*/
|
||||
export const Default: Story = {};
|
||||
|
||||
/**
|
||||
* A dropdown menu with shortcuts.
|
||||
*/
|
||||
export const WithShortcuts: Story = {
|
||||
render: (args) => (
|
||||
<DropdownMenu {...args}>
|
||||
<DropdownMenuTrigger>Open</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-44">
|
||||
<DropdownMenuLabel>Controls</DropdownMenuLabel>
|
||||
<DropdownMenuItem>
|
||||
Back
|
||||
<DropdownMenuShortcut>⌘[</DropdownMenuShortcut>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem disabled>
|
||||
Forward
|
||||
<DropdownMenuShortcut>⌘]</DropdownMenuShortcut>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
),
|
||||
};
|
||||
|
||||
/**
|
||||
* A dropdown menu with submenus.
|
||||
*/
|
||||
export const WithSubmenus: Story = {
|
||||
render: (args) => (
|
||||
<DropdownMenu {...args}>
|
||||
<DropdownMenuTrigger>Open</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-44">
|
||||
<DropdownMenuItem>
|
||||
<Search className="mr-2 size-4" />
|
||||
<span>Search</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem>
|
||||
<Plus className="mr-2 size-4" />
|
||||
<span>New Team</span>
|
||||
<DropdownMenuShortcut>⌘+T</DropdownMenuShortcut>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger>
|
||||
<UserPlus className="mr-2 size-4" />
|
||||
<span>Invite users</span>
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuPortal>
|
||||
<DropdownMenuSubContent>
|
||||
<DropdownMenuItem>
|
||||
<Mail className="mr-2 size-4" />
|
||||
<span>Email</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>
|
||||
<PlusCircle className="mr-2 size-4" />
|
||||
<span>More...</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuPortal>
|
||||
</DropdownMenuSub>
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
),
|
||||
};
|
||||
|
||||
/**
|
||||
* A dropdown menu with radio items.
|
||||
*/
|
||||
export const WithRadioItems: Story = {
|
||||
render: (args) => (
|
||||
<DropdownMenu {...args}>
|
||||
<DropdownMenuTrigger>Open</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-44">
|
||||
<DropdownMenuLabel inset>Status</DropdownMenuLabel>
|
||||
<DropdownMenuRadioGroup value="warning">
|
||||
<DropdownMenuRadioItem value="info">Info</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="warning">Warning</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="error">Error</DropdownMenuRadioItem>
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
),
|
||||
};
|
||||
|
||||
/**
|
||||
* A dropdown menu with checkboxes.
|
||||
*/
|
||||
export const WithCheckboxes: Story = {
|
||||
render: (args) => (
|
||||
<DropdownMenu {...args}>
|
||||
<DropdownMenuTrigger>Open</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-44">
|
||||
<DropdownMenuCheckboxItem checked>
|
||||
Autosave
|
||||
<DropdownMenuShortcut>⌘S</DropdownMenuShortcut>
|
||||
</DropdownMenuCheckboxItem>
|
||||
<DropdownMenuCheckboxItem>Show Comments</DropdownMenuCheckboxItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
),
|
||||
};
|
@ -1,85 +0,0 @@
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import * as z from 'zod';
|
||||
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@konobangu/design-system/components/ui/form';
|
||||
|
||||
/**
|
||||
* Building forms with React Hook Form and Zod.
|
||||
*/
|
||||
const meta: Meta<typeof Form> = {
|
||||
title: 'ui/Form',
|
||||
component: Form,
|
||||
tags: ['autodocs'],
|
||||
argTypes: {},
|
||||
render: (args) => <ProfileForm {...args} />,
|
||||
} satisfies Meta<typeof Form>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
const formSchema = z.object({
|
||||
username: z.string().min(2, {
|
||||
message: 'Username must be at least 2 characters.',
|
||||
}),
|
||||
});
|
||||
|
||||
const ProfileForm = (args: Story['args']) => {
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
username: '',
|
||||
},
|
||||
});
|
||||
function onSubmit(values: z.infer<typeof formSchema>) {
|
||||
action('onSubmit')(values);
|
||||
}
|
||||
return (
|
||||
<Form {...args} {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="username"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Username</FormLabel>
|
||||
<FormControl>
|
||||
<input
|
||||
className="w-full rounded-md border border-input bg-background px-3 py-2"
|
||||
placeholder="username"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
This is your public display name.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<button
|
||||
className="rounded bg-primary px-4 py-2 text-primary-foreground"
|
||||
type="submit"
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* The default form of the form.
|
||||
*/
|
||||
export const Default: Story = {};
|
@ -1,49 +0,0 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import {
|
||||
HoverCard,
|
||||
HoverCardContent,
|
||||
HoverCardTrigger,
|
||||
} from '@konobangu/design-system/components/ui/hover-card';
|
||||
|
||||
/**
|
||||
* For sighted users to preview content available behind a link.
|
||||
*/
|
||||
const meta = {
|
||||
title: 'ui/HoverCard',
|
||||
component: HoverCard,
|
||||
tags: ['autodocs'],
|
||||
argTypes: {},
|
||||
args: {},
|
||||
render: (args) => (
|
||||
<HoverCard {...args}>
|
||||
<HoverCardTrigger>Hover</HoverCardTrigger>
|
||||
<HoverCardContent>
|
||||
The React Framework - created and maintained by @vercel.
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
),
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
} satisfies Meta<typeof HoverCard>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
/**
|
||||
* The default form of the hover card.
|
||||
*/
|
||||
export const Default: Story = {};
|
||||
|
||||
/**
|
||||
* Use the `openDelay` and `closeDelay` props to control the delay before the
|
||||
* hover card opens and closes.
|
||||
*/
|
||||
export const Instant: Story = {
|
||||
args: {
|
||||
openDelay: 0,
|
||||
closeDelay: 0,
|
||||
},
|
||||
};
|
@ -1,70 +0,0 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { REGEXP_ONLY_DIGITS_AND_CHARS } from 'input-otp';
|
||||
|
||||
import {
|
||||
InputOTP,
|
||||
InputOTPGroup,
|
||||
InputOTPSeparator,
|
||||
InputOTPSlot,
|
||||
} from '@konobangu/design-system/components/ui/input-otp';
|
||||
|
||||
/**
|
||||
* Accessible one-time password component with copy paste functionality.
|
||||
*/
|
||||
const meta = {
|
||||
title: 'ui/InputOTP',
|
||||
component: InputOTP,
|
||||
tags: ['autodocs'],
|
||||
argTypes: {},
|
||||
args: {
|
||||
maxLength: 6,
|
||||
pattern: REGEXP_ONLY_DIGITS_AND_CHARS,
|
||||
children: null,
|
||||
},
|
||||
|
||||
render: (args) => (
|
||||
<InputOTP {...args} render={undefined}>
|
||||
<InputOTPGroup>
|
||||
<InputOTPSlot index={0} />
|
||||
<InputOTPSlot index={1} />
|
||||
<InputOTPSlot index={2} />
|
||||
<InputOTPSlot index={3} />
|
||||
<InputOTPSlot index={4} />
|
||||
<InputOTPSlot index={5} />
|
||||
</InputOTPGroup>
|
||||
</InputOTP>
|
||||
),
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
} satisfies Meta<typeof InputOTP>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
/**
|
||||
* The default form of the InputOTP field.
|
||||
*/
|
||||
export const Default: Story = {};
|
||||
|
||||
/**
|
||||
* Use multiple groups to separate the input slots.
|
||||
*/
|
||||
export const SeparatedGroup: Story = {
|
||||
render: (args) => (
|
||||
<InputOTP {...args} render={undefined}>
|
||||
<InputOTPGroup>
|
||||
<InputOTPSlot index={0} />
|
||||
<InputOTPSlot index={1} />
|
||||
<InputOTPSlot index={2} />
|
||||
</InputOTPGroup>
|
||||
<InputOTPSeparator />
|
||||
<InputOTPGroup>
|
||||
<InputOTPSlot index={3} />
|
||||
<InputOTPSlot index={4} />
|
||||
<InputOTPSlot index={5} />
|
||||
</InputOTPGroup>
|
||||
</InputOTP>
|
||||
),
|
||||
};
|
@ -1,84 +0,0 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { Input } from '@konobangu/design-system/components/ui/input';
|
||||
|
||||
/**
|
||||
* Displays a form input field or a component that looks like an input field.
|
||||
*/
|
||||
const meta = {
|
||||
title: 'ui/Input',
|
||||
component: Input,
|
||||
tags: ['autodocs'],
|
||||
argTypes: {},
|
||||
args: {
|
||||
className: 'w-96',
|
||||
type: 'email',
|
||||
placeholder: 'Email',
|
||||
disabled: false,
|
||||
},
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
} satisfies Meta<typeof Input>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
/**
|
||||
* The default form of the input field.
|
||||
*/
|
||||
export const Default: Story = {};
|
||||
|
||||
/**
|
||||
* Use the `disabled` prop to make the input non-interactive and appears faded,
|
||||
* indicating that input is not currently accepted.
|
||||
*/
|
||||
export const Disabled: Story = {
|
||||
args: { disabled: true },
|
||||
};
|
||||
|
||||
/**
|
||||
* Use the `Label` component to includes a clear, descriptive label above or
|
||||
* alongside the input area to guide users.
|
||||
*/
|
||||
export const WithLabel: Story = {
|
||||
render: (args) => (
|
||||
<div className="grid items-center gap-1.5">
|
||||
<label htmlFor="email">{args.placeholder}</label>
|
||||
<Input {...args} id="email" />
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
/**
|
||||
* Use a text element below the input field to provide additional instructions
|
||||
* or information to users.
|
||||
*/
|
||||
export const WithHelperText: Story = {
|
||||
render: (args) => (
|
||||
<div className="grid items-center gap-1.5">
|
||||
<label htmlFor="email-2">{args.placeholder}</label>
|
||||
<Input {...args} id="email-2" />
|
||||
<p className="text-foreground/50 text-sm">Enter your email address.</p>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
/**
|
||||
* Use the `Button` component to indicate that the input field can be submitted
|
||||
* or used to trigger an action.
|
||||
*/
|
||||
export const WithButton: Story = {
|
||||
render: (args) => (
|
||||
<div className="flex items-center space-x-2">
|
||||
<Input {...args} />
|
||||
<button
|
||||
className="rounded bg-primary px-4 py-2 text-primary-foreground"
|
||||
type="submit"
|
||||
>
|
||||
Subscribe
|
||||
</button>
|
||||
</div>
|
||||
),
|
||||
};
|
@ -1,30 +0,0 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { Label } from '@konobangu/design-system/components/ui/label';
|
||||
|
||||
/**
|
||||
* Renders an accessible label associated with controls.
|
||||
*/
|
||||
const meta = {
|
||||
title: 'ui/Label',
|
||||
component: Label,
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
children: {
|
||||
control: { type: 'text' },
|
||||
},
|
||||
},
|
||||
args: {
|
||||
children: 'Your email address',
|
||||
htmlFor: 'email',
|
||||
},
|
||||
} satisfies Meta<typeof Label>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof Label>;
|
||||
|
||||
/**
|
||||
* The default form of the label.
|
||||
*/
|
||||
export const Default: Story = {};
|
@ -1,126 +0,0 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import {
|
||||
Menubar,
|
||||
MenubarCheckboxItem,
|
||||
MenubarContent,
|
||||
MenubarGroup,
|
||||
MenubarItem,
|
||||
MenubarLabel,
|
||||
MenubarMenu,
|
||||
MenubarRadioGroup,
|
||||
MenubarRadioItem,
|
||||
MenubarSeparator,
|
||||
MenubarShortcut,
|
||||
MenubarSub,
|
||||
MenubarSubContent,
|
||||
MenubarSubTrigger,
|
||||
MenubarTrigger,
|
||||
} from '@konobangu/design-system/components/ui/menubar';
|
||||
|
||||
/**
|
||||
* A visually persistent menu common in desktop applications that provides
|
||||
* quick access to a consistent set of commands.
|
||||
*/
|
||||
const meta = {
|
||||
title: 'ui/Menubar',
|
||||
component: Menubar,
|
||||
tags: ['autodocs'],
|
||||
argTypes: {},
|
||||
|
||||
render: (args) => (
|
||||
<Menubar {...args}>
|
||||
<MenubarMenu>
|
||||
<MenubarTrigger>File</MenubarTrigger>
|
||||
<MenubarContent>
|
||||
<MenubarItem>
|
||||
New Tab <MenubarShortcut>⌘T</MenubarShortcut>
|
||||
</MenubarItem>
|
||||
<MenubarItem>New Window</MenubarItem>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem disabled>Share</MenubarItem>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem>Print</MenubarItem>
|
||||
</MenubarContent>
|
||||
</MenubarMenu>
|
||||
</Menubar>
|
||||
),
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
} satisfies Meta<typeof Menubar>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
/**
|
||||
* The default form of the menubar.
|
||||
*/
|
||||
export const Default: Story = {};
|
||||
|
||||
/**
|
||||
* A menubar with a submenu.
|
||||
*/
|
||||
export const WithSubmenu: Story = {
|
||||
render: (args) => (
|
||||
<Menubar {...args}>
|
||||
<MenubarMenu>
|
||||
<MenubarTrigger>Actions</MenubarTrigger>
|
||||
<MenubarContent>
|
||||
<MenubarItem>Download</MenubarItem>
|
||||
<MenubarSub>
|
||||
<MenubarSubTrigger>Share</MenubarSubTrigger>
|
||||
<MenubarSubContent>
|
||||
<MenubarItem>Email link</MenubarItem>
|
||||
<MenubarItem>Messages</MenubarItem>
|
||||
<MenubarItem>Notes</MenubarItem>
|
||||
</MenubarSubContent>
|
||||
</MenubarSub>
|
||||
</MenubarContent>
|
||||
</MenubarMenu>
|
||||
</Menubar>
|
||||
),
|
||||
};
|
||||
|
||||
/**
|
||||
* A menubar with radio items.
|
||||
*/
|
||||
export const WithRadioItems: Story = {
|
||||
render: (args) => (
|
||||
<Menubar {...args}>
|
||||
<MenubarMenu>
|
||||
<MenubarTrigger>View</MenubarTrigger>
|
||||
<MenubarContent>
|
||||
<MenubarLabel inset>Device Size</MenubarLabel>
|
||||
<MenubarRadioGroup value="md">
|
||||
<MenubarRadioItem value="sm">Small</MenubarRadioItem>
|
||||
<MenubarRadioItem value="md">Medium</MenubarRadioItem>
|
||||
<MenubarRadioItem value="lg">Large</MenubarRadioItem>
|
||||
</MenubarRadioGroup>
|
||||
</MenubarContent>
|
||||
</MenubarMenu>
|
||||
</Menubar>
|
||||
),
|
||||
};
|
||||
|
||||
/**
|
||||
* A menubar with checkbox items.
|
||||
*/
|
||||
export const WithCheckboxItems: Story = {
|
||||
render: (args) => (
|
||||
<Menubar {...args}>
|
||||
<MenubarMenu>
|
||||
<MenubarTrigger>Filters</MenubarTrigger>
|
||||
<MenubarContent>
|
||||
<MenubarItem>Show All</MenubarItem>
|
||||
<MenubarGroup>
|
||||
<MenubarCheckboxItem checked>Unread</MenubarCheckboxItem>
|
||||
<MenubarCheckboxItem checked>Important</MenubarCheckboxItem>
|
||||
<MenubarCheckboxItem>Flagged</MenubarCheckboxItem>
|
||||
</MenubarGroup>
|
||||
</MenubarContent>
|
||||
</MenubarMenu>
|
||||
</Menubar>
|
||||
),
|
||||
};
|
@ -1,79 +0,0 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import {
|
||||
NavigationMenu,
|
||||
NavigationMenuContent,
|
||||
NavigationMenuItem,
|
||||
NavigationMenuLink,
|
||||
NavigationMenuList,
|
||||
NavigationMenuTrigger,
|
||||
navigationMenuTriggerStyle,
|
||||
} from '@konobangu/design-system/components/ui/navigation-menu';
|
||||
|
||||
/**
|
||||
* A collection of links for navigating websites.
|
||||
*/
|
||||
const meta = {
|
||||
title: 'ui/NavigationMenu',
|
||||
component: NavigationMenu,
|
||||
tags: ['autodocs'],
|
||||
argTypes: {},
|
||||
render: (args) => (
|
||||
<NavigationMenu {...args}>
|
||||
<NavigationMenuList>
|
||||
<NavigationMenuItem>
|
||||
<NavigationMenuLink className={navigationMenuTriggerStyle()}>
|
||||
Overview
|
||||
</NavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
<NavigationMenuList>
|
||||
<NavigationMenuItem>
|
||||
<NavigationMenuTrigger className={navigationMenuTriggerStyle()}>
|
||||
Documentation
|
||||
</NavigationMenuTrigger>
|
||||
<NavigationMenuContent>
|
||||
<ul className="grid w-96 p-2">
|
||||
<li>
|
||||
<NavigationMenuLink className={navigationMenuTriggerStyle()}>
|
||||
API Reference
|
||||
</NavigationMenuLink>
|
||||
</li>
|
||||
<li>
|
||||
<NavigationMenuLink className={navigationMenuTriggerStyle()}>
|
||||
Getting Started
|
||||
</NavigationMenuLink>
|
||||
</li>
|
||||
<li>
|
||||
<NavigationMenuLink className={navigationMenuTriggerStyle()}>
|
||||
Guides
|
||||
</NavigationMenuLink>
|
||||
</li>
|
||||
</ul>
|
||||
</NavigationMenuContent>
|
||||
</NavigationMenuItem>
|
||||
</NavigationMenuList>
|
||||
<NavigationMenuItem>
|
||||
<NavigationMenuLink
|
||||
className={navigationMenuTriggerStyle()}
|
||||
href="https:www.google.com"
|
||||
target="_blank"
|
||||
>
|
||||
External
|
||||
</NavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
</NavigationMenuList>
|
||||
</NavigationMenu>
|
||||
),
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
} satisfies Meta<typeof NavigationMenu>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
/**
|
||||
* The default form of the navigation menu.
|
||||
*/
|
||||
export const Default: Story = {};
|
@ -1,57 +0,0 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import {
|
||||
Pagination,
|
||||
PaginationContent,
|
||||
PaginationEllipsis,
|
||||
PaginationItem,
|
||||
PaginationLink,
|
||||
PaginationNext,
|
||||
PaginationPrevious,
|
||||
} from '@konobangu/design-system/components/ui/pagination';
|
||||
|
||||
/**
|
||||
* Pagination with page navigation, next and previous links.
|
||||
*/
|
||||
const meta = {
|
||||
title: 'ui/Pagination',
|
||||
component: Pagination,
|
||||
tags: ['autodocs'],
|
||||
argTypes: {},
|
||||
render: (args) => (
|
||||
<Pagination {...args}>
|
||||
<PaginationContent>
|
||||
<PaginationItem>
|
||||
<PaginationPrevious href="#" />
|
||||
</PaginationItem>
|
||||
<PaginationItem>
|
||||
<PaginationLink href="#">1</PaginationLink>
|
||||
</PaginationItem>
|
||||
<PaginationItem>
|
||||
<PaginationLink href="#">2</PaginationLink>
|
||||
</PaginationItem>
|
||||
<PaginationItem>
|
||||
<PaginationLink href="#">3</PaginationLink>
|
||||
</PaginationItem>
|
||||
<PaginationItem>
|
||||
<PaginationEllipsis />
|
||||
</PaginationItem>
|
||||
<PaginationItem>
|
||||
<PaginationNext href="#" />
|
||||
</PaginationItem>
|
||||
</PaginationContent>
|
||||
</Pagination>
|
||||
),
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
} satisfies Meta<typeof Pagination>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
/**
|
||||
* The default form of the pagination.
|
||||
*/
|
||||
export const Default: Story = {};
|
@ -1,36 +0,0 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from '@konobangu/design-system/components/ui/popover';
|
||||
|
||||
/**
|
||||
* Displays rich content in a portal, triggered by a button.
|
||||
*/
|
||||
const meta = {
|
||||
title: 'ui/Popover',
|
||||
component: Popover,
|
||||
tags: ['autodocs'],
|
||||
argTypes: {},
|
||||
|
||||
render: (args) => (
|
||||
<Popover {...args}>
|
||||
<PopoverTrigger>Open</PopoverTrigger>
|
||||
<PopoverContent>Place content for the popover here.</PopoverContent>
|
||||
</Popover>
|
||||
),
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
} satisfies Meta<typeof Popover>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
/**
|
||||
* The default form of the popover.
|
||||
*/
|
||||
export const Default: Story = {};
|
@ -1,45 +0,0 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { Progress } from '@konobangu/design-system/components/ui/progress';
|
||||
|
||||
/**
|
||||
* Displays an indicator showing the completion progress of a task, typically
|
||||
* displayed as a progress bar.
|
||||
*/
|
||||
const meta = {
|
||||
title: 'ui/Progress',
|
||||
component: Progress,
|
||||
tags: ['autodocs'],
|
||||
argTypes: {},
|
||||
args: {
|
||||
value: 30,
|
||||
max: 100,
|
||||
},
|
||||
} satisfies Meta<typeof Progress>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
/**
|
||||
* The default form of the progress.
|
||||
*/
|
||||
export const Default: Story = {};
|
||||
|
||||
/**
|
||||
* When the progress is indeterminate.
|
||||
*/
|
||||
export const Indeterminate: Story = {
|
||||
args: {
|
||||
value: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* When the progress is completed.
|
||||
*/
|
||||
export const Completed: Story = {
|
||||
args: {
|
||||
value: 100,
|
||||
},
|
||||
};
|
@ -1,40 +0,0 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import {
|
||||
RadioGroup,
|
||||
RadioGroupItem,
|
||||
} from '@konobangu/design-system/components/ui/radio-group';
|
||||
|
||||
/**
|
||||
* A set of checkable buttons—known as radio buttons—where no more than one of
|
||||
* the buttons can be checked at a time.
|
||||
*/
|
||||
const meta = {
|
||||
title: 'ui/RadioGroup',
|
||||
component: RadioGroup,
|
||||
tags: ['autodocs'],
|
||||
argTypes: {},
|
||||
args: {
|
||||
defaultValue: 'comfortable',
|
||||
className: 'grid gap-2 grid-cols-[1rem_1fr] items-center',
|
||||
},
|
||||
render: (args) => (
|
||||
<RadioGroup {...args}>
|
||||
<RadioGroupItem value="default" id="r1" />
|
||||
<label htmlFor="r1">Default</label>
|
||||
<RadioGroupItem value="comfortable" id="r2" />
|
||||
<label htmlFor="r2">Comfortable</label>
|
||||
<RadioGroupItem value="compact" id="r3" />
|
||||
<label htmlFor="r3">Compact</label>
|
||||
</RadioGroup>
|
||||
),
|
||||
} satisfies Meta<typeof RadioGroup>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
/**
|
||||
* The default form of the radio group.
|
||||
*/
|
||||
export const Default: Story = {};
|
@ -1,59 +0,0 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import {
|
||||
ResizableHandle,
|
||||
ResizablePanel,
|
||||
ResizablePanelGroup,
|
||||
} from '@konobangu/design-system/components/ui/resizable';
|
||||
|
||||
/**
|
||||
* Accessible resizable panel groups and layouts with keyboard support.
|
||||
*/
|
||||
const meta: Meta<typeof ResizablePanelGroup> = {
|
||||
title: 'ui/ResizablePanelGroup',
|
||||
component: ResizablePanelGroup,
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
onLayout: {
|
||||
control: false,
|
||||
},
|
||||
},
|
||||
args: {
|
||||
className: 'max-w-96 rounded-lg border',
|
||||
direction: 'horizontal',
|
||||
},
|
||||
render: (args) => (
|
||||
<ResizablePanelGroup {...args}>
|
||||
<ResizablePanel defaultSize={50}>
|
||||
<div className="flex h-[200px] items-center justify-center p-6">
|
||||
<span className="font-semibold">One</span>
|
||||
</div>
|
||||
</ResizablePanel>
|
||||
<ResizableHandle />
|
||||
<ResizablePanel defaultSize={50}>
|
||||
<ResizablePanelGroup direction="vertical">
|
||||
<ResizablePanel defaultSize={25}>
|
||||
<div className="flex h-full items-center justify-center p-6">
|
||||
<span className="font-semibold">Two</span>
|
||||
</div>
|
||||
</ResizablePanel>
|
||||
<ResizableHandle />
|
||||
<ResizablePanel defaultSize={75}>
|
||||
<div className="flex h-full items-center justify-center p-6">
|
||||
<span className="font-semibold">Three</span>
|
||||
</div>
|
||||
</ResizablePanel>
|
||||
</ResizablePanelGroup>
|
||||
</ResizablePanel>
|
||||
</ResizablePanelGroup>
|
||||
),
|
||||
} satisfies Meta<typeof ResizablePanelGroup>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
/**
|
||||
* The default form of the resizable panel group.
|
||||
*/
|
||||
export const Default: Story = {};
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user