Add vendor creation
Signed-off-by: Bryan Frimin <bryan@getprobo.com> Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
committed by
Sacha Al Himdani
parent
fe3128da2a
commit
c2a69b5428
@@ -17,7 +17,9 @@ const preview: Preview = {
|
||||
(Story, { parameters }) => {
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<Story />
|
||||
<div className="text-txt-primary">
|
||||
<Story />
|
||||
</div>
|
||||
</BrowserRouter>
|
||||
);
|
||||
},
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
},
|
||||
"prettier": "@probo/prettier",
|
||||
"dependencies": {
|
||||
"@ariakit/react": "^0.4.17",
|
||||
"@probo/helpers": "1.0.0",
|
||||
"@probo/i18n": "1.0.0",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.14",
|
||||
|
||||
@@ -31,7 +31,7 @@ const badge = tv({
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
size: "md",
|
||||
size: "sm",
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Link } from "react-router";
|
||||
import { tv, type VariantProps } from "tailwind-variants";
|
||||
|
||||
const button = tv({
|
||||
base: "flex items-center justify-center gap-[6px] px-3 py-2 rounded-full cursor-pointer text-sm font-medium h-8",
|
||||
base: "flex items-center justify-center gap-[6px] px-3 py-2 rounded-full cursor-pointer text-sm font-medium h-8 focus:outline-none",
|
||||
variants: {
|
||||
variant: {
|
||||
primary:
|
||||
|
||||
@@ -7,21 +7,24 @@ import { IconDotGrid1x3Horizontal } from "../Icons/IconDotGrid1x3Horizontal.tsx"
|
||||
import { Button } from "../Button/Button.tsx";
|
||||
|
||||
type Props = PropsWithChildren<{
|
||||
toggle: ReactNode;
|
||||
toggle?: ReactNode;
|
||||
className?: string;
|
||||
open?: boolean;
|
||||
}>;
|
||||
|
||||
export function Dropdown({ children, toggle, className }: Props) {
|
||||
export const dropdown = tv({
|
||||
base: "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-level-1 overflow-y-auto overflow-x-hidden rounded-2xl border-border-low",
|
||||
});
|
||||
|
||||
export function Dropdown({ children, toggle, className, open }: Props) {
|
||||
return (
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger asChild>{toggle}</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Root open={open}>
|
||||
{toggle && (
|
||||
<DropdownMenu.Trigger asChild>{toggle}</DropdownMenu.Trigger>
|
||||
)}
|
||||
<DropdownMenu.Portal>
|
||||
<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-level-1 overflow-y-auto overflow-x-hidden rounded-2xl border-border-low",
|
||||
className,
|
||||
)}
|
||||
className={dropdown({ className })}
|
||||
sideOffset={5}
|
||||
>
|
||||
{children}
|
||||
@@ -47,8 +50,8 @@ type DropdownItemProps = PropsWithChildren<{
|
||||
}> &
|
||||
ComponentProps<typeof DropdownMenu.Item>;
|
||||
|
||||
const dropdownItem = tv({
|
||||
base: "text-txt-primary flex items-center gap-2 hover:bg-tertiary-hover active:bg-tertiary-pressed cursor-pointer p-2",
|
||||
export const dropdownItem = tv({
|
||||
base: "text-txt-primary flex items-center gap-2 hover:bg-tertiary-hover active:bg-tertiary-pressed data-active-item:bg-tertiary-pressed cursor-pointer p-2",
|
||||
variants: {
|
||||
variant: {
|
||||
primary: "text-txt-primary",
|
||||
|
||||
@@ -7,7 +7,7 @@ type Props = {
|
||||
} & InputHTMLAttributes<HTMLInputElement>;
|
||||
|
||||
export const input = tv({
|
||||
base: "py-[6px] bg-secondary border border-border-mid rounded-[10px] hover:border-border-strong focus:shadow-focus text-sm px-3 w-full bg-secondary disabled:bg-transparent",
|
||||
base: "py-[6px] bg-secondary border border-border-mid rounded-[10px] hover:border-border-strong focus:shadow-focus text-sm px-3 w-full bg-secondary disabled:bg-transparent focus:outline-none",
|
||||
variants: {
|
||||
invalid: {
|
||||
true: "border-border-danger",
|
||||
|
||||
@@ -85,9 +85,6 @@ const findSelectedOption = (
|
||||
children: unknown[],
|
||||
condition: (c: { props: Record<string, unknown> }) => boolean,
|
||||
): ReactNode => {
|
||||
if (children.length === 1) {
|
||||
debugger;
|
||||
}
|
||||
const selectedOptions = children.find(
|
||||
// @ts-expect-error We know that the children are ReactElements with props
|
||||
(c) => isValidElement(c) && condition(c),
|
||||
|
||||
12
packages/ui/src/Atoms/Spinner/Spinner.stories.tsx
Normal file
12
packages/ui/src/Atoms/Spinner/Spinner.stories.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Spinner } from "./Spinner";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
|
||||
export default {
|
||||
title: "Atoms/Spinner",
|
||||
component: Spinner,
|
||||
argTypes: {},
|
||||
} satisfies Meta<typeof Spinner>;
|
||||
|
||||
type Story = StoryObj<typeof Spinner>;
|
||||
|
||||
export const Default: Story = {};
|
||||
12
packages/ui/src/Atoms/Spinner/Spinner.tsx
Normal file
12
packages/ui/src/Atoms/Spinner/Spinner.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import clsx from "clsx";
|
||||
|
||||
type Props = { size: number; className?: string };
|
||||
|
||||
export function Spinner({ size = 16, className }: Props) {
|
||||
return (
|
||||
<div
|
||||
className={clsx("animate-spin rounded-full border-b-2", className)}
|
||||
style={{ width: size, height: size, borderColor: "currentColor" }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -12,9 +12,9 @@ type Story = StoryObj<typeof RiskBadge>;
|
||||
export const Default: Story = {
|
||||
render: () => (
|
||||
<div className="space-y-2">
|
||||
<RiskBadge score={10} />
|
||||
<RiskBadge score={8} />
|
||||
<RiskBadge score={0} />
|
||||
<RiskBadge level={10} />
|
||||
<RiskBadge level={8} />
|
||||
<RiskBadge level={0} />
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
@@ -2,29 +2,59 @@ import { useTranslate } from "@probo/i18n";
|
||||
import { Badge } from "../../Atoms/Badge/Badge";
|
||||
|
||||
type Props = {
|
||||
score: number;
|
||||
level: number | string;
|
||||
};
|
||||
|
||||
const badgeVariant = (score: number) => {
|
||||
if (score >= 15) {
|
||||
return "danger";
|
||||
const badgeVariant = (level: string | number) => {
|
||||
if (typeof level === "number") {
|
||||
if (level >= 15) {
|
||||
return "CRITICAL";
|
||||
}
|
||||
if (level >= 8) {
|
||||
return "HIGH";
|
||||
}
|
||||
return "LOW";
|
||||
}
|
||||
if (score >= 8) {
|
||||
return "warning";
|
||||
switch (level) {
|
||||
case "CRITICAL":
|
||||
return "danger";
|
||||
case "HIGH":
|
||||
return "warning";
|
||||
case "LOW":
|
||||
return "success";
|
||||
case "MEDIUM":
|
||||
return "info";
|
||||
default:
|
||||
return "neutral";
|
||||
}
|
||||
return "success";
|
||||
};
|
||||
|
||||
export function RiskBadge({ score }: Props) {
|
||||
export function RiskBadge({ level }: Props) {
|
||||
const { __ } = useTranslate();
|
||||
const label = () => {
|
||||
if (score >= 15) {
|
||||
return __("High");
|
||||
if (typeof level === "number") {
|
||||
if (level >= 15) {
|
||||
return __("High");
|
||||
}
|
||||
if (level >= 8) {
|
||||
return __("Medium");
|
||||
}
|
||||
return __("Low");
|
||||
}
|
||||
if (score >= 8) {
|
||||
return __("Medium");
|
||||
switch (level) {
|
||||
case "CRITICAL":
|
||||
return __("Critical");
|
||||
case "HIGH":
|
||||
return __("High");
|
||||
case "LOW":
|
||||
return __("Low");
|
||||
case "MEDIUM":
|
||||
return __("Medium");
|
||||
case "NONE":
|
||||
return __("None");
|
||||
default:
|
||||
return __("Low");
|
||||
}
|
||||
return __("Low");
|
||||
};
|
||||
return <Badge variant={badgeVariant(score)}>{label()}</Badge>;
|
||||
return <Badge variant={badgeVariant(level)}>{label()}</Badge>;
|
||||
}
|
||||
|
||||
37
packages/ui/src/Molecules/Combobox/Combobox.stories.tsx
Normal file
37
packages/ui/src/Molecules/Combobox/Combobox.stories.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import { useState } from "react";
|
||||
import { Combobox, ComboboxItem } from "./Combobox";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { times } from "@probo/helpers";
|
||||
|
||||
export default {
|
||||
title: "Atoms/Combobox",
|
||||
component: Combobox,
|
||||
argTypes: {},
|
||||
} satisfies Meta<typeof Combobox>;
|
||||
|
||||
type Story = StoryObj<typeof Combobox>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
open: true,
|
||||
},
|
||||
render: () => {
|
||||
const [items, setItems] = useState(["a", "b", "c"] as string[]);
|
||||
const onSearch = (query: string) => {
|
||||
setItems(times(10, (i) => `${query} ${i}`));
|
||||
};
|
||||
return (
|
||||
<>
|
||||
Lorem ipsum dolor sit, amet consectetur adipisicing elit.
|
||||
Nostrum labore repellat facere voluptatum, in voluptatem eaque
|
||||
quidem nam quod nesciunt repudiandae a illo non placeat nulla.
|
||||
Ratione ipsa at sint?
|
||||
<Combobox onSearch={onSearch}>
|
||||
{items.map((item) => (
|
||||
<ComboboxItem key={item}>{item}</ComboboxItem>
|
||||
))}
|
||||
</Combobox>
|
||||
</>
|
||||
);
|
||||
},
|
||||
};
|
||||
46
packages/ui/src/Molecules/Combobox/Combobox.tsx
Normal file
46
packages/ui/src/Molecules/Combobox/Combobox.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import {
|
||||
Combobox as AriaKitCombobox,
|
||||
ComboboxItem as AriaKitComboboxItem,
|
||||
ComboboxPopover,
|
||||
ComboboxProvider,
|
||||
} from "@ariakit/react";
|
||||
import {
|
||||
type ComponentProps,
|
||||
type PropsWithChildren,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
import { input } from "../../Atoms/Input/Input";
|
||||
import { dropdown, dropdownItem } from "../../Atoms/Dropdown/Dropdown";
|
||||
import { isEmpty } from "@probo/helpers";
|
||||
|
||||
type Props = {
|
||||
children: ReactNode;
|
||||
loading?: boolean;
|
||||
onSearch: (query: string) => void;
|
||||
placeholder?: string;
|
||||
};
|
||||
|
||||
export function Combobox({ children, onSearch, placeholder }: Props) {
|
||||
const showDropdown = !isEmpty(children);
|
||||
return (
|
||||
<ComboboxProvider setValue={onSearch}>
|
||||
<AriaKitCombobox placeholder={placeholder} className={input()} />
|
||||
{showDropdown && (
|
||||
<ComboboxPopover gutter={4} sameWidth className={dropdown()}>
|
||||
{children}
|
||||
</ComboboxPopover>
|
||||
)}
|
||||
</ComboboxProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export function ComboboxItem({
|
||||
children,
|
||||
...props
|
||||
}: PropsWithChildren<ComponentProps<typeof AriaKitComboboxItem>>) {
|
||||
return (
|
||||
<AriaKitComboboxItem className={dropdownItem()} {...props}>
|
||||
{children}
|
||||
</AriaKitComboboxItem>
|
||||
);
|
||||
}
|
||||
@@ -48,7 +48,12 @@ export function ConfirmDialog(props: Props) {
|
||||
<>
|
||||
{children}
|
||||
<Portal>
|
||||
<Dialog open={open} onOpenChange={setOpen} title={title}>
|
||||
<Dialog
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
title={title}
|
||||
className="max-w-[500px]"
|
||||
>
|
||||
<DialogContent>
|
||||
<p className="text-sm text-txt-secondary p-4">
|
||||
{props.message}
|
||||
|
||||
@@ -19,6 +19,7 @@ type Props = {
|
||||
children?: ReactNode;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
open?: boolean;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export function Dialog({
|
||||
@@ -27,6 +28,7 @@ export function Dialog({
|
||||
children,
|
||||
onOpenChange,
|
||||
open,
|
||||
className,
|
||||
}: Props) {
|
||||
return (
|
||||
<Root open={open} onOpenChange={onOpenChange}>
|
||||
@@ -42,6 +44,7 @@ export function Dialog({
|
||||
aria-describedby={undefined}
|
||||
className={clsx(
|
||||
"fixed inset-0 m-auto z-50 w-full h-max bg-level-2 rounded-2xl max-w-5xl w-[95%]",
|
||||
className,
|
||||
`duration-200
|
||||
data-[state=open]:animate-in data-[state=closed]:animate-out
|
||||
data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0
|
||||
@@ -53,7 +56,11 @@ export function Dialog({
|
||||
<div className="flex justify-between items-center p-3 border-b border-b-border-low">
|
||||
<Title className="text-sm font-medium">{title}</Title>
|
||||
<Close asChild>
|
||||
<Button variant="tertiary" icon={IconCrossLargeX} />
|
||||
<Button
|
||||
tabIndex={-1}
|
||||
variant="tertiary"
|
||||
icon={IconCrossLargeX}
|
||||
/>
|
||||
</Close>
|
||||
</div>
|
||||
{children}
|
||||
|
||||
@@ -15,6 +15,7 @@ export { Button } from "./Atoms/Button/Button";
|
||||
export { Card } from "./Atoms/Card/Card";
|
||||
export { Breadcrumb } from "./Atoms/Breadcrumb/Breadcrumb";
|
||||
export { Badge } from "./Atoms/Badge/Badge";
|
||||
export { Spinner } from "./Atoms/Spinner/Spinner";
|
||||
export {
|
||||
Dropdown,
|
||||
ActionDropdown,
|
||||
@@ -43,6 +44,7 @@ export { SeverityBadge } from "./Molecules/Badge/SeverityBadge.tsx";
|
||||
export { ConfirmDialog } from "./Molecules/Dialog/ConfirmDialog.tsx";
|
||||
export { RisksChart } from "./Molecules/Risks/RisksChart";
|
||||
export { RiskOverview } from "./Molecules/Risks/RiskOverview";
|
||||
export { Combobox, ComboboxItem } from "./Molecules/Combobox/Combobox";
|
||||
|
||||
// Hooks
|
||||
export { useToast, Toasts } from "./Atoms/Toasts/Toasts";
|
||||
|
||||
Reference in New Issue
Block a user