Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
1 change: 1 addition & 0 deletions src/frontend/packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"dependencies": {
"@radix-ui/react-slot": "^1.1.1",
"@radix-ui/react-toast": "^1.2.4",
"@tanstack/react-table": "^8.20.6",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "0.473.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Meta, StoryObj } from '@storybook/react';
import { DataTable, Stock } from './data-table';

const meta: Meta<typeof DataTable> = {
title: 'Widget/DataTable',
component: DataTable,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
};
export default meta;

type Story = StoryObj<typeof DataTable>;

const sampleData: Stock[] = [
{
id: '1',
name: 'Samsung',
currPrice: '10000',
fluctuation: '+0.1',
volume: '1000',
},
{
id: '2',
name: 'Apple',
currPrice: '20000',
fluctuation: '+0.2',
volume: '2000',
},
{
id: '3',
name: 'Google',
currPrice: '30000',
fluctuation: '-0.3',
volume: '3000',
},
];

export const Default: Story = {
args: {
data: sampleData,
},
};
146 changes: 146 additions & 0 deletions src/frontend/packages/ui/src/components/DataTable/data-table.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
'use client';

import * as React from 'react';
import {
ColumnDef,
SortingState,
flexRender,
getCoreRowModel,
getSortedRowModel,
useReactTable,
} from '@tanstack/react-table';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '../Table';
import { Button } from '../Button';
import { ArrowUpDown } from 'lucide-react';

export type Stock = {
id: string;
name: string;
currPrice: string;
fluctuation: string;
volume: string;
};

export const columns: ColumnDef<Stock>[] = [
{
accessorKey: 'name',
header: () => <div className="text-left">종목명</div>,
cell: ({ row }) => <div className="text-left">{row.getValue('name')}</div>,
},
{
accessorKey: 'currPrice',
header: ({ column }) => (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
>
현재가
<ArrowUpDown />
</Button>
),
cell: ({ row }) => {
const price = parseFloat(row.getValue('currPrice'));
const formatted = new Intl.NumberFormat('en-US', {
style: 'decimal',
currency: 'KRW',
}).format(price);
return <div className="text-right font-medium">{`${formatted}원`}</div>;
},
},
{
accessorKey: 'fluctuation',
accessorFn: (row) => parseFloat(row.fluctuation),
header: ({ column }) => (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
>
등락률
<ArrowUpDown />
</Button>
),
cell: ({ row }) => <div>{row.getValue('fluctuation')}%</div>,
},
{
accessorKey: 'volume',
header: ({ column }) => (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
>
거래량
<ArrowUpDown />
</Button>
),
cell: ({ row }) => <div>{row.getValue('volume')}</div>,
},
];

export function DataTable({ data }: { data: Stock[] }) {
const [sorting, setSorting] = React.useState<SortingState>([]);

const table = useReactTable({
data,
columns,
onSortingChange: setSorting,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
state: {
sorting,
},
});

return (
<div className="flex w-[600px] flex-wrap">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
)}
</TableHead>
);
})}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow
key={row.id}
data-state={row.getIsSelected() && 'selected'}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
);
}
1 change: 1 addition & 0 deletions src/frontend/packages/ui/src/components/DataTable/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { DataTable } from './data-table';
8 changes: 8 additions & 0 deletions src/frontend/packages/ui/src/components/Table/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export {
Table,
TableHeader,
TableBody,
TableHead,
TableRow,
TableCell,
} from './table';
48 changes: 48 additions & 0 deletions src/frontend/packages/ui/src/components/Table/table.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Meta, StoryObj } from '@storybook/react';
import {
Table,
TableHeader,
TableBody,
TableHead,
TableRow,
TableCell,
} from './table';

const meta: Meta<typeof Table> = {
title: 'Widget/Table',
component: Table,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
};
export default meta;

type Story = StoryObj<typeof Table>;

export const Default: Story = {
render: () => (
<div className="flex w-[600px] flex-wrap">
<Table>
<TableHeader>
<TableRow>
<TableHead className="text-left">종목</TableHead>
<TableHead>현재가</TableHead>
<TableHead>등락률</TableHead>
<TableHead>거래량</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{Array.from({ length: 5 }).map((_, index) => (
<TableRow key={index}>
<TableCell className="text-left">삼성전자</TableCell>
<TableCell>10,000</TableCell>
<TableCell>+00.0%</TableCell>
<TableCell>1,000</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
),
};
83 changes: 83 additions & 0 deletions src/frontend/packages/ui/src/components/Table/table.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
'use client';

import * as React from 'react';

import { cn } from '@workspace/ui/lib/utils';

const Table = React.forwardRef<
HTMLTableElement,
React.HTMLAttributes<HTMLTableElement>
>(({ className, ...props }, ref) => (
<div className={'relative w-full overflow-auto rounded-sm border'}>
<table ref={ref} className={cn('w-full text-sm', className)} {...props} />
</div>
));
Table.displayName = 'Table';

const TableHeader = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<thead
ref={ref}
className={cn('bg-muted/50 [&_tr]:border-b', className)}
{...props}
/>
));
TableHeader.displayName = 'TableHeader';

const TableBody = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tbody
ref={ref}
className={cn('[&_tr:last-child]:border-0', className)}
{...props}
/>
));
TableBody.displayName = 'TableBody';

const TableRow = React.forwardRef<
HTMLTableRowElement,
React.HTMLAttributes<HTMLTableRowElement>
>(({ className, ...props }, ref) => (
<tr
ref={ref}
className={cn(
'h-20 border-b transition-colors text-right hover:bg-muted/50',
className
)}
{...props}
/>
));
TableRow.displayName = 'TableRow';

const TableHead = React.forwardRef<
HTMLTableCellElement,
React.ThHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<th
ref={ref}
className={cn(
'h-10 pl-5 text-right align-middle font-medium text-muted-foreground',
className
)}
{...props}
/>
));
TableHead.displayName = 'TableHead';

const TableCell = React.forwardRef<
HTMLTableCellElement,
React.TdHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<td
ref={ref}
className={cn('px-4 py-2 align-middle', className)}
{...props}
/>
));
TableCell.displayName = 'TableCell';

export { Table, TableHeader, TableBody, TableHead, TableRow, TableCell };
2 changes: 2 additions & 0 deletions src/frontend/packages/ui/src/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export * from './Button';
export * from './Badge';
export * from './Textarea';
export * from './Table';
export * from './DataTable';
22 changes: 22 additions & 0 deletions src/frontend/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.