chore(ui,icons,ui-preset,toolbox): Move design system packages to monorepo (#5470)
This commit is contained in:
committed by
GitHub
parent
71853eafdd
commit
e4ce2f4e07
1
packages/design-system/ui/src/components/table/index.ts
Normal file
1
packages/design-system/ui/src/components/table/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./table"
|
||||
280
packages/design-system/ui/src/components/table/table.stories.tsx
Normal file
280
packages/design-system/ui/src/components/table/table.stories.tsx
Normal file
@@ -0,0 +1,280 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react"
|
||||
import * as React from "react"
|
||||
|
||||
import { Table } from "./table"
|
||||
|
||||
const meta: Meta<typeof Table> = {
|
||||
title: "Components/Table",
|
||||
component: Table,
|
||||
parameters: {
|
||||
layout: "centered",
|
||||
},
|
||||
}
|
||||
|
||||
export default meta
|
||||
|
||||
type Story = StoryObj<typeof Table>
|
||||
|
||||
type Order = {
|
||||
id: string
|
||||
displayId: number
|
||||
customer: string
|
||||
email: string
|
||||
amount: number
|
||||
currency: string
|
||||
}
|
||||
|
||||
const firstNames = [
|
||||
"Charles",
|
||||
"Cooper",
|
||||
"Johhny",
|
||||
"Elvis",
|
||||
"John",
|
||||
"Jane",
|
||||
"Joe",
|
||||
"Jack",
|
||||
"Jill",
|
||||
"Jenny",
|
||||
]
|
||||
const lastNames = [
|
||||
"Brown",
|
||||
"Smith",
|
||||
"Johnson",
|
||||
"Williams",
|
||||
"Jones",
|
||||
"Miller",
|
||||
"Davis",
|
||||
"Garcia",
|
||||
"Rodriguez",
|
||||
"Wilson",
|
||||
]
|
||||
const currencies = ["USD", "EUR", "GBP", "JPY"]
|
||||
|
||||
function makeDate(x: number): Order[] {
|
||||
// get random name
|
||||
const getRandomName = () => {
|
||||
const firstName = firstNames[Math.floor(Math.random() * firstNames.length)]
|
||||
const lastName = lastNames[Math.floor(Math.random() * lastNames.length)]
|
||||
return `${firstName} ${lastName}`
|
||||
}
|
||||
|
||||
const getRandomId = () => {
|
||||
return `order_${Math.floor(Math.random() * 100000)}`
|
||||
}
|
||||
|
||||
const getRandomDisplayId = () => {
|
||||
return Math.floor(Math.random() * 100000)
|
||||
}
|
||||
|
||||
const getRandomAmount = () => {
|
||||
return Math.floor(Math.random() * 1000)
|
||||
}
|
||||
|
||||
const getRandomCurrency = () => {
|
||||
return currencies[Math.floor(Math.random() * currencies.length)]
|
||||
}
|
||||
|
||||
const getRandomEmail = () => {
|
||||
return `${Math.floor(Math.random() * 100000)}@gmail.com`
|
||||
}
|
||||
|
||||
// Create x random orders and resolve them after 1 second
|
||||
const orders = Array.from({ length: x }, () => ({
|
||||
id: getRandomId(),
|
||||
displayId: getRandomDisplayId(),
|
||||
customer: getRandomName(),
|
||||
email: getRandomEmail(),
|
||||
amount: getRandomAmount(),
|
||||
currency: getRandomCurrency(),
|
||||
}))
|
||||
|
||||
return orders
|
||||
}
|
||||
|
||||
type UseFakeOrdersProps = {
|
||||
limit: number
|
||||
offset: number
|
||||
}
|
||||
|
||||
const useFakeOrders = ({ offset, limit }: UseFakeOrdersProps) => {
|
||||
const COUNT = 1000
|
||||
|
||||
const [orders, setOrders] = React.useState<Order[]>(makeDate(limit))
|
||||
const [offsetState, setOffsetState] = React.useState<number | undefined>(0)
|
||||
const [isLoading, setIsLoading] = React.useState(false)
|
||||
|
||||
// Fake API call
|
||||
React.useEffect(() => {
|
||||
const fetchOrders = async () => {
|
||||
if (offset === offsetState) {
|
||||
return
|
||||
}
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 500))
|
||||
|
||||
if (offset > COUNT) {
|
||||
return
|
||||
}
|
||||
|
||||
const newOrders = makeDate(limit)
|
||||
|
||||
setOrders(newOrders)
|
||||
|
||||
setOffsetState(offset)
|
||||
}
|
||||
|
||||
setIsLoading(true)
|
||||
|
||||
fetchOrders().then(() => {
|
||||
setIsLoading(false)
|
||||
})
|
||||
}, [offset, limit, orders, offsetState])
|
||||
|
||||
return {
|
||||
orders,
|
||||
isLoading,
|
||||
count: COUNT,
|
||||
}
|
||||
}
|
||||
|
||||
const fakeData = makeDate(10)
|
||||
|
||||
console.log(JSON.stringify(fakeData, null, 2))
|
||||
|
||||
const formatCurrency = (amount: number, currency: string) => {
|
||||
return new Intl.NumberFormat("en-US", {
|
||||
style: "currency",
|
||||
currency,
|
||||
signDisplay: "always",
|
||||
}).format(amount)
|
||||
}
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => {
|
||||
return (
|
||||
<div className="flex w-[80vw] items-center justify-center">
|
||||
<Table>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.HeaderCell>#</Table.HeaderCell>
|
||||
<Table.HeaderCell>Customer</Table.HeaderCell>
|
||||
<Table.HeaderCell>Email</Table.HeaderCell>
|
||||
<Table.HeaderCell className="text-right">Amount</Table.HeaderCell>
|
||||
<Table.HeaderCell></Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{fakeData.map((order) => {
|
||||
return (
|
||||
<Table.Row
|
||||
key={order.id}
|
||||
className="[&_td:last-child]:w-[1%] [&_td:last-child]:whitespace-nowrap"
|
||||
>
|
||||
<Table.Cell>{order.displayId}</Table.Cell>
|
||||
<Table.Cell>{order.customer}</Table.Cell>
|
||||
<Table.Cell>{order.email}</Table.Cell>
|
||||
<Table.Cell className="text-right">
|
||||
{formatCurrency(order.amount, order.currency)}
|
||||
</Table.Cell>
|
||||
<Table.Cell className="text-ui-fg-muted">
|
||||
{order.currency}
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
)
|
||||
})}
|
||||
</Table.Body>
|
||||
</Table>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
const PaginatedDemo = () => {
|
||||
const [pageIndex, setPageIndex] = React.useState(0)
|
||||
const pageSize = 10
|
||||
|
||||
const { orders, isLoading, count } = useFakeOrders({
|
||||
offset: pageIndex * pageSize,
|
||||
limit: pageSize,
|
||||
})
|
||||
|
||||
const pageCount = Math.ceil(count / pageSize)
|
||||
|
||||
const canNextPage = pageIndex < pageCount - 1 && !isLoading
|
||||
const canPreviousPage = pageIndex > 0 && !isLoading
|
||||
|
||||
const nextPage = () => {
|
||||
if (canNextPage) {
|
||||
setPageIndex(pageIndex + 1)
|
||||
}
|
||||
}
|
||||
|
||||
const previousPage = () => {
|
||||
if (canPreviousPage) {
|
||||
setPageIndex(pageIndex - 1)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex w-[80vw] flex-col items-center justify-center">
|
||||
<Table>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.HeaderCell>#</Table.HeaderCell>
|
||||
<Table.HeaderCell>Customer</Table.HeaderCell>
|
||||
<Table.HeaderCell>Email</Table.HeaderCell>
|
||||
<Table.HeaderCell className="text-right">Amount</Table.HeaderCell>
|
||||
<Table.HeaderCell></Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{orders.map((order) => {
|
||||
return (
|
||||
<Table.Row
|
||||
key={order.id}
|
||||
className="[&_td:last-child]:w-[1%] [&_td:last-child]:whitespace-nowrap"
|
||||
>
|
||||
<Table.Cell>{order.displayId}</Table.Cell>
|
||||
<Table.Cell>{order.customer}</Table.Cell>
|
||||
<Table.Cell>{order.email}</Table.Cell>
|
||||
<Table.Cell className="text-right">{order.amount}</Table.Cell>
|
||||
<Table.Cell className="text-ui-fg-muted">
|
||||
{order.currency}
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
)
|
||||
})}
|
||||
</Table.Body>
|
||||
</Table>
|
||||
<Table.Pagination
|
||||
pageCount={pageCount}
|
||||
canNextPage={canNextPage}
|
||||
canPreviousPage={canPreviousPage}
|
||||
count={count}
|
||||
pageSize={pageSize}
|
||||
pageIndex={pageIndex}
|
||||
nextPage={nextPage}
|
||||
previousPage={previousPage}
|
||||
/>
|
||||
<div className="mt-12 flex flex-col items-center gap-y-4 font-mono text-xs">
|
||||
<div className="flex items-center gap-x-4">
|
||||
<p>Page Index: {pageIndex}</p>
|
||||
<p>Page Count: {pageCount}</p>
|
||||
<p>Count: {count}</p>
|
||||
<p>Page Size: {pageSize}</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-x-4">
|
||||
<p>Can Next Page: {canNextPage ? "true" : "false"}</p>
|
||||
<p>Can Previous Page: {canPreviousPage ? "true" : "false"}</p>
|
||||
<p>Is Loading: {isLoading ? "true" : "false"}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const Paginated: Story = {
|
||||
render: () => {
|
||||
return <PaginatedDemo />
|
||||
},
|
||||
}
|
||||
163
packages/design-system/ui/src/components/table/table.tsx
Normal file
163
packages/design-system/ui/src/components/table/table.tsx
Normal file
@@ -0,0 +1,163 @@
|
||||
import { Minus } from "@medusajs/icons"
|
||||
import * as React from "react"
|
||||
|
||||
import { Button } from "@/components/button"
|
||||
import { clx } from "@/utils/clx"
|
||||
|
||||
const Root = React.forwardRef<
|
||||
HTMLTableElement,
|
||||
React.HTMLAttributes<HTMLTableElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<table
|
||||
ref={ref}
|
||||
className={clx("text-ui-fg-subtle txt-compact-small w-full", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
Root.displayName = "Table"
|
||||
|
||||
const Row = React.forwardRef<
|
||||
HTMLTableRowElement,
|
||||
React.HTMLAttributes<HTMLTableRowElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<tr
|
||||
ref={ref}
|
||||
className={clx(
|
||||
"bg-ui-bg-base hover:bg-ui-bg-base-hover border-ui-border-base transition-fg border-b",
|
||||
"[&_td:last-child]:pr-8 [&_th:last-child]:pr-8",
|
||||
"[&_td:first-child]:pl-8 [&_th:first-child]:pl-8",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
Row.displayName = "Table.Row"
|
||||
|
||||
const Cell = React.forwardRef<
|
||||
HTMLTableCellElement,
|
||||
React.HTMLAttributes<HTMLTableCellElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<td ref={ref} className={clx("h-12 pr-3", className)} {...props} />
|
||||
))
|
||||
Cell.displayName = "Table.Cell"
|
||||
|
||||
const Header = React.forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<thead
|
||||
ref={ref}
|
||||
className={clx(
|
||||
"border-ui-border-base txt-compact-small-plus [&_tr:hover]:bg-ui-bg-base border-y",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
Header.displayName = "Table.Header"
|
||||
|
||||
const HeaderCell = React.forwardRef<
|
||||
HTMLTableCellElement,
|
||||
React.TdHTMLAttributes<HTMLTableCellElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<th ref={ref} className={clx("h-12 pr-3 text-left", className)} {...props} />
|
||||
))
|
||||
HeaderCell.displayName = "Table.HeaderCell"
|
||||
|
||||
const Body = React.forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<tbody
|
||||
ref={ref}
|
||||
className={clx("border-ui-border-base border-b", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
Body.displayName = "Table.Body"
|
||||
|
||||
interface TablePaginationProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
count: number
|
||||
pageSize: number
|
||||
pageIndex: number
|
||||
pageCount: number
|
||||
canPreviousPage: boolean
|
||||
canNextPage: boolean
|
||||
previousPage: () => void
|
||||
nextPage: () => void
|
||||
}
|
||||
|
||||
const Pagination = React.forwardRef<HTMLDivElement, TablePaginationProps>(
|
||||
(
|
||||
{
|
||||
className,
|
||||
count,
|
||||
pageSize,
|
||||
pageCount,
|
||||
pageIndex,
|
||||
canPreviousPage,
|
||||
canNextPage,
|
||||
nextPage,
|
||||
previousPage,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const { from, to } = React.useMemo(() => {
|
||||
const from = count === 0 ? count : pageIndex * pageSize + 1
|
||||
const to = Math.min(count, (pageIndex + 1) * pageSize)
|
||||
|
||||
return { from, to }
|
||||
}, [count, pageIndex, pageSize])
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={clx(
|
||||
"text-ui-fg-subtle txt-compact-small-plus flex w-full items-center justify-between px-5 pb-6 pt-4",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className="inline-flex items-center gap-x-1 px-3 py-[5px]">
|
||||
<p>{from}</p>
|
||||
<Minus className="text-ui-fg-muted" />
|
||||
<p>{`${to} of ${count} results`}</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-x-2">
|
||||
<div className="inline-flex items-center gap-x-1 px-3 py-[5px]">
|
||||
<p>
|
||||
{pageIndex + 1} of {Math.max(pageCount, 1)}
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
variant={"transparent"}
|
||||
onClick={previousPage}
|
||||
disabled={!canPreviousPage}
|
||||
>
|
||||
Prev
|
||||
</Button>
|
||||
<Button
|
||||
variant={"transparent"}
|
||||
onClick={nextPage}
|
||||
disabled={!canNextPage}
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
Pagination.displayName = "Table.Pagination"
|
||||
|
||||
const Table = Object.assign(Root, {
|
||||
Row,
|
||||
Cell,
|
||||
Header,
|
||||
HeaderCell,
|
||||
Body,
|
||||
Pagination,
|
||||
})
|
||||
|
||||
export { Table }
|
||||
Reference in New Issue
Block a user