Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: Deploy Storybook to Pages

# Builds the Storybook static site and publishes it to GitHub Pages on every
# push to main. Enable once in Settings → Pages → Source: "GitHub Actions".
on:
push:
branches: [main]
workflow_dispatch:

permissions:
contents: read
pages: write
id-token: write

# Allow one concurrent deployment; don't cancel an in-progress one.
concurrency:
group: pages
cancel-in-progress: false

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
# Generate the Panda styled-system the stories import from.
- run: pnpm codegen
- run: pnpm build-storybook
- uses: actions/upload-pages-artifact@v3
with:
path: storybook-static

deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- id: deployment
uses: actions/deploy-pages@v4
6 changes: 6 additions & 0 deletions docs/component-authoring.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import { hstack, stack } from "@/styled/patterns";
- colors: `brand.*`, `text.{default,lighter,onbutton-default,onbutton-alternative,onbutton-disabled}`,
`action.{default,disabled,alt-default,hover,pressed,focus}`,
`bg.{primary,secondary,primary-alternative,primary-highlight,secondary-highlight}`,
`category.{violet,green,red,yellow,pink,orange,green-light}` (speaker palette),
`system.{success,error,warning,info}` (+ `-secondary`).
- radii (px-accurate to Figma): `xs`=2, `sm`=4, `md`=8, `lg`=16, `xl`=24, `full`.
- textStyles: `title.md`, `subtitle.{md,sm}`, `paragraph.{md,sm,xsm}`,
Expand Down Expand Up @@ -94,6 +95,11 @@ a single variant symbol id, not the whole family frame.
| CheckCircle | `1568:25593` | |
| Logo | `40000499:40071` | Logo / Logo+Feature / Iso |
| Tag | `40000041:10589` | Persona/CUIJ/Num_Expediente/Num_Actuacion/Fecha |
| AvatarPill ✅ | `40002313:53080` | Default/Selected; reuses Avatar + name |
| Option ✅ | `40001295:56887` | menu/list row: label + X; selected bg |
| Category Item ✅ | `40001297:49803` | label + Switch row |
| TranscriptBlock ✅ | `40002318:32859` | Voz a texto turn: Default/Select/Select+Hover/Typed |
| Side Panel (Voz a texto) ✅ | `40002322:53113` | composite: turno + personas sugeridas + marca de tiempo + acciones |

## Verify

Expand Down
42 changes: 42 additions & 0 deletions src/components/avatar-pill/AvatarPill.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import type { Meta, StoryObj } from "@storybook/react";
import { AvatarPill } from "./AvatarPill";

const meta = {
title: "Components/AvatarPill",
component: AvatarPill,
parameters: {
layout: "centered",
figma: {
url: "https://www.figma.com/design/2BahKpebYzaccFih0ZB79y?node-id=40002313:53080",
},
},
argTypes: {
selected: { control: "boolean" },
color: {
control: "inline-radio",
options: ["primary", "secondary", "warning", "success"],
},
},
} satisfies Meta<typeof AvatarPill>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Default: Story = {
args: { initials: "AB", name: "Name Surname", selected: false },
};

export const Selected: Story = {
args: { initials: "AB", name: "Name Surname", selected: true },
};

export const Matrix: Story = {
render: () => (
<div style={{ display: "flex", flexDirection: "column", gap: "8px" }}>
<AvatarPill initials="AB" name="Persona 1" color="primary" />
<AvatarPill initials="AB" name="Persona 2" color="success" />
<AvatarPill initials="JU" name="Jueza" color="warning" />
<AvatarPill initials="DE" name="Defensor" color="secondary" selected />
</div>
),
};
73 changes: 73 additions & 0 deletions src/components/avatar-pill/AvatarPill.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { type RecipeVariantProps, cva, cx } from "@/styled/css";
import type { HTMLAttributes } from "react";
import { Avatar, type AvatarColor } from "../avatar";

/**
* AvatarPill — speaker chip used in the Voz a texto (speech-to-text) side panel.
* AymurAI UI Library node 40002313:53080.
*
* Reuses {@link Avatar} (24px circle) plus a name label inside a rounded pill.
* Two states: Default (white bg, lighter name) and Selected (primary-alternative
* bg, default name). The avatar circle colour is per-speaker (passthrough).
*/
const pillRoot = cva({
base: {
display: "inline-flex",
alignItems: "center",
gap: "2", // 8px
p: "2", // 8px
rounded: "xl", // 24px
cursor: "default",
Comment on lines +13 to +20

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): AvatarPill is used as a clickable control but rendered as a non-interactive span with a default cursor.

In SidePanel this pill is wired with onClick/selected but still lacks keyboard affordances and a pointer cursor, so it’s effectively inaccessible to keyboard users. Please either render a semantic <button> (or a <div role="button" tabIndex={0}> with cursor: "pointer") when clickable, or add a prop (e.g. interactive / as="button") that switches to an interactive element for these cases.

userSelect: "none",
},
variants: {
selected: {
true: { bg: "bg.primary-alternative" }, // #E5E8FF
false: { bg: "bg.secondary" }, // #FFFFFF
},
},
defaultVariants: { selected: false },
});

const pillName = cva({
base: {
textStyle: "label.md.default", // Archivo 16px
whiteSpace: "nowrap",
flexShrink: "0",
},
variants: {
selected: {
true: { color: "text.default" }, // #110041
false: { color: "text.lighter" }, // #625C68
},
},
defaultVariants: { selected: false },
});

export type AvatarPillProps = RecipeVariantProps<typeof pillRoot> & {
/** Initials shown inside the avatar circle (e.g. "AB") */
initials: string;
/** Speaker name shown next to the avatar */
name: string;
/** Avatar circle colour (per-speaker) */
color?: AvatarColor;
className?: string;
} & Omit<HTMLAttributes<HTMLSpanElement>, "color">;

export function AvatarPill({
initials,
name,
selected,
color = "primary",
className,
...props
}: AvatarPillProps) {
return (
<span className={cx(pillRoot({ selected }), className)} {...props}>
<Avatar initials={initials} size="sm" color={color} />
<span className={pillName({ selected })}>{name}</span>
</span>
);
}

export default AvatarPill;
2 changes: 2 additions & 0 deletions src/components/avatar-pill/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { AvatarPill, default } from "./AvatarPill";
export type { AvatarPillProps } from "./AvatarPill";
21 changes: 20 additions & 1 deletion src/components/avatar/Avatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,31 @@ const avatar = cva({
secondary: { bg: "bg.secondary-highlight" },
warning: { bg: "system.warning-secondary" },
success: { bg: "system.success-secondary" },
// Speaker palette (Figma "bg/category/*").
violet: { bg: "category.violet" },
green: { bg: "category.green" },
red: { bg: "category.red" },
yellow: { bg: "category.yellow" },
pink: { bg: "category.pink" },
orange: { bg: "category.orange" },
"green-light": { bg: "category.green-light" },
},
},
defaultVariants: { size: "sm", color: "primary" },
});

export type AvatarColor = "primary" | "secondary" | "warning" | "success";
export type AvatarColor =
| "primary"
| "secondary"
| "warning"
| "success"
| "violet"
| "green"
| "red"
| "yellow"
| "pink"
| "orange"
| "green-light";

export type AvatarProps = RecipeVariantProps<typeof avatar> & {
/** Initials shown inside the circle (e.g. "AD") */
Expand Down
51 changes: 51 additions & 0 deletions src/components/category-item/CategoryItem.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import type { Meta, StoryObj } from "@storybook/react";
import { CategoryItem } from "./CategoryItem";

const meta = {
title: "Components/CategoryItem",
component: CategoryItem,
parameters: {
layout: "centered",
figma: {
url: "https://www.figma.com/design/2BahKpebYzaccFih0ZB79y?node-id=40001297:49803",
},
},
} satisfies Meta<typeof CategoryItem>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Default: Story = {
args: { label: "Categoria" },
decorators: [
(Story) => (
<div style={{ width: 415 }}>
<Story />
</div>
),
],
};

export const Checked: Story = {
args: { label: "Categoria", defaultChecked: true },
decorators: [
(Story) => (
<div style={{ width: 415 }}>
<Story />
</div>
),
],
};

export const List: Story = {
render: () => (
<div
style={{ display: "flex", flexDirection: "column", gap: 4, width: 415 }}
>
<CategoryItem label="Personas" defaultChecked />
<CategoryItem label="Fechas" />
<CategoryItem label="Números de expediente" defaultChecked />
<CategoryItem label="Categoría deshabilitada" disabled />
</div>
),
};
59 changes: 59 additions & 0 deletions src/components/category-item/CategoryItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { css, cx } from "@/styled/css";
import { Switch, type SwitchProps } from "../switch";

/**
* CategoryItem — a labelled row with a trailing toggle, used to enable/disable
* categories. AymurAI UI Library node 40001297:49803.
*
* Layout: bg.secondary (white), h 40px, px 16px / py 8px, rounded sm (4px),
* label on the left (label.md.default) and a {@link Switch} on the right.
*/
const root = css({
display: "flex",
alignItems: "center",
justifyContent: "space-between",
gap: "2",
w: "full",
h: "[40px]",
px: "4", // 16px
py: "2", // 8px
rounded: "sm", // 4px
bg: "bg.secondary",
textStyle: "label.md.default",
color: "text.default",
});

export type CategoryItemProps = {
/** Row label */
label: string;
className?: string;
} & Pick<
SwitchProps,
"checked" | "defaultChecked" | "onCheckedChange" | "disabled" | "name"
>;

export function CategoryItem({
label,
className,
checked,
defaultChecked,
onCheckedChange,
disabled,
name,
}: CategoryItemProps) {
return (
<div className={cx(root, className)}>
<span>{label}</span>
<Switch
checked={checked}
defaultChecked={defaultChecked}
onCheckedChange={onCheckedChange}
disabled={disabled}
name={name}
aria-label={label}
/>
</div>
);
}

export default CategoryItem;
2 changes: 2 additions & 0 deletions src/components/category-item/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { CategoryItem, default } from "./CategoryItem";
export type { CategoryItemProps } from "./CategoryItem";
52 changes: 52 additions & 0 deletions src/components/option/Option.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import type { Meta, StoryObj } from "@storybook/react";
import { Option } from "./Option";

const meta = {
title: "Components/Option",
component: Option,
parameters: {
layout: "centered",
figma: {
url: "https://www.figma.com/design/2BahKpebYzaccFih0ZB79y?node-id=40001295:56887",
},
},
argTypes: {
selected: { control: "boolean" },
},
} satisfies Meta<typeof Option>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Default: Story = {
args: { label: "Content", selected: false },
decorators: [
(Story) => (
<div style={{ width: 407 }}>
<Story />
</div>
),
],
};

export const Selected: Story = {
args: { label: "Content", selected: true },
decorators: [
(Story) => (
<div style={{ width: 407 }}>
<Story />
</div>
),
],
};

export const List: Story = {
render: () => (
<div style={{ display: "flex", flexDirection: "column", width: 407 }}>
<Option label="Persona 1" selected />
<Option label="Persona 2" />
<Option label="Jueza" />
<Option label="Fiscal" />
</div>
),
};
Loading
Loading