Add risks

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
Jonathan
2025-05-20 18:49:47 +02:00
committed by Sacha Al Himdani
parent a2d1310780
commit a86181c83d
41 changed files with 4533 additions and 1304 deletions

View File

@@ -1,18 +1,19 @@
import { tv } from "tailwind-variants";
type Props = {
fullName: string;
name: string;
src?: string | null;
size?: "s" | "m" | "l" | "xl";
};
const avatar = tv({
base: "bg-txt-success text-sm text-invert rounded-full text-xxs font-semibold flex items-center justify-center",
base: "bg-txt-success text-txt-invert rounded-full font-semibold flex items-center justify-center",
variants: {
size: {
s: "size-5",
m: "size-6",
l: "size-8",
xl: "size-16",
s: "size-5 text-xss",
m: "size-6 text-xxs",
l: "size-8 text-sm",
xl: "size-16 text-3xl",
},
},
defaultVariants: {
@@ -20,8 +21,13 @@ const avatar = tv({
},
});
export function Avatar({ fullName, size = "m" }: Props) {
return <div className={avatar({ size })}>{extractInitials(fullName)}</div>;
export function Avatar({ name, src, size = "m" }: Props) {
if (src) {
return <img className={avatar({ size })} src={src} alt={name} />;
}
return (
<div className={avatar({ size })}>{extractInitials(name ?? "")}</div>
);
}
function extractInitials(name: string) {

View File

@@ -0,0 +1,29 @@
import { Breadcrumb } from "./Breadcrumb";
import type { Meta, StoryObj } from "@storybook/react";
export default {
title: "Atoms/Breadcrumb",
component: Breadcrumb,
argTypes: {},
} satisfies Meta<typeof Breadcrumb>;
type Story = StoryObj<typeof Breadcrumb>;
export const Default: Story = {
args: {
items: [
{
label: "Home",
to: "/",
},
{
label: "Library",
to: "/library",
},
{
label: "Data",
to: "/data",
},
],
},
};

View File

@@ -0,0 +1,51 @@
import clsx from "clsx";
import { Link } from "react-router";
import { IconChevronRight } from "../Icons";
type Props = {
items: (ItemProps | string)[];
};
type ItemProps = {
label: string;
to?: string;
active?: boolean;
};
export function Breadcrumb({ items }: Props) {
return (
<div className="flex items-center gap-[6px] text-txt-tertiary">
{items.map((item, index) => (
<>
{index > 0 && <IconChevronRight size={12} />}
<BreadcrumbItem
key={index}
{...(typeof item === "string" ? { label: item } : item)}
active={index === items.length - 1}
/>
</>
))}
</div>
);
}
function BreadcrumbItem({ label, to, active }: ItemProps) {
const className = clsx(
"text-sm px-1 rounded-sm h-5",
active && "text-txt-primary font-medium",
);
if (to) {
return (
<Link
to={to}
className={clsx(
className,
"hover:bg-tertiary-hover active:bg-tertiary-pressed",
)}
>
{label}
</Link>
);
}
return <span className={className}>{label}</span>;
}

View File

@@ -0,0 +1,17 @@
import { Card } from "./Card";
import type { Meta, StoryObj } from "@storybook/react";
export default {
title: "Atoms/Card",
component: Card,
argTypes: {},
} satisfies Meta<typeof Card>;
type Story = StoryObj<typeof Card>;
export const Default: Story = {
args: {
padded: true,
children: "lorem ipsum",
},
};

View File

@@ -0,0 +1,27 @@
import type { PropsWithChildren } from "react";
import { tv } from "tailwind-variants";
import { Slot } from "../Slot";
type Props = PropsWithChildren<{
padded?: boolean;
className?: string;
asChild?: boolean;
}>;
const card = tv({
base: "border border-border-low surface-1 rounded-2xl",
variants: {
padded: {
true: "p-6",
},
},
});
export function Card({ padded = false, children, className, asChild }: Props) {
const Component = asChild ? Slot : "div";
return (
<Component className={card({ padded, className })}>
{children}
</Component>
);
}

View File

@@ -17,7 +17,7 @@ export function Dropdown({ children, toggle, className }: Props) {
<DropdownMenu.Content
className={clsx(
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-dropdown-menu-content-transform-origin]",
"z-50 p-2 shadow-mid min-w-[8rem] bg-surface-1 overflow-y-auto overflow-x-hidden rounded-2xl border-border-low",
"z-50 p-2 shadow-mid min-w-[8rem] bg-level-1 overflow-y-auto overflow-x-hidden rounded-2xl border-border-low",
className,
)}
sideOffset={5}

View File

@@ -6,6 +6,7 @@ import {
} from "react";
import { clsx } from "clsx";
import { IconCollapse, IconExpand } from "../Icons";
import { Button } from "../Button/Button";
const sidebarContext = createContext({ open: true });
@@ -15,21 +16,17 @@ export function Sidebar({ children }: PropsWithChildren) {
<sidebarContext.Provider value={{ open }}>
<aside
className={clsx(
"border-r border-border-solid relative pt-12 flex-none",
"border-r border-border-solid relative pt-16 flex-none",
open ? "px-4 w-[260px]" : "px-2",
)}
>
{children}
<button
<Button
variant="tertiary"
icon={open ? IconCollapse : IconExpand}
onClick={() => setOpen(!open)}
className="cursor-pointer absolute bottom-4 left-4"
>
{open ? (
<IconCollapse size={16} />
) : (
<IconExpand size={16} />
)}
</button>
className="absolute bottom-4 left-4"
/>
</aside>
</sidebarContext.Provider>
);

View File

@@ -0,0 +1,16 @@
import { Skeleton } from "./Skeleton";
import type { Meta, StoryObj } from "@storybook/react";
export default {
title: "Atoms/Skeleton",
component: Skeleton,
argTypes: {},
} satisfies Meta<typeof Skeleton>;
type Story = StoryObj<typeof Skeleton>;
export const Default: Story = {
args: {
className: "w-50 h-4",
},
};

View File

@@ -0,0 +1,16 @@
import { clsx } from "clsx";
export function Skeleton({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) {
return (
<div
className={clsx(
"animate-pulse rounded-md bg-border-low",
className,
)}
{...props}
/>
);
}

View File

@@ -0,0 +1,29 @@
import clsx from "clsx";
import React from "react";
import { cloneElement, isValidElement, Children } from "react";
import type { ReactNode } from "react";
export type AsChildProps<DefaultElementProps> =
| ({ asChild?: false } & DefaultElementProps)
| { asChild: true; children: ReactNode };
export function Slot({
children,
...props
}: React.HTMLAttributes<HTMLElement> & {
children?: ReactNode;
}) {
if (isValidElement(children)) {
return cloneElement(children, {
...props,
// @ts-expect-error children.props can be undefined it's ok
...children.props,
// @ts-expect-error children.props.className can be undefined it's ok
className: clsx(props.className, children.props.className),
});
}
if (Children.count(children) > 1) {
Children.only(null);
}
return null;
}