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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,5 @@ npm-debug.log*
yarn-debug.log*
yarn-error.log*
storybook-static

.cache
6 changes: 6 additions & 0 deletions .storybook/main.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import ViteYaml from '@modyfi/vite-plugin-yaml';
import type { StorybookConfig } from '@storybook/react-vite';
import path from 'node:path';
import { mergeConfig } from 'vite';
import viteTsconfigPaths from 'vite-tsconfig-paths';

Expand All @@ -20,6 +21,11 @@ const config: StorybookConfig = {
viteFinal: async (config) =>
mergeConfig(config, {
plugins: [viteTsconfigPaths(), ViteYaml()],
resolve: {
alias: {
'virtual:pwa-register': path.resolve(__dirname, './mocks/pwa-register.ts'),
},
},
define: {
__GIT_COMMIT__: JSON.stringify('storybook'),
__GIT_TAG__: JSON.stringify(''),
Expand Down
8 changes: 8 additions & 0 deletions .storybook/mocks/pwa-register.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
type RegisterSWOptions = {
onNeedRefresh?: () => void;
onOfflineReady?: () => void;
};

export function registerSW(_options?: RegisterSWOptions) {
return async (_reloadPage?: boolean) => {};
}
30 changes: 29 additions & 1 deletion .storybook/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,33 @@ import { MemoryRouter } from 'react-router-dom';
import '../src/i18n';
import '../src/styles/index.scss';

const mobileViewports = {
androidTypical: {
name: 'Android Typical',
styles: {
width: '360px',
height: '800px',
},
type: 'mobile',
},
iphoneModern: {
name: 'iPhone Modern',
styles: {
width: '393px',
height: '852px',
},
type: 'mobile',
},
phoneLegacy: {
name: 'Phone 2016',
styles: {
width: '375px',
height: '667px',
},
type: 'mobile',
},
};

const preview: Preview = {
decorators: [
(Story) => (
Expand All @@ -23,7 +50,8 @@ const preview: Preview = {
},
layout: 'centered',
viewport: {
defaultViewport: 'mobile1',
defaultViewport: 'androidTypical',
options: mobileViewports,
},
},
};
Expand Down
48 changes: 48 additions & 0 deletions src/components/ActivityRow/ActivityRow.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import type { Meta, StoryObj } from '@storybook/react';
import { storybookCompetitionFixture } from '@/storybook/competitionFixtures';
import { ActivityRow } from './ActivityRow';

const mainRoom = storybookCompetitionFixture.schedule.venues[0].rooms[0];
const stageActivity = mainRoom.activities[0].childActivities[0];
const otherActivity = storybookCompetitionFixture.schedule.venues[0].rooms[1].activities[1];

const meta = {
title: 'Components/Competition/ActivityRow',
component: ActivityRow,
decorators: [
(Story) => (
<div className="w-full max-w-2xl bg-panel">
<Story />
</div>
),
],
parameters: {
layout: 'padded',
},
tags: ['autodocs'],
} satisfies Meta<typeof ActivityRow>;

export default meta;

type Story = StoryObj<typeof meta>;

export const WithStage: Story = {
args: {
activity: stageActivity,
competitionId: storybookCompetitionFixture.id,
stage: {
name: 'Blue Stage',
color: '#3b82f6',
},
timeZone: 'America/Los_Angeles',
},
};

export const WithoutStage: Story = {
args: {
activity: otherActivity,
competitionId: storybookCompetitionFixture.id,
timeZone: 'America/Los_Angeles',
showRoom: false,
},
};
19 changes: 14 additions & 5 deletions src/components/ActivityRow/ActivityRow.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,31 @@
import { Activity, Room, Venue } from '@wca/helpers';
import classNames from 'classnames';
import { useMemo } from 'react';
import { Link, useParams } from 'react-router-dom';
import { Link } from 'react-router-dom';
import { Stage } from '@/extensions/org.cubingusa.natshelper.v1/types';
import { useNow } from '@/hooks/useNow';
import { activityCodeToName } from '@/lib/activityCodes';
import { LinkRenderer } from '@/lib/linkRenderer';
import { formatTimeRange } from '@/lib/time';
import { RoomPill } from '../Pill';

interface ActivityRowProps {
activity: Activity;
competitionId: string;
stage?: Pick<Stage | Room, 'name' | 'color'>;
timeZone: Venue['timezone'];
showRoom?: boolean;
LinkComponent?: LinkRenderer;
}

export function ActivityRow({ activity, stage, timeZone, showRoom = true }: ActivityRowProps) {
const { competitionId } = useParams();
export function ActivityRow({
activity,
competitionId,
stage,
timeZone,
showRoom = true,
LinkComponent = Link,
}: ActivityRowProps) {
const now = useNow();

const isOver = useMemo(
Expand All @@ -28,7 +37,7 @@ export function ActivityRow({ activity, stage, timeZone, showRoom = true }: Acti
: activityCodeToName(activity.activityCode);

return (
<Link
<LinkComponent
key={activity.id}
className={classNames(
'flex flex-col w-full p-2 type-body even:table-bg-row-alt hover:table-bg-row-hover',
Expand All @@ -50,6 +59,6 @@ export function ActivityRow({ activity, stage, timeZone, showRoom = true }: Acti
)}
<span>{formatTimeRange(activity.startTime, activity.endTime, 5, timeZone)}</span>
</span>
</Link>
</LinkComponent>
);
}
2 changes: 1 addition & 1 deletion src/components/AppUpdatePrompt/AppUpdatePrompt.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react';
import { AppUpdatePrompt } from './AppUpdatePrompt';

const meta = {
title: 'Components/AppUpdatePrompt',
title: 'Components/App/AppUpdatePrompt',
component: AppUpdatePrompt,
args: {
onUpdate: () => {},
Expand Down
61 changes: 61 additions & 0 deletions src/components/AssignmentCodeCell/AssignmentCodeCell.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import type { Meta, StoryObj } from '@storybook/react';
import { AssignmentCodeCell } from './AssignmentCodeCell';

const meta = {
title: 'Components/Competition/AssignmentCodeCell',
component: AssignmentCodeCell,
decorators: [
(Story) => (
<div className="w-full max-w-xl bg-panel p-4">
<table className="w-full">
<tbody>
<tr>
<Story />
</tr>
</tbody>
</table>
</div>
),
],
tags: ['autodocs'],
} satisfies Meta<typeof AssignmentCodeCell>;

export default meta;

type Story = StoryObj<typeof meta>;

export const Competitor: Story = {
args: {
assignmentCode: 'competitor',
},
};

export const Verb: Story = {
args: {
assignmentCode: 'staff-judge',
grammar: 'verb',
},
};

export const Letter: Story = {
args: {
assignmentCode: 'staff-scrambler',
letter: true,
},
};

export const BorderedCount: Story = {
args: {
as: 'div',
assignmentCode: 'staff-runner',
border: true,
count: 4,
},
decorators: [
(Story) => (
<div className="w-full max-w-xl bg-panel p-4">
<Story />
</div>
),
],
};
31 changes: 31 additions & 0 deletions src/components/AssignmentLabel/AssignmentLabel.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { Meta, StoryObj } from '@storybook/react';
import { AssignmentLabel } from './AssignmentLabel';

const meta = {
title: 'Components/Competition/AssignmentLabel',
component: AssignmentLabel,
decorators: [
(Story) => (
<div className="p-4">
<Story />
</div>
),
],
tags: ['autodocs'],
} satisfies Meta<typeof AssignmentLabel>;

export default meta;

type Story = StoryObj<typeof meta>;

export const Competitor: Story = {
args: {
assignmentCode: 'competitor',
},
};

export const Judge: Story = {
args: {
assignmentCode: 'staff-judge',
},
};
51 changes: 51 additions & 0 deletions src/components/Breadcrumbs/Breadcrumbs.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import type { Meta, StoryObj } from '@storybook/react';
import { Breadcrumbs } from './Breadcrumbs';

const meta = {
title: 'Components/Competition/Breadcrumbs',
component: Breadcrumbs,
decorators: [
(Story) => (
<div className="bg-panel p-4">
<Story />
</div>
),
],
tags: ['autodocs'],
} satisfies Meta<typeof Breadcrumbs>;

export default meta;

type Story = StoryObj<typeof meta>;

export const LinkedTrail: Story = {
args: {
breadcrumbs: [
{
label: '3x3x3 Cube, Round 1',
href: '/competitions/SeattleSummerOpen2026/events/333-r1',
},
{
label: 'Group 1',
},
],
},
};

export const MultipleLinks: Story = {
args: {
breadcrumbs: [
{
label: 'Schedule',
href: '/competitions/SeattleSummerOpen2026/activities',
},
{
label: 'Main Stage',
href: '/competitions/SeattleSummerOpen2026/rooms/10',
},
{
label: '3x3x3 Cube, Round 1',
},
],
},
};
8 changes: 5 additions & 3 deletions src/components/Breadcrumbs/Breadcrumbs.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import classNames from 'classnames';
import { Fragment } from 'react';
import { Link } from 'react-router-dom';
import { LinkRenderer } from '@/lib/linkRenderer';
import { BreadcrumbPill, PillProps } from '../Pill';

export type Breadcrumb =
Expand All @@ -15,16 +16,17 @@ export type Breadcrumb =

export interface BreadcrumbsProps {
breadcrumbs: Breadcrumb[];
LinkComponent?: LinkRenderer;
}

export const Breadcrumbs = ({ breadcrumbs }: BreadcrumbsProps) => {
export const Breadcrumbs = ({ breadcrumbs, LinkComponent = Link }: BreadcrumbsProps) => {
return (
<div className="flex items-center space-x-1">
{breadcrumbs.map(({ label, ...breadcrumb }, index) => (
<Fragment key={label}>
{index > 0 && <span className="text-muted">·</span>}
{'href' in breadcrumb ? (
<Link to={breadcrumb.href}>
<LinkComponent to={breadcrumb.href}>
<BreadcrumbPill
{...breadcrumb.pillProps}
className={classNames(
Expand All @@ -33,7 +35,7 @@ export const Breadcrumbs = ({ breadcrumbs }: BreadcrumbsProps) => {
)}>
{label}
</BreadcrumbPill>
</Link>
</LinkComponent>
) : (
<span key={label}>{label}</span>
)}
Expand Down
Loading
Loading