Add policies listing page

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-26 13:29:48 +02:00
committed by Sacha Al Himdani
parent 7903bd3a77
commit abe0ce1dde
36 changed files with 3343 additions and 138 deletions

View File

@@ -19,7 +19,7 @@ export const button = tv({
"bg-tertiary text-txt-primary hover:bg-tertiary-hover active:bg-tertiary-pressed focus:shadow-focus",
quaternary:
"bg-highlight text-txt-primary hover:bg-highlight-hover active:bg-highlight-pressed",
danger: "bg-danger-plain text-invert hover:bg-danger-hover shadow-base hover:shadow-hover active:bg-danger-pressed focus:shadow-focus border border-border-danger",
danger: "bg-danger-plain text-txt-invert hover:bg-danger-hover shadow-base hover:shadow-hover active:bg-danger-pressed focus:shadow-focus border border-border-danger",
},
disabled: {
true: "opacity-60 cursor-default",

View File

@@ -16,3 +16,10 @@ export const Disabled: Story = {
disabled: true,
},
};
export const Title: Story = {
args: {
variant: "title",
placeholder: "Policy title",
},
};

View File

@@ -7,14 +7,24 @@ type Props = {
disabled?: boolean;
icon?: FC<IconProps>;
onValueChange?: (value: string) => void;
variant?: "bordered" | "ghost" | "title";
} & 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 focus:outline-none data-[focus=true]:shadow-focus",
base: "w-full disabled:bg-transparent",
variants: {
invalid: {
true: "border-border-danger",
},
variant: {
bordered:
"py-[6px] bg-secondary border border-border-mid rounded-[10px] hover:border-border-strong text-sm px-3 text-txt-primary",
ghost: "text-base text-txt-secondary",
title: "text-2xl font-semibold text-txt-primary",
},
},
defaultVariants: {
variant: "bordered",
},
});
@@ -35,6 +45,7 @@ export function Input({
>
<IconComponent size={16} className="text-txt-secondary" />
<input
{...props}
onFocus={(e) => {
setFocus(true);
props.onFocus?.(e);
@@ -45,7 +56,6 @@ export function Input({
}}
aria-invalid={invalid}
className="w-full outline-none"
{...props}
onChange={(e) => {
onValueChange?.(e.currentTarget.value);
props.onChange?.(e);
@@ -54,5 +64,15 @@ export function Input({
</div>
);
}
return <input aria-invalid={invalid} className={input(props)} {...props} />;
return (
<input
{...props}
aria-invalid={invalid}
className={input(props)}
onChange={(e) => {
onValueChange?.(e.currentTarget.value);
props.onChange?.(e);
}}
/>
);
}

View File

@@ -0,0 +1,75 @@
import { Markdown } from "./Markdown";
import type { Meta, StoryObj } from "@storybook/react";
export default {
title: "Atoms/Markdown",
component: Markdown,
argTypes: {},
} satisfies Meta<typeof Markdown>;
type Story = StoryObj<typeof Markdown>;
export const Default: Story = {
args: {
children: `# Siquis facies post
## Venit Ianigenam egressus a tamen terra frater
Lorem markdownum hoste omnis auxilium et mille caeruleum coeperunt rebus Iunonis
te tibi? Neque in ille mane? Mugitus terra Phineu pendebat lanugine fundae regi
vestigia.
if (finder_pad > path_browser_beta * 5) {
powerDtd += 5;
} else {
cluster += markup_golden;
radcab(progressive(addressIoIde), basic(cacheSoftware, sample,
hdmiKilobyteRootkit));
bitmapBoot(utility_dot, 1);
}
var pcmcia_e = newlineKerningOutput(lifoMbr(graphicsScareware), 5,
t.codec_isdn_digitize.dot(php_lun, 5));
componentRootkit.ipx_heap_hdtv = algorithm;
if (-5) {
southbridge_zone.rateOasis(61 + nvram_windows_drive);
}
Formam meo. *Coniuge nos* in circuit mihi: ad: ominibus data. Cum necopina deus
dona ferendo est. Tua lato fessa monitis terris inconcessisque redeunt delphines
dumque tremebunda moenia et limus.
1. Evolvere nescis
2. Tum magna
3. Proque vapor serum valens hasta visa in
4. Plus dolentius teneas et foedataque poenam alimentaque
## Pervenit sepulcris sponte inferias tolle
Suarum Phrygum noscere et umeris causam. Cum illa stratoque taedis actum
subiectis undae parvis gratissima membra. Faciebant numerare Clarium nomen fit
et aliena decet ergo silvas somnum, conpositas Granico, rettulit deos, erat.
Rupit quoque sparsumque eodem culpae exequialia potentem refert?
> Tamen enim armataque cuncta fuit legit numinis. Labentibus equam torvos et
> pudet liquidis exululat et iussis ferret. Mea contenta in Midan, nam, o
> Lacinia potuit digredimur corpora. Aut scabrae, monte in fons pomi **se
> hiatu** anhelitus! Taurum fugiunt inpius summa aut crimina Parcite e corpus
> altius humus, iacto non *corpus capillos aequore*!
Lacubus distant, nec dixit **parili ore** te exspectatus radii certe. Ostendere
disparibus campus; spondere toris; quod tolerare umbras, nunc arma conprecor
nec. *Penthea* si **viri** paulatim cornu; **si** musco monitis artes? Parte vox
parit utinam solum futurum vecta, taloque attonito meae virga lanaeque ait
tantus locoque illa aurum sortitus atra. Dentibus *per telum horrentia*
abstulerat arces frementes scripta viridi: celare principium mihi.
- Nonne facinus quamvis sanguinis Arcadiae posuit
- Tabellis cepi Hectora
- Rigido percurrens damus
Fecissem quem degravat quibus movere inmotus sacer Iovem, sic *urbis mala
quoque*. Tereu ut tuos sed recessit petit patriaeque subit palude subposuere
certum: tempora, telisque. In quaesitique habitavit nostris Scylaceaque potest
omnia pastoribus meminisse ignara. Sed pando functaque perenni gemitus tibi.`,
},
};

View File

@@ -0,0 +1,20 @@
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import rehypeRaw from "rehype-raw";
type Props = {
content: string;
};
export function Markdown({ content }: Props) {
return (
<div className="prose prose-neutral">
<ReactMarkdown
remarkPlugins={[remarkGfm]}
rehypePlugins={[rehypeRaw]}
>
{content}
</ReactMarkdown>
</div>
);
}

View File

@@ -33,7 +33,6 @@ const select = tv({
"flex justify-between items-center data-placeholder:text-txt-tertiary whitespace-nowrap cursor-pointer *:first:contents",
content:
"z-100 shadow-mid rounded-[10px] bg-level-1 p-1 animate-in fade-in slide-in-from-top-2 overflow-y-auto overflow-y-auto",
option: "flex gap-2 items-center h-8 text-sm font-medium text-txt-primary hover:bg-tertiary-hover active:bg-tertiary-pressed cursor-pointer px-[10px]",
icon: "-mr-1",
},
variants: {
@@ -71,8 +70,6 @@ const select = tv({
},
});
const { trigger, option, content, icon } = select();
export function Select({
placeholder,
children,
@@ -80,9 +77,13 @@ export function Select({
value,
...props
}: Props) {
const { trigger, content, icon } = select({
...props,
});
return (
<Root onValueChange={onValueChange} value={value}>
<Trigger {...props} className={trigger({ ...props })}>
<Trigger {...props} className={trigger()}>
<Value placeholder={placeholder} />
<Icon className={icon()}>
<IconChevronGrabberVertical size={16} />
@@ -115,7 +116,12 @@ export function Select({
export function Option({ children, ...props }: ComponentProps<typeof Item>) {
const hasSingleChildren = Children.count(children) <= 1;
return (
<Item {...props} className={option(props)}>
<Item
{...props}
className={
"flex gap-2 items-center h-8 text-sm font-medium text-txt-primary hover:bg-tertiary-hover active:bg-tertiary-pressed cursor-pointer px-[10px]"
}
>
<ItemText asChild>
<span
className={

View File

@@ -10,3 +10,8 @@ export default {
type Story = StoryObj<typeof Textarea>;
export const Default: Story = {};
export const Autogrow: Story = {
args: {
autogrow: true,
},
};

View File

@@ -1,13 +1,39 @@
import type { TextareaHTMLAttributes } from "react";
import { tv } from "tailwind-variants";
import {
useRef,
type FormEventHandler,
type TextareaHTMLAttributes,
type RefObject,
} from "react";
import { input } from "../Input/Input";
import clsx from "clsx";
type Props = TextareaHTMLAttributes<HTMLTextAreaElement>;
export const textarea = tv({
base: input({ class: "min-h-20" }),
});
type Props = TextareaHTMLAttributes<HTMLTextAreaElement> & {
variant?: "bordered" | "ghost" | "title";
autogrow?: boolean;
ref?: RefObject<HTMLTextAreaElement>;
};
export function Textarea(props: Props) {
return <textarea {...props} className={textarea(props)} />;
const textbox = useRef<HTMLTextAreaElement>(null);
const { autogrow, variant, ref: propsRef, ...restProps } = props;
const ref = propsRef || textbox;
const adjustHeight: FormEventHandler<HTMLTextAreaElement> = (e) => {
props.onInput?.(e);
if (!autogrow || !ref.current) return;
ref.current.style.height = "inherit";
const paddingY = 2;
ref.current.style.height = `${ref.current.scrollHeight + paddingY * 2}px`;
};
return (
<textarea
{...restProps}
ref={ref}
onInput={adjustHeight}
className={input({
...props,
className: clsx("min-h-20", props.className),
})}
/>
);
}

View File

@@ -67,7 +67,10 @@ export function Layout({ header, sidebar, children }: Props) {
);
}
export function Drawer({ children }: PropsWithChildren) {
export function Drawer({
children,
className,
}: PropsWithChildren<{ className?: string }>) {
const { setDrawer } = useContext(LayoutContext);
useEffect(() => {
setDrawer(true);
@@ -76,7 +79,12 @@ export function Drawer({ children }: PropsWithChildren) {
};
}, []);
return createPortal(
<aside className="absolute pt-20 top-0 right-0 w-105 px-6 pb-8 border-border-solid border-l h-screen">
<aside
className={clsx(
"absolute pt-20 top-0 right-0 w-105 px-6 pb-8 border-border-solid border-l h-screen",
className,
)}
>
{children}
</aside>,
document.body,

View File

@@ -54,7 +54,9 @@ 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>
<Title className="text-sm font-medium text-txt-primary">
{title}
</Title>
<Close asChild>
<Button
tabIndex={-1}

View File

@@ -31,6 +31,7 @@ export { Label } from "./Atoms/Label/Label";
export { PropertyRow } from "./Atoms/PropertyRow/PropertyRow";
export { Table, Thead, Tr, Tbody, Td, Th } from "./Atoms/Table/Table";
export { Tabs, TabLink } from "./Atoms/Tabs/Tabs";
export { Markdown } from "./Atoms/Markdown/Markdown";
// Molecules
export {

View File

@@ -1,3 +1,6 @@
@plugin "@tailwindcss/typography";
@config "../tailwind.config.js";
@theme {
--font-sans: "Geist", "sans-serif";
--color-*: initial;
@@ -31,6 +34,7 @@
--color-active: #e4f7c7;
--color-accent: #141e12;
--color-danger: #ffefef;
--color-danger-plain: #ffefef;
--color-danger-dark: #dc3d43;
--color-success: #eefadc;
--color-info: #edf6ff;
@@ -173,7 +177,7 @@
/* Text colors */
--color-txt-primary: #eceeec;
--color-invert: #151715;
--color-txt-invert: var(--color-invert);
--color-txt-invert: #fcfdfc;
--color-txt-secondary: #9aa299;
--color-txt-tertiary: #778175;
--color-txt-quaternary: #4c514b;
@@ -194,6 +198,7 @@
--color-active: rgba(239, 254, 226, 0.08);
--color-accent: #c4f042;
--color-danger: rgba(254, 58, 61, 0.13);
--color-danger-plain: rgba(254, 58, 61, 0.13);
--color-danger-dark: rgba(255, 89, 95, 0.94);
--color-success: rgba(184, 253, 106, 0.06);
--color-info: rgba(22, 119, 254, 0.14);
@@ -235,3 +240,24 @@
--color-danger-pressed: rgba(254, 58, 61, 0.5);
}
}
*:focus-visible {
box-shadow: var(--shadow-focus);
outline: none;
border-radius: 0.25rem;
}
::-webkit-scrollbar {
width: 7px;
height: 7px;
}
::-webkit-scrollbar-track {
background: var(--color-level-1);
padding: 1px;
}
::-webkit-scrollbar-thumb {
background: var(--color-txt-tertiary);
border-radius: 4px;
}