UNPKG

vueless

Version:

Vue Styleless UI Component Library, powered by Tailwind CSS.

578 lines (532 loc) 15.3 kB
import type { Meta, StoryFn } from "@storybook/vue3"; import { getArgTypes, getSlotNames, getSlotsFragment, getDocsDescription, } from "../../utils/storybook.ts"; import { getRandomId } from "../../utils/helper.ts"; import UTable from "../UTable.vue"; import UButton from "../../ui.button/UButton.vue"; import ULink from "../../ui.button-link/ULink.vue"; import UMoney from "../../ui.text-money/UMoney.vue"; import UBadge from "../../ui.text-badge/UBadge.vue"; import URow from "../../ui.container-row/URow.vue"; import UIcon from "../../ui.image-icon/UIcon.vue"; import ULoader from "../../ui.loader/ULoader.vue"; import type { Row, UTableProps } from "../types.ts"; interface UTableArgs extends UTableProps { slotTemplate?: string; enum: "size"; numberOfRows: number; row: Row | typeof getRow | typeof getNestedRow | typeof getNestedContentRow; } const SHORT_STORY_PARAMETERS = { docs: { story: { inline: false, iframeHeight: 450, }, }, }; export default { id: "7010", title: "Data / Table", component: UTable, argTypes: { ...getArgTypes(UTable.__name), row: { table: { disable: true, }, }, numberOfRows: { description: "The number of table rows (not a component prop).", }, }, args: { columns: [ { key: "orderId", label: "Order Id", thClass: "w-2/5" }, { key: "customerName", label: "Customer Name" }, { key: "status", label: "Status" }, { key: "totalPrice", label: "Total Price" }, ], row: getRow, numberOfRows: 5, }, parameters: { docs: { ...getDocsDescription(UTable.__name), }, }, } as Meta; function getDateDividerRow(rowAmount: number) { return Array(rowAmount) .fill({}) .map((_, index) => { let rowDate = new Date().toString(); if (index > 1) { const date = new Date(); date.setFullYear(date.getFullYear()); rowDate = date.toDateString(); } return { id: getRandomId(), isChecked: false, rowDate, orderId: `ORD-${Math.floor(Math.random() * 10000)}`, customerName: ["Alice Johnson", "Michael Smith", "Emma Brown", "James Wilson"][ Math.floor(Math.random() * 4) ], status: ["Pending", "Shipped", "Delivered", "Cancelled"][Math.floor(Math.random() * 4)], totalPrice: `$${(Math.random() * 500).toFixed(2)}`, }; }); } function getRow() { return { id: getRandomId(), isChecked: false, orderId: `ORD-${Math.floor(Math.random() * 10000)}`, customerName: ["Alice Johnson", "Michael Smith", "Emma Brown", "James Wilson"][ Math.floor(Math.random() * 4) ], status: ["Pending", "Shipped", "Delivered", "Cancelled"][Math.floor(Math.random() * 4)], totalPrice: `$${(Math.random() * 500).toFixed(2)}`, }; } function getNestedRow() { return { id: getRandomId(), isChecked: false, orderId: `ORD-${Math.floor(Math.random() * 10000)}`, customerName: ["Alice Johnson", "Michael Smith", "Emma Brown", "James Wilson"][ Math.floor(Math.random() * 4) ], status: ["Processing", "Shipped", "Delivered", "Cancelled"][Math.floor(Math.random() * 4)], totalPrice: `$${(Math.random() * 500).toFixed(2)}`, row: [ { id: getRandomId(), isChecked: false, isShown: false, orderId: "Suborder-1", customerName: "", status: "", totalPrice: `$${(Math.random() * 500).toFixed(2)}`, row: { id: getRandomId(), isChecked: false, isShown: false, orderId: "Extra Services", customerName: "", status: "", totalPrice: `$${(Math.random() * 500).toFixed(2)}`, }, }, { id: getRandomId(), isChecked: false, isShown: false, orderId: "Suborder-2", customerName: "", status: "", totalPrice: `$${(Math.random() * 500).toFixed(2)}`, row: { id: getRandomId(), isChecked: false, isShown: false, orderId: "Extra Services", customerName: "", status: "", totalPrice: `$${(Math.random() * 500).toFixed(2)}`, }, }, ], }; } function getNestedContentRow(index: number) { if (index === 1) { return { id: getRandomId(), isChecked: false, orderId: `ORD-${Math.floor(Math.random() * 10000)}`, customerName: "John Doe", status: "Processing", nestedData: { isChecked: false, isShown: false, rows: [ { id: getRandomId(), category: "Gadgets", itemName: "Ergonomic Mouse", quantity: 2, }, { id: getRandomId(), category: "Gadgets", itemName: "Wireless Keyboard", quantity: 1, }, { id: getRandomId(), category: "Electronics", itemName: "USB-C Hub", quantity: 3, }, ], }, }; } else { return { id: getRandomId(), isChecked: false, orderId: `ORD-${Math.floor(Math.random() * 10000)}`, customerName: ["Alice Johnson", "Michael Smith", "Emma Brown", "James Wilson"][ Math.floor(Math.random() * 4) ], status: ["Processing", "Shipped", "Delivered", "Cancelled"][Math.floor(Math.random() * 4)], totalPrice: `$${(Math.random() * 500).toFixed(2)}`, }; } } const DefaultTemplate: StoryFn<UTableArgs> = (args: UTableArgs) => ({ components: { UTable, UButton, ULink, UMoney, UBadge, URow, UIcon, ULoader }, setup() { const slots = getSlotNames(UTable.__name); return { args, slots }; }, template: ` <UTable v-bind="args" :rows="args.rows || itemsData" > ${args.slotTemplate || getSlotsFragment("")} </UTable> `, computed: { itemsData() { const rows = []; if (typeof args.row === "function") { for (let i = 0; i < args.numberOfRows; i++) { rows.push(args.row(i)); } } else { rows.push(args.row); } return rows; }, }, }); export const Default = DefaultTemplate.bind({}); Default.args = {}; const LoadingTemplate: StoryFn<UTableArgs> = (args: UTableArgs) => ({ components: { UTable, UButton }, setup() { const slots = getSlotNames(UTable.__name); return { args, slots }; }, template: ` <UButton label="Toggle loading" @click="args.loading = !args.loading" class="mb-4" /> <UTable v-bind="args" :rows="args.rows" > ${args.slotTemplate || getSlotsFragment("")} </UTable> `, }); export const Loading = LoadingTemplate.bind({}); Loading.args = {}; Loading.parameters = { docs: { description: { story: "Set table loader state.", }, }, }; export const EmptyCellLabel = DefaultTemplate.bind({}); EmptyCellLabel.args = { emptyCellLabel: "NO DATA FOUND", rows: [ { id: getRandomId(), isChecked: false, orderId: `ORD-${Math.floor(Math.random() * 10000)}`, customerName: "", status: "", totalPrice: `$${(Math.random() * 500).toFixed(2)}`, }, ], }; EmptyCellLabel.parameters = { docs: { description: { story: "Label to display for empty cell values.", }, }, }; export const Nesting = DefaultTemplate.bind({}); Nesting.args = { row: getNestedRow }; Nesting.parameters = { docs: { description: { story: "If you need to have nested row(s) in the table, you can use the `row` key inside a row object.", }, }, }; export const CellClasses = DefaultTemplate.bind({}); CellClasses.args = { rows: [ { id: getRandomId(), isChecked: false, orderId: `ORD-${Math.floor(Math.random() * 10000)}`, customerName: "John Doe", status: { value: "Cancelled", class: "bg-red-200" }, totalPrice: "$18.92", }, { id: getRandomId(), isChecked: false, class: "bg-green-100", orderId: `ORD-${Math.floor(Math.random() * 10000)}`, customerName: "Bob Smith", status: "Delivered", totalPrice: "$173.11", }, { id: getRandomId(), isChecked: false, orderId: `ORD-${Math.floor(Math.random() * 10000)}`, customerName: "Helen Williams", status: { value: "Delivered", contentClasses: "line-through" }, totalPrice: "$314.26", }, ], }; CellClasses.parameters = { docs: { description: { story: // eslint-disable-next-line vue/max-len "To apply classes to a table content, you may use different approaches: <br/> 1. Pass a string with classes to the `class` key in a row object (classes are applied to the whole row). <br/> 2. Pass a string with classes to the `class` key in a cell object (classes are applied to the table cell, while the value is passed via a `value` key). <br/> 3. Pass a string with classes to the `contentClasses` key in a cell object (classes are applied to the cell content, while the value is passed via a `value` key).", }, }, }; export const Empty = DefaultTemplate.bind({}); Empty.args = { rows: [] }; export const Selectable = DefaultTemplate.bind({}); Selectable.args = { selectable: true }; export const StickyHeader = DefaultTemplate.bind({}); StickyHeader.parameters = SHORT_STORY_PARAMETERS; StickyHeader.args = { numberOfRows: 50, selectable: true, stickyHeader: true }; export const StickyFooter = DefaultTemplate.bind({}); StickyFooter.parameters = SHORT_STORY_PARAMETERS; StickyFooter.args = { numberOfRows: 50, selectable: true, stickyFooter: true, slotTemplate: ` <template #footer> <td colspan="4"> <p class="font-semibold text-gray-700"> 📊 Summary: 50 transactions processed | Total Revenue: <strong>$12,345.67</strong> </p> </td> </template> `, }; export const Compact = DefaultTemplate.bind({}); Compact.args = { compact: true }; Compact.parameters = { docs: { description: { story: "`compact` prop makes the table compact (fewer spacings).", }, }, }; export const DateDivider = DefaultTemplate.bind({}); DateDivider.args = { dateDivider: true, rows: getDateDividerRow(4) }; DateDivider.parameters = { docs: { description: { story: "Show date divider line between dates.", }, }, }; export const DateDividerCustomLabel = DefaultTemplate.bind({}); DateDividerCustomLabel.args = { rows: getDateDividerRow(4), dateDivider: [ { date: getDateDividerRow(4).at(2)!.rowDate, label: "Custom label for specific date", config: { label: "!text-orange-400", divider: "!border-orange-300" }, }, ], }; DateDividerCustomLabel.parameters = { docs: { description: { story: "You can customize date divider by passing necessary data in `date`, `label` and `config` object keys.", }, }, }; export const SlotHeaderKey = DefaultTemplate.bind({}); SlotHeaderKey.args = { slotTemplate: ` <template #header-status="{ column }"> <UBadge :label="column?.label" /> </template> `, }; export const SlotHeaderActions = DefaultTemplate.bind({}); SlotHeaderActions.parameters = SHORT_STORY_PARAMETERS; SlotHeaderActions.args = { numberOfRows: 50, stickyHeader: true, selectable: true, slotTemplate: ` <template #header-actions> <URow gap="2xs"> <UButton label="Edit" variant="thirdary" color="brand" size="sm" /> <UButton label="Delete" variant="thirdary" color="brand" size="sm" /> </URow> </template> `, }; export const SlotBeforeHeader = DefaultTemplate.bind({}); SlotBeforeHeader.args = { slotTemplate: ` <template #before-header="{ colsCount, classes }"> <td :colspan="colsCount" :class="classes"> <p class="p-2"> 📅 Latest data updated on {{ new Date().toLocaleDateString() }}. Please verify all entries for accuracy before proceeding. </p> </td> </template> `, }; export const SlotBeforeFirstRow = DefaultTemplate.bind({}); SlotBeforeFirstRow.args = { slotTemplate: ` <template #before-first-row> <p class="py-2"> 📌 Showing the latest data as of {{ new Date().toLocaleDateString() }}. Please ensure all entries are up to date. </p> </template> `, }; export const CellSlots = DefaultTemplate.bind({}); CellSlots.args = { slotTemplate: ` <template #cell-orderId="{ value }"> <ULink :label="value" color="green" /> </template> <template #cell-customerName="{ value }"> <UBadge :label="value" /> </template> <template #cell-totalPrice="{ value }"> <UMoney :value="value.slice(1)" symbol="€" symbolAlign="left" /> </template> `, }; export const SlotExpand = DefaultTemplate.bind({}); SlotExpand.args = { row: getNestedRow, slotTemplate: ` <template #expand="{ row, expanded }"> <UBadge v-if="expanded" label="Collapse" class="cursor-pointer" /> <UBadge v-if="!expanded" label="Expand" class="cursor-pointer" /> </template> `, }; export const SlotNestedContent = DefaultTemplate.bind({}); SlotNestedContent.args = { columns: [ { key: "orderId", label: "Order Id" }, { key: "customerName", label: "Customer Name" }, { key: "status", label: "Status" }, ], row: getNestedContentRow, slotTemplate: ` <template #nested-content="{ row }"> <div class="p-4 bg-gray-100"> <UTable :columns="[ { key: 'category', label: 'Category' }, { key: 'itemName', label: 'Product' }, { key: 'quantity', label: 'Quantity' }, ]" :rows="row.nestedData.rows" compact /> </div> </template> `, }; SlotNestedContent.parameters = { docs: { description: { story: // eslint-disable-next-line vue/max-len "You can also pass nested content inside the row (to render a nested table, for example). Use the `nestedData` key inside a row object.", }, }, }; export const SlotAfterLastRow = DefaultTemplate.bind({}); SlotAfterLastRow.args = { slotTemplate: ` <template #after-last-row="{ colsCount, classes }"> <td :colspan="colsCount" :class="classes"> <p> ✅ End of results. If you need more data, try adjusting your filters or loading more entries. </p> </td> </template> `, }; export const SlotEmptyState = DefaultTemplate.bind({}); SlotEmptyState.args = { rows: [], config: { i18n: { noData: "Fetching data..." }, bodyEmptyStateCell: "py-10", }, slotTemplate: ` <template #empty-state> <ULoader loading size="lg" :config="{ loader: 'mx-auto mb-4' }" /> <p class="text-center">Fetching latest data, please wait...</p> </template> `, }; export const SlotFooter = DefaultTemplate.bind({}); SlotFooter.args = { slotTemplate: ` <template #footer> <td colspan="100%" class="px-2"> 🔍 For more detailed insights, please visit our data analysis page or reach out to support for assistance. </td> </template> `, };