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": {
|
"scripts": {
|
||||||
"dev": "npx --yes mintlify dev --port 5004",
|
"dev": "npx --yes mintlify dev --port 5004",
|
||||||
"lint": "npx --yes mintlify broken-links"
|
"lint": "npx --yes mintlify broken-links"
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"typescript": "^5.7.3"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,15 +10,11 @@
|
|||||||
"typecheck": "tsc --noEmit --emitDeclarationOnly false"
|
"typecheck": "tsc --noEmit --emitDeclarationOnly false"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@konobangu/email": "workspace:*",
|
|
||||||
"@react-email/components": "0.0.31",
|
"@react-email/components": "0.0.31",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-email": "3.0.4"
|
"react-email": "3.0.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@konobangu/typescript-config": "workspace:*",
|
"@types/react": "19.0.1"
|
||||||
"@types/node": "22.10.1",
|
|
||||||
"@types/react": "19.0.1",
|
|
||||||
"typescript": "^5.7.3"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
{
|
{
|
||||||
"extends": "@konobangu/typescript-config/nextjs.json",
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"jsx": "react-jsx"
|
||||||
|
},
|
||||||
"include": ["**/*.ts", "**/*.tsx"],
|
"include": ["**/*.ts", "**/*.tsx"],
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules"]
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,6 @@ sea-orm = { version = "1.1", features = [
|
|||||||
"debug-print",
|
"debug-print",
|
||||||
] }
|
] }
|
||||||
figment = { version = "0.10", features = ["toml", "json", "env", "yaml"] }
|
figment = { version = "0.10", features = ["toml", "json", "env", "yaml"] }
|
||||||
|
|
||||||
axum = "0.8"
|
axum = "0.8"
|
||||||
uuid = { version = "1.6.0", features = ["v4"] }
|
uuid = { version = "1.6.0", features = ["v4"] }
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] }
|
||||||
|
@ -8,30 +8,17 @@
|
|||||||
"preview": "rsbuild preview"
|
"preview": "rsbuild preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@abraham/reflection": "^0.12.0",
|
|
||||||
"@graphiql/react": "^0.28.2",
|
"@graphiql/react": "^0.28.2",
|
||||||
"@graphiql/toolkit": "^0.11.1",
|
"@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",
|
"graphiql": "^3.8.3",
|
||||||
"graphql-ws": "^5.16.2",
|
"graphql-ws": "^6.0.4",
|
||||||
"observable-hooks": "^4.2.4",
|
"observable-hooks": "^4.2.4",
|
||||||
"oidc-client-rx": "0.1.0-alpha.6",
|
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0"
|
||||||
"rxjs": "^7.8.1"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@konobangu/typescript-config": "workspace:*",
|
|
||||||
"@rsbuild/core": "1.1.3",
|
|
||||||
"@rsbuild/plugin-react": "^1.1.1",
|
"@rsbuild/plugin-react": "^1.1.1",
|
||||||
"@tanstack/router-plugin": "^1.95.6",
|
|
||||||
"@types/react": "^19.0.7",
|
"@types/react": "^19.0.7",
|
||||||
"@types/react-dom": "^19.0.3",
|
"@types/react-dom": "^19.0.3"
|
||||||
"tailwindcss": "^3.4.17",
|
|
||||||
"typescript": "^5.7.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)]
|
use serde::{
|
||||||
pub struct GraphQLConfig {
|
Deserialize, Deserializer, Serialize, Serializer,
|
||||||
pub depth_limit: Option<usize>,
|
de::{self, Unexpected},
|
||||||
pub complexity_limit: Option<usize>,
|
};
|
||||||
|
|
||||||
|
#[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,
|
config: GraphQLConfig,
|
||||||
db: DatabaseConnection,
|
db: DatabaseConnection,
|
||||||
) -> RResult<Self> {
|
) -> 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 })
|
Ok(Self { schema })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,5 +24,5 @@ pub mod sync;
|
|||||||
pub mod tasks;
|
pub mod tasks;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub mod test_utils;
|
pub mod test_utils;
|
||||||
pub mod views;
|
pub mod utils;
|
||||||
pub mod web;
|
pub mod web;
|
||||||
|
@ -1,3 +1 @@
|
|||||||
@tailwind base;
|
@import "tailwindcss";
|
||||||
@tailwind components;
|
|
||||||
@tailwind utilities;
|
|
||||||
|
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 { createFileRoute } from '@tanstack/react-router';
|
||||||
import GraphiQL from 'graphiql';
|
import GraphiQL from 'graphiql';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { beforeLoadGuard } from '../../../auth/guard';
|
|
||||||
import 'graphiql/graphiql.css';
|
|
||||||
import { firstValueFrom } from 'rxjs';
|
import { firstValueFrom } from 'rxjs';
|
||||||
|
import { beforeLoadGuard } from '../../../auth/guard';
|
||||||
import { useAuth } from '../../../auth/hooks';
|
import { useAuth } from '../../../auth/hooks';
|
||||||
|
import 'graphiql/graphiql.css';
|
||||||
|
|
||||||
export const Route = createFileRoute('/graphql/')({
|
export const Route = createFileRoute('/graphql/')({
|
||||||
component: RouteComponent,
|
component: RouteComponent,
|
||||||
@ -32,5 +32,5 @@ function RouteComponent() {
|
|||||||
[oidcSecurityService]
|
[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": {
|
"compilerOptions": {
|
||||||
"lib": ["DOM", "ES2024", "DOM.AsyncIterable", "DOM.Iterable"],
|
"rootDir": ".",
|
||||||
|
"composite": true,
|
||||||
"jsx": "react-jsx",
|
"jsx": "react-jsx",
|
||||||
"noEmit": true,
|
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"moduleResolution": "Bundler",
|
"moduleResolution": "Bundler",
|
||||||
"allowImportingTsExtensions": true,
|
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
"strict": 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