Add measures tab

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-23 17:54:50 +02:00
committed by Sacha Al Himdani
parent fbfc3bb2b5
commit 1253145d3b
23 changed files with 1161 additions and 68 deletions

View File

@@ -31,6 +31,7 @@ const badge = tv({
},
},
defaultVariants: {
variant: "neutral",
size: "sm",
},
});

View File

@@ -1,13 +1,16 @@
import type { InputHTMLAttributes } from "react";
import { type InputHTMLAttributes, type FC, useState } from "react";
import { tv } from "tailwind-variants";
import type { IconProps } from "../Icons/type";
type Props = {
invalid?: boolean;
disabled?: boolean;
icon?: FC<IconProps>;
onValueChange?: (value: string) => void;
} & 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",
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",
variants: {
invalid: {
true: "border-border-danger",
@@ -15,6 +18,41 @@ export const input = tv({
},
});
export function Input({ invalid, ...props }: Props) {
export function Input({
invalid,
icon: IconComponent,
onValueChange,
...props
}: Props) {
if (IconComponent) {
const [focus, setFocus] = useState(false);
return (
<div
className={input({
className: "flex items-center gap-2",
})}
data-focus={focus}
>
<IconComponent size={16} className="text-txt-secondary" />
<input
onFocus={(e) => {
setFocus(true);
props.onFocus?.(e);
}}
onBlur={(e) => {
setFocus(false);
props.onBlur?.(e);
}}
aria-invalid={invalid}
className="w-full outline-none"
{...props}
onChange={(e) => {
onValueChange?.(e.currentTarget.value);
props.onChange?.(e);
}}
/>
</div>
);
}
return <input aria-invalid={invalid} className={input(props)} {...props} />;
}

View File

@@ -1,11 +1,15 @@
import clsx from "clsx";
type Props = { size: number; className?: string };
type Props = { size?: number; className?: string; centered?: boolean };
export function Spinner({ size = 16, className }: Props) {
export function Spinner({ size = 16, className, centered }: Props) {
return (
<div
className={clsx("animate-spin rounded-full border-b-2", className)}
className={clsx(
"animate-spin rounded-full border-b-2",
centered && "my-4 mx-auto",
className,
)}
style={{ width: size, height: size, borderColor: "currentColor" }}
/>
);

View File

@@ -0,0 +1,20 @@
import { Tabs, TabLink } from "./Tabs";
import type { Meta, StoryObj } from "@storybook/react";
export default {
title: "Atoms/Tabs",
component: Tabs,
argTypes: {},
} satisfies Meta<typeof Tabs>;
type Story = StoryObj<typeof Tabs>;
export const Default: Story = {
render: () => (
<Tabs>
<TabLink to="#">Tab 1</TabLink>
<TabLink to="#">Tab 2</TabLink>
<TabLink to="#">Tab 3</TabLink>
</Tabs>
),
};

View File

@@ -0,0 +1,33 @@
import type { PropsWithChildren } from "react";
import { NavLink } from "react-router";
import { Root, List } from "@radix-ui/react-tabs";
import clsx from "clsx";
export function Tabs(props: PropsWithChildren) {
return (
<Root className="TabsRoot" defaultValue="tab1">
<List className="TabsList" aria-label="Manage your account">
<div
className="border-b border-border-low flex gap-6 text-sm font-medium text-txt-secondary"
{...props}
/>
</List>
</Root>
);
}
export function TabLink(props: PropsWithChildren<{ to: string }>) {
return (
<NavLink
className={(params) =>
clsx(
"py-4 hover:text-txt-primary border-b-2 active:border-border-active -mb-[1px] active:text-txt-primary",
params.isActive
? "border-border-active text-txt-primary"
: "border-transparent",
)
}
{...props}
/>
);
}

View File

@@ -70,23 +70,35 @@ export function Dialog({
);
}
export function DialogFooter({ children }: { children?: ReactNode }) {
export function DialogFooter({
children,
exitLabel,
}: {
children?: ReactNode;
exitLabel?: string;
}) {
const { __ } = useTranslate();
return (
<footer className="flex justify-end items-center p-3 border-t border-t-border-low gap-2">
<Close asChild>
<Button variant="secondary">{__("Cancel")}</Button>
<Button variant="secondary">{exitLabel ?? __("Cancel")}</Button>
</Close>
{children}
</footer>
);
}
export function DialogContent(props: HTMLAttributes<HTMLDivElement>) {
export function DialogContent(
props: HTMLAttributes<HTMLDivElement> & { padded?: boolean },
) {
return (
<div
{...props}
className={clsx("overflow-y-auto", props.className)}
className={clsx(
"overflow-y-auto",
props.className,
props.padded && "p-6",
)}
style={{
maxHeight: "min(640px, calc(100vh - 140px))",
}}

View File

@@ -30,6 +30,7 @@ export { Select, Option } from "./Atoms/Select/Select.tsx";
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";
// Molecules
export {