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}
/>
);
}