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
108 changes: 108 additions & 0 deletions src/components/CorpusCard/CorpusCard.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import type { Meta, StoryObj } from '@storybook/react-vite';

import CorpusCard, { CorpusCardRow } from './CorpusCard';

const meta: Meta<typeof CorpusCard> = {
title: 'Molecules/CorpusCard',
component: CorpusCard,
tags: ['autodocs'],
parameters: {
router: {
initialEntries: ['/'],
routes: ['/'],
},
},
};

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

export const Default: Story = {
args: {
title: 'EcoCor Corpus of English Novels',
to: '/',
children: (
<>
<CorpusCardRow label="Number of texts" value={128} />
<CorpusCardRow label="Number of authors" value={87} />
<CorpusCardRow label="Number of paragraphs" value={42317} />
<CorpusCardRow label="Number of Words" value={4218933} />
<CorpusCardRow label="Number of Entities" value={12043} />
</>
),
},
};

export const WithCommit: Story = {
args: {
title: 'German EcoCor',
to: '/',
className: 'w-full md:w-96',
commit: 'f086cb0ea1f99907d99fba12807e866875ed645d',
repo: 'https://github.com/EcoCor/eco-de',
updated: '2026-05-08T18:47:05Z',
children: (
<>
<CorpusCardRow label="Number of texts" value={190} />
<CorpusCardRow label="Number of authors" value={95} />
<CorpusCardRow label="Number of Paragraphs" value={123638} />
<CorpusCardRow label="Number of Words" value={7395276} />
</>
),
},
};

export const WithPopover: Story = {
args: {
title: 'ELTeC English Novel Corpus',
to: '/',
className: 'w-full md:w-96',
commit: '639316c845b80f29bc51b805ce7e7230d4ec63a8',
repo: 'https://github.com/clscor-io/ELTeC-eng',
updated: '2026-04-12T09:15:00Z',
children: (
<>
<CorpusCardRow
label="ELTeC Score"
value={100}
valueClassName="text-2xl"
popoverContent={
<table className="m-0 text-xs">
<caption className="text-gray-600">Sub&nbsp;scores</caption>
<tbody>
<tr>
<th className="p-0.5 text-right italic">text</th>
<td className="p-0.5 text-right">10</td>
</tr>
<tr>
<th className="p-0.5 text-right italic">range</th>
<td className="p-0.5 text-right">9</td>
</tr>
<tr>
<th className="p-0.5 text-right italic">female</th>
<td className="p-0.5 text-right">11</td>
</tr>
<tr>
<th className="p-0.5 text-right italic">reprint</th>
<td className="p-0.5 text-right">10</td>
</tr>
</tbody>
</table>
}
/>
<CorpusCardRow label="Number of texts" value={100} />
<CorpusCardRow label="Number of authors" value={80} />
<CorpusCardRow label="Number of Paragraphs" value={233391} />
<CorpusCardRow label="Number of Words" value={12560916} />
</>
),
},
};

export const NoMetrics: Story = {
args: {
title: 'An empty corpus card',
to: '/',
className: 'w-full md:w-96',
},
};
31 changes: 31 additions & 0 deletions src/components/CorpusCard/CorpusCard.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { render } from '@testing-library/react';
import { composeStory } from '@storybook/react-vite';
import Meta, {
Default,
WithCommit,
WithPopover,
NoMetrics,
} from './CorpusCard.stories';

const DefaultCard = composeStory(Default, Meta);
const WithCommitCard = composeStory(WithCommit, Meta);
const WithPopoverCard = composeStory(WithPopover, Meta);
const NoMetricsCard = composeStory(NoMetrics, Meta);

describe('CorpusCard', () => {
test('renders default corpus card', () => {
render(<DefaultCard />);
});

test('renders card with commit and updated', () => {
render(<WithCommitCard />);
});

test('renders card with popover row', () => {
render(<WithPopoverCard />);
});

test('renders card without metrics', () => {
render(<NoMetricsCard />);
});
});
109 changes: 109 additions & 0 deletions src/components/CorpusCard/CorpusCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { type ReactNode, useId } from 'react';
import { Link } from '@tanstack/react-router';
import Commit from '../Commit';

export interface CorpusCardRowProps {
label: ReactNode;
value: ReactNode;
valueClassName?: string;
popoverContent?: ReactNode;
numberLocale?: string;
}

export function CorpusCardRow({
label,
value,
valueClassName,
popoverContent,
numberLocale = 'en',
}: CorpusCardRowProps) {
const id = useId();
const anchorName = `--anchor-${id.replace(/[^a-zA-Z0-9_-]/g, '-')}`;
const content =
typeof value === 'number' ? value.toLocaleString(numberLocale) : value;
const valueClasses = `font-bold ${valueClassName ?? ''}`.trim();
return (
<tr className="border-b-2 text-primary font-normal">
<td className="pt-2 pb-1 px-3">
{popoverContent ? (
<>
<button
type="button"
popoverTarget={id}
style={{ anchorName } as React.CSSProperties}
className={`${valueClasses} cursor-pointer`}
>
{content}
</button>
<div
id={id}
popover=""
className="m-0 p-3 rounded-lg border border-gray-200 shadow-lg bg-white text-sm [position-area:block-end_span-inline-end]"
style={{ positionAnchor: anchorName } as React.CSSProperties}
>
{popoverContent}
</div>
</>
) : (
<span className={valueClasses}>{content}</span>
)}
</td>
<th className="pt-2 pb-1 px-3 text-right font-normal">{label}</th>
</tr>
);
}

export interface CorpusCardProps {
title: string;
to: string;
commit?: string;
repo?: string;
updated?: string;
locale?: string;
className?: string;
children?: ReactNode;
}

export default function CorpusCard({
title,
to,
commit,
repo,
updated,
locale,
className,
children,
}: CorpusCardProps) {
const hasFooterRows = Boolean(commit || updated);
const hasTable = Boolean(children) || hasFooterRows;
const wrapperClass =
`rounded-xl inline-block shadow-lg ${className ?? ''}`.trim();
return (
<div className={wrapperClass}>
<div className="bg-white rounded-t-xl p-2 text-2xl font-bold text-primary whitespace-nowrap text-ellipsis overflow-hidden">
<Link to={to as never} className="text-primary" title={title}>
{title}
</Link>
</div>
{hasTable && (
<table className="m-0 w-full">
<tbody>
{children}
{commit && (
<CorpusCardRow
label="Git commit"
value={<Commit repo={repo}>{commit}</Commit>}
/>
)}
{updated && (
<CorpusCardRow
label="last update"
value={new Date(updated).toLocaleString(locale)}
/>
)}
</tbody>
</table>
)}
</div>
);
}
2 changes: 2 additions & 0 deletions src/components/CorpusCard/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default, CorpusCardRow } from './CorpusCard';
export type { CorpusCardProps, CorpusCardRowProps } from './CorpusCard';
93 changes: 93 additions & 0 deletions src/components/DracorCorpusCard/DracorCorpusCard.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import type { Meta, StoryObj } from '@storybook/react-vite';

import DracorCorpusCard from './DracorCorpusCard';

const meta: Meta<typeof DracorCorpusCard> = {
title: 'Molecules/DracorCorpusCard',
component: DracorCorpusCard,
tags: ['autodocs'],
parameters: {
router: {
initialEntries: ['/'],
routes: ['/'],
},
},
};

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

export const Default: Story = {
args: {
name: 'ger',
title: 'German Drama Corpus',
to: '/',
metrics: {
plays: 704,
characters: 13832,
male: 10982,
female: 2847,
sp: 115234,
stage: 43217,
wordcount: { text: 8248968, sp: 6432100, stage: 1816868 },
updated: '2024-12-01T10:30:00Z',
},
},
};

export const WithCommit: Story = {
args: {
name: 'shake',
title: 'Shakespeare Drama Corpus',
to: '/',
commit: 'ae2d0c4d965bf796284334f6b99a529d9e485943',
repo: 'https://github.com/dracor-org/shakedracor',
metrics: {
plays: 37,
characters: 1244,
male: 982,
female: 262,
sp: 31420,
stage: 8934,
wordcount: { text: 884229, sp: 835112, stage: 49117 },
updated: '2024-11-15T08:00:00Z',
},
},
};

export const NoGender: Story = {
args: {
name: 'cal',
title: 'Calderón Drama Corpus',
to: '/',
metrics: {
plays: 244,
characters: 1742,
male: 0,
female: 0,
wordcount: { text: 3200000, sp: 2900000, stage: 300000 },
updated: '2024-10-20T12:00:00Z',
},
},
};

export const NeoLatin: Story = {
args: {
name: 'neolat',
title: 'Neo-Latin Drama Corpus',
acronym: 'NeoLatDraCor',
to: '/',
commit: '75eef622ff3e85d2f5d92b4717aae0810283efc9',
repo: 'https://github.com/dracor-org/neolatdracor',
metrics: {
plays: 22,
characters: 320,
male: 226,
female: 62,
sp: 4641,
stage: 609,
wordcount: { text: 125228, sp: 115474, stage: 1712 },
updated: '2026-05-08T18:47:05Z',
},
},
};
21 changes: 21 additions & 0 deletions src/components/DracorCorpusCard/DracorCorpusCard.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { render } from '@testing-library/react';
import { composeStory } from '@storybook/react-vite';
import Meta, { Default, WithCommit, NoGender } from './DracorCorpusCard.stories';

const DefaultCard = composeStory(Default, Meta);
const WithCommitCard = composeStory(WithCommit, Meta);
const NoGenderCard = composeStory(NoGender, Meta);

describe('DracorCorpusCard', () => {
test('renders default corpus card', () => {
render(<DefaultCard />);
});

test('renders card with commit', () => {
render(<WithCommitCard />);
});

test('renders card without gender breakdown', () => {
render(<NoGenderCard />);
});
});
Loading