Add frameworks listing and import

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
Jonathan
2025-06-01 21:37:11 +02:00
committed by Sacha Al Himdani
parent 7cb84d8ce8
commit 1870e050ce
23 changed files with 1517 additions and 69 deletions

View File

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

View File

@@ -74,7 +74,7 @@ export function ActionDropdown(
{...props}
toggle={
<Button
className="inline-flex"
className="inline-flex isolate z-5"
variant={props.variant ?? "tertiary"}
icon={IconDotGrid1x3Horizontal}
/>

View File

@@ -37,17 +37,22 @@ export const dialog = tv({
},
});
export type DialogRef = RefObject<{
open: () => void;
close: () => void;
} | null>;
type Props = {
trigger?: ReactNode;
title?: ReactNode;
children?: ReactNode;
defaultOpen?: boolean;
className?: string;
ref?: RefObject<{ open: () => void; close: () => void } | null>;
ref?: DialogRef;
};
export const useDialogRef = () => {
return useRef<{ open: () => void; close: () => void } | null>(null);
export const useDialogRef = (): DialogRef => {
return useRef(null);
};
export function Dialog({

View File

@@ -0,0 +1,12 @@
import { FrameworkSelector } from "./FrameworkSelector";
import type { Meta, StoryObj } from "@storybook/react";
export default {
title: "Molecules/FrameworkSelector",
component: FrameworkSelector,
argTypes: {},
} satisfies Meta<typeof FrameworkSelector>;
type Story = StoryObj<typeof FrameworkSelector>;
export const Default: Story = {};

View File

@@ -0,0 +1,76 @@
import { Dropdown, DropdownItem } from "../../Atoms/Dropdown/Dropdown";
import { IconChevronDown, IconPlusLarge } from "../../Atoms/Icons";
import { Button } from "../../Atoms/Button/Button";
import { availableFrameworks } from "@probo/helpers";
import { useTranslate } from "@probo/i18n";
type Framework = (typeof availableFrameworks)[number];
type Props = {
disabled?: boolean;
onSelect: (frameworkId: string) => void;
};
export function FrameworkSelector({ disabled, onSelect }: Props) {
const { __ } = useTranslate();
return (
<Dropdown
toggle={
<Button
icon={IconPlusLarge}
iconAfter={IconChevronDown}
disabled={disabled}
>
{__("New framework")}
</Button>
}
>
<FrameworkItem onClick={() => onSelect("custom")} />
{availableFrameworks.map((framework) => (
<FrameworkItem
key={framework.id}
framework={framework}
onClick={() => onSelect(framework.id)}
/>
))}
</Dropdown>
);
}
function FrameworkItem(props: { framework?: Framework; onClick: () => void }) {
const { __ } = useTranslate();
if (!props.framework) {
return (
<DropdownItem onClick={props.onClick} className="">
<div className="rounded-full size-8 bg-highlight text-txt-primary flex items-center justify-center">
<IconPlusLarge size={16} />
</div>
<div className="space-y-[2px]">
<div className="text-sm font-medium">
{__("Custom framework")}
</div>
<div className="text-xs text-txt-secondary">
{__("Start from scratch")}
</div>
</div>
</DropdownItem>
);
}
return (
<DropdownItem onClick={props.onClick} className="">
<img
src={props.framework.logo}
alt={props.framework.name}
className="size-8"
/>
<div className="space-y-[2px]">
<div className="text-sm font-medium">
{props.framework.name}
</div>
<div className="text-xs text-txt-secondary">
{props.framework.description}
</div>
</div>
</DropdownItem>
);
}

View File

@@ -47,6 +47,7 @@ export {
DialogFooter,
DialogTitle,
useDialogRef,
type DialogRef,
} from "./Molecules/Dialog/Dialog";
export { useConfirm } from "./Molecules/Dialog/ConfirmDialog";
export { RiskBadge } from "./Molecules/Badge/RiskBadge";
@@ -58,6 +59,7 @@ export { Combobox, ComboboxItem } from "./Molecules/Combobox/Combobox";
export { FileButton } from "./Molecules/FileButton/FileButton";
export { MeasureBadge } from "./Molecules/Badge/MeasureBadge";
export { MeasureImplementation } from "./Molecules/MeasureImplementation/MeasureImplementation";
export { FrameworkSelector } from "./Molecules/FrameworkSelector/FrameworkSelector";
// Hooks
export { useToast, Toasts } from "./Atoms/Toasts/Toasts";