From bb8caa918d77c9678b143e5c1876ac60a862d128 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Jun 2025 20:20:13 +0000 Subject: [PATCH 1/4] Initial plan for issue From 7b91ca1132108def6b5d491c6fa1c175867f5b8a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Jun 2025 20:33:18 +0000 Subject: [PATCH 2/4] Add Badge, Table, Progress, Accordion, and Tabs components with comprehensive stories Co-authored-by: wtfzdotnet <639376+wtfzdotnet@users.noreply.github.com> --- package-lock.json | 166 ++++--- package.json | 3 + .../ui/accordion/Accordion.stories.tsx | 459 ++++++++++++++++++ src/components/ui/accordion/accordion.tsx | 54 +++ src/components/ui/accordion/index.ts | 6 + src/components/ui/badge/Badge.stories.tsx | 307 ++++++++++++ src/components/ui/badge/badge.tsx | 23 +- src/components/ui/badge/index.ts | 2 +- .../ui/progress/Progress.stories.tsx | 348 +++++++++++++ src/components/ui/progress/index.ts | 1 + src/components/ui/progress/progress.tsx | 103 ++++ src/components/ui/table/Table.stories.tsx | 400 +++++++++++++++ src/components/ui/table/index.ts | 10 + src/components/ui/table/table.tsx | 119 +++++ src/components/ui/tabs/index.ts | 1 + src/components/ui/tabs/tabs.tsx | 52 ++ tailwind.config.ts | 14 + 17 files changed, 2009 insertions(+), 59 deletions(-) create mode 100644 src/components/ui/accordion/Accordion.stories.tsx create mode 100644 src/components/ui/accordion/accordion.tsx create mode 100644 src/components/ui/accordion/index.ts create mode 100644 src/components/ui/badge/Badge.stories.tsx create mode 100644 src/components/ui/progress/Progress.stories.tsx create mode 100644 src/components/ui/progress/index.ts create mode 100644 src/components/ui/progress/progress.tsx create mode 100644 src/components/ui/table/Table.stories.tsx create mode 100644 src/components/ui/table/index.ts create mode 100644 src/components/ui/table/table.tsx create mode 100644 src/components/ui/tabs/index.ts create mode 100644 src/components/ui/tabs/tabs.tsx diff --git a/package-lock.json b/package-lock.json index 24c70be..4820217 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "frontend-recipeer", "version": "1.0.0", "dependencies": { + "@radix-ui/react-accordion": "^1.2.11", "@radix-ui/react-alert-dialog": "^1.1.14", "@radix-ui/react-avatar": "^1.1.10", "@radix-ui/react-checkbox": "^1.3.2", @@ -16,12 +17,14 @@ "@radix-ui/react-label": "^2.1.7", "@radix-ui/react-navigation-menu": "^1.2.13", "@radix-ui/react-popover": "^1.1.14", + "@radix-ui/react-progress": "^1.1.7", "@radix-ui/react-radio-group": "^1.3.7", "@radix-ui/react-select": "^2.2.5", "@radix-ui/react-separator": "^1.1.7", "@radix-ui/react-slider": "^1.3.5", "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-switch": "^1.2.5", + "@radix-ui/react-tabs": "^1.1.12", "@radix-ui/react-toast": "^1.2.14", "@radix-ui/react-tooltip": "^1.2.7", "@shadcn/ui": "^0.0.4", @@ -724,8 +727,6 @@ }, "node_modules/@floating-ui/core": { "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.1.tgz", - "integrity": "sha512-azI0DrjMMfIug/ExbBaeDVJXcY0a7EPvPjb2xAJPa4HeimBX+Z18HK8QQR3jb6356SnDDdxx+hinMLcJEDdOjw==", "license": "MIT", "dependencies": { "@floating-ui/utils": "^0.2.9" @@ -733,8 +734,6 @@ }, "node_modules/@floating-ui/dom": { "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.1.tgz", - "integrity": "sha512-cwsmW/zyw5ltYTUeeYJ60CnQuPqmGwuGVhG9w0PRaRKkAyi38BT5CKrpIbb+jtahSwUl04cWzSx9ZOIxeS6RsQ==", "license": "MIT", "dependencies": { "@floating-ui/core": "^1.7.1", @@ -743,8 +742,6 @@ }, "node_modules/@floating-ui/react-dom": { "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.3.tgz", - "integrity": "sha512-huMBfiU9UnQ2oBwIhgzyIiSpVgvlDstU8CX0AF+wS+KzmYMs0J2a3GwuFHV1Lz+jlrQGeC1fF+Nv0QoumyV0bA==", "license": "MIT", "dependencies": { "@floating-ui/dom": "^1.0.0" @@ -756,8 +753,6 @@ }, "node_modules/@floating-ui/utils": { "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz", - "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==", "license": "MIT" }, "node_modules/@humanfs/core": { @@ -1132,14 +1127,41 @@ }, "node_modules/@radix-ui/number": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", - "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==", "license": "MIT" }, "node_modules/@radix-ui/primitive": { "version": "1.1.2", "license": "MIT" }, + "node_modules/@radix-ui/react-accordion": { + "version": "1.2.11", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-collapsible": "1.1.11", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-alert-dialog": { "version": "1.1.14", "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.14.tgz", @@ -1170,8 +1192,6 @@ }, "node_modules/@radix-ui/react-arrow": { "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", - "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", "license": "MIT", "dependencies": { "@radix-ui/react-primitive": "2.1.3" @@ -1244,6 +1264,34 @@ } } }, + "node_modules/@radix-ui/react-collapsible": { + "version": "1.1.11", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.4", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-collection": { "version": "1.1.7", "license": "MIT", @@ -1370,8 +1418,6 @@ }, "node_modules/@radix-ui/react-focus-guards": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.2.tgz", - "integrity": "sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -1385,8 +1431,6 @@ }, "node_modules/@radix-ui/react-focus-scope": { "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", - "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", @@ -1457,8 +1501,6 @@ }, "node_modules/@radix-ui/react-label": { "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.7.tgz", - "integrity": "sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==", "license": "MIT", "dependencies": { "@radix-ui/react-primitive": "2.1.3" @@ -1551,8 +1593,6 @@ }, "node_modules/@radix-ui/react-popper": { "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.7.tgz", - "integrity": "sha512-IUFAccz1JyKcf/RjB552PlWwxjeCJB8/4KxT7EhBHOJM+mN7LdW+B3kacJXILm32xawcMMjb2i0cIZpo+f9kiQ==", "license": "MIT", "dependencies": { "@floating-ui/react-dom": "^2.0.0", @@ -1583,8 +1623,6 @@ }, "node_modules/@radix-ui/react-portal": { "version": "1.1.9", - "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", - "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", "license": "MIT", "dependencies": { "@radix-ui/react-primitive": "2.1.3", @@ -1648,10 +1686,30 @@ } } }, + "node_modules/@radix-ui/react-progress": { + "version": "1.1.7", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-radio-group": { "version": "1.3.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.3.7.tgz", - "integrity": "sha512-9w5XhD0KPOrm92OTTE0SysH3sYzHsSTHNvZgUBo/VZ80VdYyB5RneDbc0dKpURS24IxkoFRu/hI0i4XyfFwY6g==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.2", @@ -1682,8 +1740,6 @@ }, "node_modules/@radix-ui/react-roving-focus": { "version": "1.1.10", - "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.10.tgz", - "integrity": "sha512-dT9aOXUen9JSsxnMPv/0VqySQf5eDQ6LCk5Sw28kamz8wSOW2bJdlX2Bg5VUIIcV+6XlHpWTIuTPCf/UNIyq8Q==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.2", @@ -1713,8 +1769,6 @@ }, "node_modules/@radix-ui/react-select": { "version": "2.2.5", - "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.5.tgz", - "integrity": "sha512-HnMTdXEVuuyzx63ME0ut4+sEMYW6oouHWNGUZc7ddvUWIcfCva/AMoqEW/3wnEllriMWBa0RHspCYnfCWJQYmA==", "license": "MIT", "dependencies": { "@radix-ui/number": "1.1.1", @@ -1777,8 +1831,6 @@ }, "node_modules/@radix-ui/react-slider": { "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.3.5.tgz", - "integrity": "sha512-rkfe2pU2NBAYfGaxa3Mqosi7VZEWX5CxKaanRv0vZd4Zhl9fvQrg0VM93dv3xGLGfrHuoTRF3JXH8nb9g+B3fw==", "license": "MIT", "dependencies": { "@radix-ui/number": "1.1.1", @@ -1826,8 +1878,6 @@ }, "node_modules/@radix-ui/react-switch": { "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.5.tgz", - "integrity": "sha512-5ijLkak6ZMylXsaImpZ8u4Rlf5grRmoc0p0QeX9VJtlrM4f5m3nCTX8tWga/zOA8PZYIR/t0p2Mnvd7InrJ6yQ==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.2", @@ -1853,6 +1903,34 @@ } } }, + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.12", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.4", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.10", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-toast": { "version": "1.2.14", "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.14.tgz", @@ -2027,8 +2105,6 @@ }, "node_modules/@radix-ui/react-use-rect": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", - "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==", "license": "MIT", "dependencies": { "@radix-ui/rect": "1.1.1" @@ -2082,8 +2158,6 @@ }, "node_modules/@radix-ui/rect": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", - "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", "license": "MIT" }, "node_modules/@rolldown/pluginutils": { @@ -3653,8 +3727,6 @@ }, "node_modules/aria-hidden": { "version": "1.2.6", - "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", - "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", "license": "MIT", "dependencies": { "tslib": "^2.0.0" @@ -4647,8 +4719,6 @@ }, "node_modules/detect-node-es": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", - "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", "license": "MIT" }, "node_modules/didyoumean": { @@ -5004,8 +5074,6 @@ }, "node_modules/eslint-plugin-storybook": { "version": "9.0.9", - "resolved": "https://registry.npmjs.org/eslint-plugin-storybook/-/eslint-plugin-storybook-9.0.9.tgz", - "integrity": "sha512-IPxl6OfZPQq9R/aAac5F/gKdu/wW83z1HYkEdoLf0MDUgFDt+QMmMUi1jmW7oVt7c8tMaYKCcAVCZ+EC7ZXBug==", "dev": true, "license": "MIT", "dependencies": { @@ -5560,8 +5628,6 @@ }, "node_modules/get-nonce": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", - "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", "license": "MIT", "engines": { "node": ">=6" @@ -6684,8 +6750,6 @@ }, "node_modules/next-themes": { "version": "0.4.6", - "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz", - "integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==", "license": "MIT", "peerDependencies": { "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", @@ -10290,8 +10354,6 @@ }, "node_modules/react-remove-scroll": { "version": "2.7.1", - "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.1.tgz", - "integrity": "sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==", "license": "MIT", "dependencies": { "react-remove-scroll-bar": "^2.3.7", @@ -10315,8 +10377,6 @@ }, "node_modules/react-remove-scroll-bar": { "version": "2.3.8", - "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", - "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", "license": "MIT", "dependencies": { "react-style-singleton": "^2.2.2", @@ -10337,8 +10397,6 @@ }, "node_modules/react-style-singleton": { "version": "2.2.3", - "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", - "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", "license": "MIT", "dependencies": { "get-nonce": "^1.0.0", @@ -11986,8 +12044,6 @@ }, "node_modules/use-callback-ref": { "version": "1.3.3", - "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", - "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", "license": "MIT", "dependencies": { "tslib": "^2.0.0" @@ -12007,8 +12063,6 @@ }, "node_modules/use-sidecar": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", - "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", "license": "MIT", "dependencies": { "detect-node-es": "^1.1.0", diff --git a/package.json b/package.json index 2409bd0..c02a41e 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "semantic-release": "semantic-release" }, "dependencies": { + "@radix-ui/react-accordion": "^1.2.11", "@radix-ui/react-alert-dialog": "^1.1.14", "@radix-ui/react-avatar": "^1.1.10", "@radix-ui/react-checkbox": "^1.3.2", @@ -24,12 +25,14 @@ "@radix-ui/react-label": "^2.1.7", "@radix-ui/react-navigation-menu": "^1.2.13", "@radix-ui/react-popover": "^1.1.14", + "@radix-ui/react-progress": "^1.1.7", "@radix-ui/react-radio-group": "^1.3.7", "@radix-ui/react-select": "^2.2.5", "@radix-ui/react-separator": "^1.1.7", "@radix-ui/react-slider": "^1.3.5", "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-switch": "^1.2.5", + "@radix-ui/react-tabs": "^1.1.12", "@radix-ui/react-toast": "^1.2.14", "@radix-ui/react-tooltip": "^1.2.7", "@shadcn/ui": "^0.0.4", diff --git a/src/components/ui/accordion/Accordion.stories.tsx b/src/components/ui/accordion/Accordion.stories.tsx new file mode 100644 index 0000000..09e0245 --- /dev/null +++ b/src/components/ui/accordion/Accordion.stories.tsx @@ -0,0 +1,459 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger +} from './accordion'; +import { Badge } from '../badge'; +import { Progress } from '../progress'; +import { ChefHat, Clock, Users, AlertTriangle, Utensils, Info } from 'lucide-react'; + +const meta: Meta = { + title: 'Design System/Components/Data Display/Accordion', + component: Accordion, + parameters: { + layout: 'centered', + docs: { + description: { + component: ` +# Accordion Component + +A collapsible content component for organizing recipe instructions, FAQ sections, and expandable recipe information in a mobile-optimized interface. + +## Features + +- **Single/Multiple**: Support for single or multiple open items +- **Smooth Animations**: CSS-based expand/collapse animations +- **Keyboard Navigation**: Full keyboard accessibility support +- **Mobile Optimized**: Collapsible design perfect for mobile screens +- **Nested Content**: Support for complex content including other components + +## Use Cases + +- **Recipe Instructions**: "Ingredients", "Instructions", "Chef's Notes" +- **FAQ Sections**: Common cooking questions and troubleshooting +- **Recipe Details**: Expandable sections for nutritional info, variations +- **Mobile Navigation**: Collapsible content for small screens +- **Progressive Disclosure**: Show/hide detailed information on demand + ` + } + } + }, + tags: ['autodocs'], + argTypes: { + type: { + control: 'select', + options: ['single', 'multiple'], + description: 'Whether multiple items can be open at once' + }, + collapsible: { + control: 'boolean', + description: 'Whether all items can be collapsed' + } + } +}; + +export default meta; +type Story = StoryObj; + +// Default accordion +export const Default: Story = { + render: () => ( +
+ + + Ingredients + +
    +
  • 400g pasta (spaghetti or linguine)
  • +
  • 200g guanciale or pancetta, diced
  • +
  • 4 large eggs
  • +
  • 100g Pecorino Romano cheese, grated
  • +
  • Freshly ground black pepper
  • +
  • Salt for pasta water
  • +
+
+
+ + Instructions + +
    +
  1. Bring a large pot of salted water to boil
  2. +
  3. Cook pasta according to package directions until al dente
  4. +
  5. While pasta cooks, render the guanciale in a large pan
  6. +
  7. Whisk eggs with cheese and pepper in a bowl
  8. +
  9. Drain pasta, reserving 1 cup pasta water
  10. +
  11. Add hot pasta to pan with guanciale
  12. +
  13. Remove from heat, add egg mixture, toss quickly
  14. +
  15. Add pasta water as needed for creaminess
  16. +
+
+
+ + Chef's Notes + +
+

Tip: The key is timing - add the egg mixture off the heat to prevent scrambling.

+

Variation: Use bacon if guanciale isn't available, though it won't be traditional.

+

Storage: Best served immediately, doesn't reheat well.

+
+
+
+
+
+ ), +}; + +// Recipe details accordion +export const RecipeDetails: Story = { + render: () => ( +
+ + + +
+ + Recipe Overview +
+
+ +
+
+ +

Total Time

+

35 minutes

+
+
+ +

Servings

+

4 people

+
+
+ +

Difficulty

+ Medium +
+
+
+

Rating

+

4.8/5

+
+
+
+
+ + + +
+ Ingredients + 6 items +
+
+ +
+ {[ + { name: 'Pasta (spaghetti)', amount: '400g', note: 'al dente' }, + { name: 'Guanciale', amount: '200g', note: 'diced' }, + { name: 'Large eggs', amount: '4', note: 'room temperature' }, + { name: 'Pecorino Romano', amount: '100g', note: 'freshly grated' }, + { name: 'Black pepper', amount: 'to taste', note: 'freshly ground' }, + { name: 'Salt', amount: 'for water', note: 'coarse sea salt' } + ].map((ingredient, i) => ( +
+
+

{ingredient.name}

+

{ingredient.note}

+
+ {ingredient.amount} +
+ ))} +
+
+
+ + + Step-by-Step Instructions + +
+ {[ + { step: 1, title: 'Prepare the water', desc: 'Bring a large pot of salted water to a rolling boil.', time: '5 min' }, + { step: 2, title: 'Cook the pasta', desc: 'Add pasta and cook according to package directions until al dente.', time: '8-12 min' }, + { step: 3, title: 'Render the guanciale', desc: 'Cook diced guanciale in a large pan until crispy and golden.', time: '5-7 min' }, + { step: 4, title: 'Prepare egg mixture', desc: 'Whisk eggs with grated cheese and plenty of black pepper.', time: '2 min' }, + { step: 5, title: 'Combine and finish', desc: 'Toss hot pasta with guanciale, then add egg mixture off heat.', time: '2-3 min' } + ].map((instruction) => ( +
+
+ {instruction.step} +
+
+
+

{instruction.title}

+ {instruction.time} +
+

{instruction.desc}

+
+
+ ))} +
+
+
+ + + Nutritional Information + +
+
+

425

+

Calories

+
+
+

18g

+

Fat

+
+
+

42g

+

Carbs

+
+
+

18g

+

Protein

+
+
+
+
+ + + +
+ + Chef's Tips & Variations +
+
+ +
+
+
+ +
+

Pro Tip

+

+ Remove the pan from heat before adding the egg mixture to prevent scrambling. +

+
+
+
+ +
+

Variations

+
    +
  • • Add frozen peas for color and sweetness
  • +
  • • Use bacon if guanciale isn't available
  • +
  • • Try half Pecorino, half Parmesan for milder flavor
  • +
+
+ +
+

Storage

+

+ Best served immediately. Leftovers can be stored for 1 day but don't reheat well. +

+
+
+
+
+
+
+ ), + parameters: { + layout: 'padded', + docs: { + description: { + story: 'Comprehensive recipe details accordion with multiple sections including overview, ingredients, instructions, nutrition, and tips.' + } + } + } +}; + +// FAQ accordion +export const FAQ: Story = { + render: () => ( +
+

Frequently Asked Questions

+ + + Can I substitute ingredients? + +
+

Guanciale: You can use pancetta or bacon, though the flavor will be different.

+

Pecorino Romano: Parmigiano-Reggiano works as a substitute.

+

Pasta: Any long pasta like linguine, fettuccine, or bucatini works well.

+
+
+
+ + + Can this be made vegan? + +
+

Traditional carbonara cannot be made vegan while maintaining its authentic character, as eggs and cheese are essential components.

+

However, you could create a "carbonara-style" dish using:

+
    +
  • Cashew cream instead of eggs
  • +
  • Nutritional yeast for cheesy flavor
  • +
  • Smoky tempeh or mushrooms instead of guanciale
  • +
+
+
+
+ + + My eggs scrambled! What went wrong? + +
+

This is the most common carbonara mistake. Here's how to prevent it:

+
    +
  • Remove from heat: Take the pan off the burner before adding eggs
  • +
  • Toss quickly: Move the pasta constantly while adding the mixture
  • +
  • Use pasta water: Add hot pasta water to help create a smooth sauce
  • +
  • Temperature matters: Let the pasta cool for 30 seconds before adding eggs
  • +
+
+
+
+ + + How do I store leftovers? + +
+

Carbonara is best enjoyed fresh, but if you have leftovers:

+
    +
  • Refrigerate: Store in fridge for up to 1 day
  • +
  • Don't microwave: The eggs will curdle
  • +
  • Gentle reheating: Use low heat in a pan with a splash of cream
  • +
  • Consider repurposing: Use as a base for frittata or pasta salad
  • +
+
+
+
+ + + What wine pairs well with carbonara? + +
+

Carbonara pairs beautifully with several wine styles:

+
+
+

White Wines

+
    +
  • Frascati (traditional Roman choice)
  • +
  • Soave
  • +
  • Vermentino
  • +
  • Pinot Grigio
  • +
+
+
+

Light Reds

+
    +
  • Chianti
  • +
  • Sangiovese
  • +
  • Barbera d'Alba
  • +
+
+
+
+
+
+
+
+ ), + parameters: { + layout: 'padded', + docs: { + description: { + story: 'FAQ accordion showing common cooking questions with detailed answers and troubleshooting tips.' + } + } + } +}; + +// Mobile optimized accordion +export const MobileOptimized: Story = { + render: () => ( +
+ + + Quick Recipe Facts + +
+
+ Prep Time + 10 min +
+
+ Cook Time + 25 min +
+
+ Difficulty + Medium +
+
+ Servings + 4 people +
+
+
+
+ + + Ingredients (6) + +
+ {[ + '400g pasta', + '200g guanciale', + '4 large eggs', + '100g Pecorino Romano', + 'Black pepper', + 'Salt for water' + ].map((ingredient, i) => ( +
+ + {ingredient} +
+ ))} +
+
+
+ + + Cooking Progress + +
+ +

+ Currently: Rendering the guanciale until crispy +

+
+

✓ Water boiling

+

✓ Pasta cooking

+

→ Rendering guanciale

+

• Prepare egg mixture

+

• Combine and finish

+
+
+
+
+
+
+ ), + parameters: { + docs: { + description: { + story: 'Mobile-optimized accordion with compact layout and touch-friendly interactions.' + } + } + } +}; \ No newline at end of file diff --git a/src/components/ui/accordion/accordion.tsx b/src/components/ui/accordion/accordion.tsx new file mode 100644 index 0000000..8f849de --- /dev/null +++ b/src/components/ui/accordion/accordion.tsx @@ -0,0 +1,54 @@ +import * as React from "react" +import * as AccordionPrimitive from "@radix-ui/react-accordion" +import { ChevronDownIcon } from "lucide-react" +import { cn } from "@/lib/utils" + +const Accordion = AccordionPrimitive.Root + +const AccordionItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AccordionItem.displayName = "AccordionItem" + +const AccordionTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + svg]:rotate-180", + className + )} + {...props} + > + {children} + + + +)) +AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName + +const AccordionContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + +
{children}
+
+)) +AccordionContent.displayName = AccordionPrimitive.Content.displayName + +export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } \ No newline at end of file diff --git a/src/components/ui/accordion/index.ts b/src/components/ui/accordion/index.ts new file mode 100644 index 0000000..b956782 --- /dev/null +++ b/src/components/ui/accordion/index.ts @@ -0,0 +1,6 @@ +export { + Accordion, + AccordionItem, + AccordionTrigger, + AccordionContent, +} from './accordion' \ No newline at end of file diff --git a/src/components/ui/badge/Badge.stories.tsx b/src/components/ui/badge/Badge.stories.tsx new file mode 100644 index 0000000..5ffd6d1 --- /dev/null +++ b/src/components/ui/badge/Badge.stories.tsx @@ -0,0 +1,307 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Badge } from './badge'; +import { Star, Utensils, Clock, Users, AlertTriangle, CheckCircle, Leaf, Flame } from 'lucide-react'; + +const meta: Meta = { + title: 'Design System/Components/Data Display/Badge', + component: Badge, + parameters: { + layout: 'centered', + docs: { + description: { + component: ` +# Badge Component + +A versatile badge component for displaying recipe tags, dietary indicators, difficulty levels, and other categorization elements in the recipe platform. + +## Features + +- **Recipe-Specific Variants**: dietary, difficulty, cuisine, rating, warning, success +- **Multiple Sizes**: small, default, large +- **Icon Support**: Works seamlessly with Lucide React icons +- **Accessibility**: Proper focus states and semantic structure +- **Dark Mode**: Full dark mode support + +## Use Cases + +- **Dietary Tags**: "Vegetarian", "Gluten-Free", "Keto", "Vegan" +- **Difficulty Levels**: "Easy", "Medium", "Hard" +- **Cuisine Types**: "Italian", "Mexican", "Asian", "Mediterranean" +- **Rating Indicators**: "5-Star", "Editor's Choice", "Popular" +- **Status Indicators**: "New", "Featured", "Trending" +- **Warning Labels**: "Contains Nuts", "Spicy", "Hot Surface" + ` + } + } + }, + tags: ['autodocs'], + argTypes: { + variant: { + control: 'select', + options: ['default', 'secondary', 'destructive', 'outline', 'dietary', 'difficulty', 'cuisine', 'rating', 'warning', 'success'], + description: 'Visual variant of the badge' + }, + size: { + control: 'select', + options: ['sm', 'default', 'lg'], + description: 'Size of the badge' + }, + children: { + control: 'text', + description: 'Content to display in the badge' + } + } +}; + +export default meta; +type Story = StoryObj; + +// Default badge +export const Default: Story = { + args: { + children: "Default Badge", + }, +}; + +// All variants +export const Variants: Story = { + render: () => ( +
+ Default + Secondary + Destructive + Outline + Dietary + Difficulty + Cuisine + Rating + Warning + Success +
+ ), + parameters: { + docs: { + description: { + story: 'All available badge variants with their distinct styling.' + } + } + } +}; + +// Different sizes +export const Sizes: Story = { + render: () => ( +
+ Small + Default + Large +
+ ), + parameters: { + docs: { + description: { + story: 'Badge component in different sizes.' + } + } + } +}; + +// Recipe-specific examples +export const RecipeExamples: Story = { + render: () => ( +
+
+

Dietary Restrictions

+
+ + + Vegetarian + + Gluten-Free + Keto + Dairy-Free +
+
+ +
+

Difficulty Levels

+
+ + + Easy + + Medium + + + Hard + +
+
+ +
+

Cuisine Types

+
+ + + Italian + + Mexican + Asian + Mediterranean +
+
+ +
+

Recipe Metadata

+
+ + + 5-Star + + + + Quick & Easy + + + + Serves 4 + +
+
+ +
+

Safety Warnings

+
+ + + Contains Nuts + + Spicy + Hot Surface +
+
+
+ ), + parameters: { + layout: 'padded', + docs: { + description: { + story: 'Real-world examples of badges used in recipe contexts with appropriate icons and variants.' + } + } + } +}; + +// Recipe card integration +export const RecipeCardIntegration: Story = { + render: () => ( +
+
+ 🍝 +
+
+
+

Pasta Carbonara

+ + + 4.8 + +
+

+ Classic Italian pasta dish with eggs, cheese, and pancetta +

+
+ ⏱️ 20min + 👥 4 servings +
+
+ Italian + Medium + Comfort Food + Contains Eggs +
+
+
+ ), + parameters: { + docs: { + description: { + story: 'Example integration of badges within recipe cards showing how they enhance recipe categorization and metadata display.' + } + } + } +}; + +// Cultural formatting example +export const CulturalFormatting: Story = { + render: () => ( +
+
+

Time Formats

+
+ 20 min + 1h 30m + 45 minutes +
+
+ +
+

Serving Sizes

+
+ Serves 4 + 4 portions + 4 servings +
+
+ +
+

Regional Cuisines

+
+ 中式 (Chinese) + Français + Español + Italiano +
+
+
+ ), + parameters: { + docs: { + description: { + story: 'Examples showing cultural data formatting for international recipe platform usage.' + } + } + } +}; + +// Responsive design +export const ResponsiveDesign: Story = { + render: () => ( +
+ {Array.from({ length: 6 }, (_, i) => ( +
+
+

Recipe {i + 1}

+ + + {(4.2 + i * 0.2).toFixed(1)} + +
+
+ Italian + Vegetarian + Easy +
+
+ ))} +
+ ), + parameters: { + layout: 'fullscreen', + docs: { + description: { + story: 'Responsive grid layout showing how badges adapt to different screen sizes and content arrangements.' + } + } + } +}; \ No newline at end of file diff --git a/src/components/ui/badge/badge.tsx b/src/components/ui/badge/badge.tsx index 12daad7..1965d5d 100644 --- a/src/components/ui/badge/badge.tsx +++ b/src/components/ui/badge/badge.tsx @@ -15,10 +15,29 @@ const badgeVariants = cva( destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", outline: "text-foreground", + // Recipe-specific variants + dietary: + "border-green-200 bg-green-50 text-green-700 dark:border-green-800 dark:bg-green-950 dark:text-green-300", + difficulty: + "border-amber-200 bg-amber-50 text-amber-700 dark:border-amber-800 dark:bg-amber-950 dark:text-amber-300", + cuisine: + "border-blue-200 bg-blue-50 text-blue-700 dark:border-blue-800 dark:bg-blue-950 dark:text-blue-300", + rating: + "border-yellow-200 bg-yellow-50 text-yellow-700 dark:border-yellow-800 dark:bg-yellow-950 dark:text-yellow-300", + warning: + "border-orange-200 bg-orange-50 text-orange-700 dark:border-orange-800 dark:bg-orange-950 dark:text-orange-300", + success: + "border-emerald-200 bg-emerald-50 text-emerald-700 dark:border-emerald-800 dark:bg-emerald-950 dark:text-emerald-300", + }, + size: { + default: "px-2.5 py-0.5 text-xs", + sm: "px-2 py-0.5 text-xs", + lg: "px-3 py-1 text-sm", }, }, defaultVariants: { variant: "default", + size: "default", }, } ) @@ -27,9 +46,9 @@ export interface BadgeProps extends React.HTMLAttributes, VariantProps {} -function Badge({ className, variant, ...props }: BadgeProps) { +function Badge({ className, variant, size, ...props }: BadgeProps) { return ( -
+
) } diff --git a/src/components/ui/badge/index.ts b/src/components/ui/badge/index.ts index 594e7e3..2a75b43 100644 --- a/src/components/ui/badge/index.ts +++ b/src/components/ui/badge/index.ts @@ -1 +1 @@ -export { Badge, badgeVariants, type BadgeProps } from './badge' \ No newline at end of file +export { Badge, type BadgeProps, badgeVariants } from './badge' diff --git a/src/components/ui/progress/Progress.stories.tsx b/src/components/ui/progress/Progress.stories.tsx new file mode 100644 index 0000000..6b15ae7 --- /dev/null +++ b/src/components/ui/progress/Progress.stories.tsx @@ -0,0 +1,348 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Progress } from './progress'; +import { Clock, ChefHat, CheckCircle } from 'lucide-react'; + +const meta: Meta = { + title: 'Design System/Components/Data Display/Progress', + component: Progress, + parameters: { + layout: 'centered', + docs: { + description: { + component: ` +# Progress Component + +A versatile progress indicator component for tracking cooking workflows, meal preparation completion, and recipe progress in the recipe platform. + +## Features + +- **Recipe-Specific Variants**: cooking, prep, success, warning +- **Multiple Sizes**: small, default, large, extra-large +- **Label Support**: Optional labels and percentage display +- **Accessibility**: Proper ARIA attributes and screen reader support +- **Smooth Animations**: CSS transitions for progress changes + +## Use Cases + +- **Cooking Progress**: "Step 3 of 8", "Prep: 75% complete" +- **Recipe Completion**: Track user progress through recipe steps +- **Meal Prep**: Show batch cooking or weekly meal prep progress +- **Loading States**: Recipe data loading, image uploads +- **Skill Development**: Track cooking skill progression + ` + } + } + }, + tags: ['autodocs'], + argTypes: { + value: { + control: { type: 'range', min: 0, max: 100, step: 5 }, + description: 'Progress value between 0 and 100' + }, + variant: { + control: 'select', + options: ['default', 'cooking', 'prep', 'success', 'warning'], + description: 'Visual variant of the progress bar' + }, + size: { + control: 'select', + options: ['sm', 'default', 'lg', 'xl'], + description: 'Size of the progress bar' + }, + showLabel: { + control: 'boolean', + description: 'Show progress label' + }, + showPercentage: { + control: 'boolean', + description: 'Show percentage value' + }, + label: { + control: 'text', + description: 'Custom label text' + } + } +}; + +export default meta; +type Story = StoryObj; + +// Default progress bar +export const Default: Story = { + args: { + value: 60, + showLabel: true, + showPercentage: true, + }, +}; + +// All variants +export const Variants: Story = { + render: () => ( +
+
+

Default

+ +
+
+

Cooking

+ +
+
+

Prep

+ +
+
+

Success

+ +
+
+

Warning

+ +
+
+ ), + parameters: { + docs: { + description: { + story: 'All available progress variants with their distinct styling and use cases.' + } + } + } +}; + +// Different sizes +export const Sizes: Story = { + render: () => ( +
+
+

Small

+ +
+
+

Default

+ +
+
+

Large

+ +
+
+

Extra Large

+ +
+
+ ), + parameters: { + docs: { + description: { + story: 'Progress component in different sizes for various use cases.' + } + } + } +}; + +// Recipe cooking workflow +export const CookingWorkflow: Story = { + render: () => ( +
+
+
+ +

Pasta Carbonara

+
+ +
+
+
+ + Prep Work +
+ +

All ingredients ready

+
+ +
+
+ + Cooking Steps +
+ +

Adding pasta to pan

+
+ +
+
+ + Overall Progress +
+ +

Almost done!

+
+
+
+
+ ), + parameters: { + docs: { + description: { + story: 'Complete cooking workflow example showing prep, cooking steps, and overall progress.' + } + } + } +}; + +// Meal prep tracking +export const MealPrepTracking: Story = { + render: () => ( +
+
+

Weekly Meal Prep

+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ +
+
+
+ ), + parameters: { + docs: { + description: { + story: 'Meal prep tracking example showing daily progress and weekly overview.' + } + } + } +}; + +// Skill development tracking +export const SkillDevelopment: Story = { + render: () => ( +
+
+

Cooking Skills Progress

+ +
+
+ +

9/10 techniques mastered

+
+
+ +

6/8 recipes completed

+
+
+ +

5/11 sauces learned

+
+
+ +

2/10 recipes attempted

+
+
+ +

Just getting started

+
+
+
+
+ ), + parameters: { + docs: { + description: { + story: 'Skill development tracking showing user progress across different cooking categories.' + } + } + } +}; + +// Loading states +export const LoadingStates: Story = { + render: () => ( +
+
+

Recipe Loading

+ +
+
+

Image Upload

+ +
+
+

Processing

+ +
+
+ ), + parameters: { + docs: { + description: { + story: 'Progress bars used for loading states and data processing.' + } + } + } +}; + +// Interactive demo +export const InteractiveDemo: Story = { + render: () => { + const [progress, setProgress] = React.useState(0) + + React.useEffect(() => { + const timer = setTimeout(() => { + setProgress((prev) => { + if (prev >= 100) return 0 + return prev + 10 + }) + }, 500) + + return () => clearTimeout(timer) + }, [progress]) + + return ( +
+ +

+ Watch the cooking progress animation +

+
+ ) + }, + parameters: { + docs: { + description: { + story: 'Interactive demo showing progress animation and real-time updates.' + } + } + } +}; \ No newline at end of file diff --git a/src/components/ui/progress/index.ts b/src/components/ui/progress/index.ts new file mode 100644 index 0000000..e5ea435 --- /dev/null +++ b/src/components/ui/progress/index.ts @@ -0,0 +1 @@ +export { Progress, type ProgressProps } from './progress' \ No newline at end of file diff --git a/src/components/ui/progress/progress.tsx b/src/components/ui/progress/progress.tsx new file mode 100644 index 0000000..60e9c6f --- /dev/null +++ b/src/components/ui/progress/progress.tsx @@ -0,0 +1,103 @@ +import * as React from "react" +import * as ProgressPrimitive from "@radix-ui/react-progress" +import { cva, type VariantProps } from "class-variance-authority" +import { cn } from "@/lib/utils" + +const progressVariants = cva( + "relative h-2 w-full overflow-hidden rounded-full bg-primary/20", + { + variants: { + variant: { + default: "bg-primary/20", + cooking: "bg-orange-100 dark:bg-orange-950", + prep: "bg-blue-100 dark:bg-blue-950", + success: "bg-green-100 dark:bg-green-950", + warning: "bg-yellow-100 dark:bg-yellow-950", + }, + size: { + default: "h-2", + sm: "h-1.5", + lg: "h-3", + xl: "h-4", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) + +const progressIndicatorVariants = cva( + "h-full w-full flex-1 transition-all", + { + variants: { + variant: { + default: "bg-primary", + cooking: "bg-orange-500", + prep: "bg-blue-500", + success: "bg-green-500", + warning: "bg-yellow-500", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +export interface ProgressProps + extends React.ComponentPropsWithoutRef, + VariantProps { + /** Progress value between 0 and 100 */ + value?: number + /** Maximum value (defaults to 100) */ + max?: number + /** Show progress label */ + showLabel?: boolean + /** Custom label text */ + label?: string + /** Show percentage */ + showPercentage?: boolean +} + +const Progress = React.forwardRef< + React.ElementRef, + ProgressProps +>(({ className, value = 0, max = 100, variant, size, showLabel, label, showPercentage, ...props }, ref) => { + const percentage = Math.round((value / max) * 100) + + return ( +
+ {(showLabel || showPercentage) && ( +
+ {showLabel && ( + + {label || 'Progress'} + + )} + {showPercentage && ( + + {percentage}% + + )} +
+ )} + + + +
+ ) +}) +Progress.displayName = ProgressPrimitive.Root.displayName + +export { Progress } \ No newline at end of file diff --git a/src/components/ui/table/Table.stories.tsx b/src/components/ui/table/Table.stories.tsx new file mode 100644 index 0000000..fd3f011 --- /dev/null +++ b/src/components/ui/table/Table.stories.tsx @@ -0,0 +1,400 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { + Table, + TableBody, + TableCaption, + TableCell, + TableFooter, + TableHead, + TableHeader, + TableRow, +} from './table'; +import { Badge } from '../badge'; + +const meta: Meta = { + title: 'Design System/Components/Data Display/Table', + component: Table, + parameters: { + layout: 'centered', + docs: { + description: { + component: ` +# Table Component + +A comprehensive table component system for displaying structured data in recipe platforms, including nutritional information, ingredient measurements, and cooking analytics. + +## Features + +- **Responsive Design**: Automatically handles overflow with horizontal scrolling +- **Accessibility**: Proper semantic structure with headers and captions +- **Hover States**: Visual feedback for row interactions +- **Flexible Layout**: Supports headers, footers, and captions +- **Cultural Formatting**: Supports international number formats and units + +## Use Cases + +- **Nutritional Information**: Per-serving nutritional data with daily values +- **Ingredient Measurements**: Conversion tables for different serving sizes +- **Recipe Analytics**: Cooking times, difficulty ratings, popularity metrics +- **Comparison Tables**: Side-by-side recipe comparisons +- **Cooking Progress**: Step-by-step progress tracking + ` + } + } + }, + tags: ['autodocs'], +}; + +export default meta; +type Story = StoryObj; + +// Basic table +export const Default: Story = { + render: () => ( + + A list of recent recipe uploads + + + Recipe + Status + Method + Prep Time + + + + + Pasta Carbonara + Published + Stovetop + 15 min + + + Chocolate Cake + Draft + Oven + 30 min + + + Caesar Salad + Published + No-Cook + 10 min + + +
+ ), +}; + +// Nutritional information table +export const NutritionalInformation: Story = { + render: () => ( +
+ + Nutritional information per serving + + + Nutrient + Amount + % Daily Value + + + + + Calories + 425 + 21% + + + Total Fat + 18g + 23% + + + Saturated Fat + 8g + 40% + + + Cholesterol + 95mg + 32% + + + Sodium + 650mg + 28% + + + Total Carbs + 42g + 15% + + + Protein + 18g + 36% + + + + + Total Daily Values + Based on 2000 calorie diet + + +
+
+ ), + parameters: { + docs: { + description: { + story: 'Nutritional facts table showing per-serving data with daily value percentages, ideal for recipe detail pages.' + } + } + } +}; + +// Ingredient conversion table +export const IngredientConversions: Story = { + render: () => ( +
+ + Ingredient measurements for different serving sizes + + + Ingredient + 2 servings + 4 servings + 6 servings + + + + + Pasta + 200g + 400g + 600g + + + Eggs + 2 large + 4 large + 6 large + + + Pancetta + 100g + 200g + 300g + + + Parmesan + 50g + 100g + 150g + + + Black Pepper + 1/4 tsp + 1/2 tsp + 3/4 tsp + + +
+
+ ), + parameters: { + docs: { + description: { + story: 'Ingredient conversion table showing measurements scaled for different serving sizes.' + } + } + } +}; + +// Recipe comparison table +export const RecipeComparison: Story = { + render: () => ( +
+ + Comparison of pasta recipes + + + Recipe + Difficulty + Prep Time + Cook Time + Servings + Rating + Dietary + + + + + Pasta Carbonara + + Medium + + 10 min + 15 min + 4 + 4.8/5 + + Contains Eggs + + + + Aglio e Olio + + Easy + + 5 min + 12 min + 4 + 4.5/5 + + Vegetarian + + + + Pasta Puttanesca + + Easy + + 15 min + 20 min + 4 + 4.6/5 + + Dairy-Free + + + + Cacio e Pepe + + Hard + + 5 min + 10 min + 2 + 4.9/5 + + Vegetarian + + + +
+
+ ), + parameters: { + layout: 'fullscreen', + docs: { + description: { + story: 'Recipe comparison table with badges for difficulty levels and dietary restrictions.' + } + } + } +}; + +// Cultural formatting example +export const CulturalFormatting: Story = { + render: () => ( +
+ + International measurement conversions + + + Ingredient + Metric + Imperial + Japanese + + + + + Flour + 250g + 2 cups + 250ml + + + Sugar + 200g + 1 cup + 200ml + + + Butter + 115g + 1/2 cup + 115g + + + Milk + 240ml + 1 cup + 1合 (gō) + + +
+
+ ), + parameters: { + docs: { + description: { + story: 'Table showing cultural data formatting for international measurement systems.' + } + } + } +}; + +// Mobile responsive example +export const ResponsiveDesign: Story = { + render: () => ( +
+ + Recipe quick facts (mobile view) + + + Metric + Value + + + + + Prep Time + 15 min + + + Cook Time + 20 min + + + Total Time + 35 min + + + Servings + 4 people + + + Difficulty + + Medium + + + + Rating + 4.8/5 ⭐ + + +
+
+ ), + parameters: { + docs: { + description: { + story: 'Mobile-optimized table layout showing key recipe information in a compact format.' + } + } + } +}; \ No newline at end of file diff --git a/src/components/ui/table/index.ts b/src/components/ui/table/index.ts new file mode 100644 index 0000000..9748279 --- /dev/null +++ b/src/components/ui/table/index.ts @@ -0,0 +1,10 @@ +export { + Table, + TableHeader, + TableBody, + TableFooter, + TableHead, + TableRow, + TableCell, + TableCaption, +} from './table' \ No newline at end of file diff --git a/src/components/ui/table/table.tsx b/src/components/ui/table/table.tsx new file mode 100644 index 0000000..0f0efb8 --- /dev/null +++ b/src/components/ui/table/table.tsx @@ -0,0 +1,119 @@ +import * as React from "react" +import { cn } from "@/lib/utils" + +const Table = React.forwardRef< + HTMLTableElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+ + +)) +Table.displayName = "Table" + +const TableHeader = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableHeader.displayName = "TableHeader" + +const TableBody = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableBody.displayName = "TableBody" + +const TableFooter = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + tr]:last:border-b-0", + className + )} + {...props} + /> +)) +TableFooter.displayName = "TableFooter" + +const TableRow = React.forwardRef< + HTMLTableRowElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableRow.displayName = "TableRow" + +const TableHead = React.forwardRef< + HTMLTableCellElement, + React.ThHTMLAttributes +>(({ className, ...props }, ref) => ( +
[role=checkbox]]:translate-y-[2px]", + className + )} + {...props} + /> +)) +TableHead.displayName = "TableHead" + +const TableCell = React.forwardRef< + HTMLTableCellElement, + React.TdHTMLAttributes +>(({ className, ...props }, ref) => ( + [role=checkbox]]:translate-y-[2px]", + className + )} + {...props} + /> +)) +TableCell.displayName = "TableCell" + +const TableCaption = React.forwardRef< + HTMLTableCaptionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +TableCaption.displayName = "TableCaption" + +export { + Table, + TableHeader, + TableBody, + TableFooter, + TableHead, + TableRow, + TableCell, + TableCaption, +} \ No newline at end of file diff --git a/src/components/ui/tabs/index.ts b/src/components/ui/tabs/index.ts new file mode 100644 index 0000000..57bdfd3 --- /dev/null +++ b/src/components/ui/tabs/index.ts @@ -0,0 +1 @@ +export { Tabs, TabsList, TabsTrigger, TabsContent } from './tabs' \ No newline at end of file diff --git a/src/components/ui/tabs/tabs.tsx b/src/components/ui/tabs/tabs.tsx new file mode 100644 index 0000000..8e22d22 --- /dev/null +++ b/src/components/ui/tabs/tabs.tsx @@ -0,0 +1,52 @@ +import * as React from "react" +import * as TabsPrimitive from "@radix-ui/react-tabs" +import { cn } from "@/lib/utils" + +const Tabs = TabsPrimitive.Root + +const TabsList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsList.displayName = TabsPrimitive.List.displayName + +const TabsTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsTrigger.displayName = TabsPrimitive.Trigger.displayName + +const TabsContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsContent.displayName = TabsPrimitive.Content.displayName + +export { Tabs, TabsList, TabsTrigger, TabsContent } \ No newline at end of file diff --git a/tailwind.config.ts b/tailwind.config.ts index e0c796c..40ff4b2 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -86,6 +86,20 @@ export default { "2xl": "0px 25px 50px -12px rgba(0, 0, 0, 0.25)", inner: "inset 0px 2px 4px 0px rgba(0, 0, 0, 0.05)", }, + keyframes: { + "accordion-down": { + from: { height: "0" }, + to: { height: "var(--radix-accordion-content-height)" }, + }, + "accordion-up": { + from: { height: "var(--radix-accordion-content-height)" }, + to: { height: "0" }, + }, + }, + animation: { + "accordion-down": "accordion-down 0.2s ease-out", + "accordion-up": "accordion-up 0.2s ease-out", + }, }, }, plugins: [], From 2216430e55691d1e63c1ec81b14dc0e06c3ec94f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Jun 2025 20:40:18 +0000 Subject: [PATCH 3/4] Complete all 8 data display components with comprehensive stories and accessibility features Co-authored-by: wtfzdotnet <639376+wtfzdotnet@users.noreply.github.com> --- package-lock.json | 24 + package.json | 1 + .../ui/accordion/Accordion.stories.tsx | 2 +- .../ui/aspect-ratio/AspectRatio.stories.tsx | 395 +++++++++++++ .../ui/aspect-ratio/aspect-ratio.tsx | 5 + src/components/ui/aspect-ratio/index.ts | 1 + src/components/ui/badge/Badge.stories.tsx | 2 +- .../ui/progress/Progress.stories.tsx | 3 +- .../ui/skeleton/Skeleton.stories.tsx | 402 +++++++++++++ src/components/ui/skeleton/index.ts | 1 + src/components/ui/skeleton/skeleton.tsx | 16 + src/components/ui/table/Table.stories.tsx | 2 +- src/components/ui/tabs/Tabs.stories.tsx | 530 ++++++++++++++++++ 13 files changed, 1380 insertions(+), 4 deletions(-) create mode 100644 src/components/ui/aspect-ratio/AspectRatio.stories.tsx create mode 100644 src/components/ui/aspect-ratio/aspect-ratio.tsx create mode 100644 src/components/ui/aspect-ratio/index.ts create mode 100644 src/components/ui/skeleton/Skeleton.stories.tsx create mode 100644 src/components/ui/skeleton/index.ts create mode 100644 src/components/ui/skeleton/skeleton.tsx create mode 100644 src/components/ui/tabs/Tabs.stories.tsx diff --git a/package-lock.json b/package-lock.json index 4820217..ad513aa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@radix-ui/react-accordion": "^1.2.11", "@radix-ui/react-alert-dialog": "^1.1.14", + "@radix-ui/react-aspect-ratio": "^1.1.7", "@radix-ui/react-avatar": "^1.1.10", "@radix-ui/react-checkbox": "^1.3.2", "@radix-ui/react-dialog": "^1.1.14", @@ -1211,6 +1212,29 @@ } } }, + "node_modules/@radix-ui/react-aspect-ratio": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-aspect-ratio/-/react-aspect-ratio-1.1.7.tgz", + "integrity": "sha512-Yq6lvO9HQyPwev1onK1daHCHqXVLzPhSVjmsNjCa2Zcxy2f7uJD2itDtxknv6FzAKCwD1qQkeVDmX/cev13n/g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-avatar": { "version": "1.1.10", "license": "MIT", diff --git a/package.json b/package.json index c02a41e..2994ebf 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "dependencies": { "@radix-ui/react-accordion": "^1.2.11", "@radix-ui/react-alert-dialog": "^1.1.14", + "@radix-ui/react-aspect-ratio": "^1.1.7", "@radix-ui/react-avatar": "^1.1.10", "@radix-ui/react-checkbox": "^1.3.2", "@radix-ui/react-dialog": "^1.1.14", diff --git a/src/components/ui/accordion/Accordion.stories.tsx b/src/components/ui/accordion/Accordion.stories.tsx index 09e0245..0c55a04 100644 --- a/src/components/ui/accordion/Accordion.stories.tsx +++ b/src/components/ui/accordion/Accordion.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react-vite'; import { Accordion, AccordionContent, diff --git a/src/components/ui/aspect-ratio/AspectRatio.stories.tsx b/src/components/ui/aspect-ratio/AspectRatio.stories.tsx new file mode 100644 index 0000000..b780334 --- /dev/null +++ b/src/components/ui/aspect-ratio/AspectRatio.stories.tsx @@ -0,0 +1,395 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { AspectRatio } from './aspect-ratio'; +import { Badge } from '../badge'; +import { Play, Volume2, Maximize } from 'lucide-react'; + +const meta: Meta = { + title: 'Design System/Components/Data Display/Aspect Ratio', + component: AspectRatio, + parameters: { + layout: 'centered', + docs: { + description: { + component: ` +# Aspect Ratio Component + +A component for maintaining consistent aspect ratios for recipe images, instruction videos, and other media content across different screen sizes. + +## Features + +- **Consistent Ratios**: Maintains proportions across all devices +- **Responsive**: Scales properly with container width +- **Media Optimized**: Perfect for images and videos +- **Accessibility**: Proper semantic structure for media content +- **Performance**: CSS-based aspect ratio maintenance + +## Use Cases + +- **Recipe Images**: Hero images, step photos, ingredient shots +- **Instruction Videos**: Cooking demonstrations, technique videos +- **Photo Galleries**: Recipe photo collections +- **Social Media**: Recipe posts with consistent dimensions +- **Thumbnails**: Preview images for recipe cards and lists + +## Common Ratios + +- **16:9** - Video content, hero images +- **4:3** - Traditional photography, detailed shots +- **1:1** - Social media, Instagram-style posts +- **3:2** - Standard photography ratio + ` + } + } + }, + tags: ['autodocs'], + argTypes: { + ratio: { + control: { type: 'number', min: 0.1, max: 5, step: 0.1 }, + description: 'Aspect ratio (width / height)' + } + } +}; + +export default meta; +type Story = StoryObj; + +// Default aspect ratio +export const Default: Story = { + render: () => ( +
+ +
+ 🍝 +
+
+
+ ), +}; + +// Recipe image gallery +export const RecipeImageGallery: Story = { + render: () => ( +
+ {/* Hero image - 16:9 */} +
+ +
+ 🍝 +
+ + Hero Image + +
+
+
+
+ + {/* Process shots - 4:3 */} +
+ +
+ 🧄 +
+ + Step 1 + +
+
+
+ + +
+ 🍳 +
+ + Step 2 + +
+
+
+
+ + {/* Square social media format - 1:1 */} +
+ +
+ 🧀 +
+
+ + +
+ 🥓 +
+
+ + +
+ 🌿 +
+
+ + +
+ 🍷 +
+
+
+ + {/* Final dish - 3:2 standard photo */} +
+ +
+ 🍽️ +
+ + Final Dish + +
+
+
+
+
+ ), + parameters: { + layout: 'fullscreen', + docs: { + description: { + story: 'Recipe image gallery showing different aspect ratios for various types of food photography and social media formats.' + } + } + } +}; + +// Video content +export const VideoContent: Story = { + render: () => ( +
+ {/* Main cooking video - 16:9 */} +
+

How to Make Perfect Carbonara

+ +
+
+
+ +
+

Full Recipe Tutorial

+

15:32

+
+ + {/* Video controls overlay */} +
+
+
+ +
+
+
+ 5:12 / 15:32 + + +
+
+
+
+
+
+ + {/* Quick tips videos - 9:16 (mobile/story format) */} +
+

Quick Cooking Tips

+
+ {[ + { title: "Perfect Egg Technique", duration: "0:45", emoji: "🥚" }, + { title: "Grating Cheese Tips", duration: "0:30", emoji: "🧀" }, + { title: "Pan Temperature", duration: "1:12", emoji: "🍳" }, + { title: "Pasta Water Secret", duration: "0:52", emoji: "💧" } + ].map((tip, i) => ( + +
+ {tip.emoji} +

{tip.title}

+

{tip.duration}

+ +
+
+ +
+
+
+
+ ))} +
+
+
+ ), + parameters: { + layout: 'fullscreen', + docs: { + description: { + story: 'Video content examples showing different aspect ratios for cooking tutorials and quick tips in both landscape and portrait formats.' + } + } + } +}; + +// Recipe cards with different ratios +export const RecipeCards: Story = { + render: () => ( +
+ {/* Standard recipe card - 4:3 */} +
+ +
+ 🍝 +
+
+
+

Pasta Carbonara

+

Classic Roman pasta dish

+
+ Italian + Medium +
+
+
+ + {/* Widescreen recipe card - 16:9 */} +
+ +
+ 🥗 +
+
+
+

Caesar Salad

+

Fresh and crispy classic

+
+ Vegetarian + Easy +
+
+
+ + {/* Square recipe card - 1:1 */} +
+ +
+ 🧁 +
+
+
+

Vanilla Cupcakes

+

Sweet and fluffy treats

+
+ Dessert + Easy +
+
+
+
+ ), + parameters: { + layout: 'fullscreen', + docs: { + description: { + story: 'Recipe cards demonstrating different aspect ratios for various types of recipe imagery and layouts.' + } + } + } +}; + +// Mobile responsive +export const MobileResponsive: Story = { + render: () => ( +
+ {/* Mobile hero image - 16:9 */} + +
+ 🍕 +
+ + 4.8 ⭐ + +
+
+
+ + {/* Mobile process shots - 3:2 */} +
+ +
+ 🍅 +
+
+ + +
+ 🧀 +
+
+
+ + {/* Mobile video tutorial - 16:9 */} + +
+
+
+ +
+

Pizza Tutorial

+

8:45

+
+
+
+
+ ), + parameters: { + docs: { + description: { + story: 'Mobile-optimized aspect ratios for recipe content on small screens with touch-friendly interactions.' + } + } + } +}; + +// Comparison of all ratios +export const AspectRatioComparison: Story = { + render: () => ( +
+

Common Aspect Ratios

+ +
+ {[ + { ratio: 16 / 9, name: "16:9", description: "Widescreen - Perfect for video content and hero images", color: "from-blue-100 to-cyan-100" }, + { ratio: 4 / 3, name: "4:3", description: "Traditional - Great for detailed food photography", color: "from-green-100 to-emerald-100" }, + { ratio: 3 / 2, name: "3:2", description: "Standard Photo - Classic photography ratio", color: "from-yellow-100 to-amber-100" }, + { ratio: 1 / 1, name: "1:1", description: "Square - Perfect for social media and thumbnails", color: "from-purple-100 to-pink-100" }, + { ratio: 9 / 16, name: "9:16", description: "Portrait - Mobile stories and vertical videos", color: "from-orange-100 to-red-100" } + ].map((item, i) => ( +
+
+

{item.name}

+

{item.description}

+
+
+ +
+ {item.name} +
+
+
+
+ ))} +
+
+ ), + parameters: { + docs: { + description: { + story: 'Visual comparison of common aspect ratios used in recipe and food content with their typical use cases.' + } + } + } +}; \ No newline at end of file diff --git a/src/components/ui/aspect-ratio/aspect-ratio.tsx b/src/components/ui/aspect-ratio/aspect-ratio.tsx new file mode 100644 index 0000000..750d38c --- /dev/null +++ b/src/components/ui/aspect-ratio/aspect-ratio.tsx @@ -0,0 +1,5 @@ +import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio" + +const AspectRatio = AspectRatioPrimitive.Root + +export { AspectRatio } \ No newline at end of file diff --git a/src/components/ui/aspect-ratio/index.ts b/src/components/ui/aspect-ratio/index.ts new file mode 100644 index 0000000..49e1080 --- /dev/null +++ b/src/components/ui/aspect-ratio/index.ts @@ -0,0 +1 @@ +export { AspectRatio } from './aspect-ratio' \ No newline at end of file diff --git a/src/components/ui/badge/Badge.stories.tsx b/src/components/ui/badge/Badge.stories.tsx index 5ffd6d1..ad879f4 100644 --- a/src/components/ui/badge/Badge.stories.tsx +++ b/src/components/ui/badge/Badge.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react-vite'; import { Badge } from './badge'; import { Star, Utensils, Clock, Users, AlertTriangle, CheckCircle, Leaf, Flame } from 'lucide-react'; diff --git a/src/components/ui/progress/Progress.stories.tsx b/src/components/ui/progress/Progress.stories.tsx index 6b15ae7..a41180f 100644 --- a/src/components/ui/progress/Progress.stories.tsx +++ b/src/components/ui/progress/Progress.stories.tsx @@ -1,4 +1,5 @@ -import type { Meta, StoryObj } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react-vite'; +import * as React from 'react'; import { Progress } from './progress'; import { Clock, ChefHat, CheckCircle } from 'lucide-react'; diff --git a/src/components/ui/skeleton/Skeleton.stories.tsx b/src/components/ui/skeleton/Skeleton.stories.tsx new file mode 100644 index 0000000..6495c2a --- /dev/null +++ b/src/components/ui/skeleton/Skeleton.stories.tsx @@ -0,0 +1,402 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { Skeleton } from './skeleton'; + +const meta: Meta = { + title: 'Design System/Components/Data Display/Skeleton', + component: Skeleton, + parameters: { + layout: 'centered', + docs: { + description: { + component: ` +# Skeleton Component + +A loading placeholder component that provides visual feedback while recipe data, images, and other content are being fetched. + +## Features + +- **Pulse Animation**: Smooth loading animation that indicates content is loading +- **Flexible Sizing**: Can be sized to match any content shape +- **Accessible**: Maintains layout structure during loading states +- **Performance**: Lightweight CSS-only animations +- **Responsive**: Adapts to different screen sizes and content areas + +## Use Cases + +- **Recipe Cards**: Loading placeholders for recipe information +- **Images**: Placeholder for recipe photos and instruction videos +- **Nutritional Data**: Loading states for nutrition facts tables +- **User Content**: Placeholder for user profiles and reviews +- **Lists**: Loading states for recipe collections and search results + ` + } + } + }, + tags: ['autodocs'], + argTypes: { + className: { + control: 'text', + description: 'Additional CSS classes for styling' + } + } +}; + +export default meta; +type Story = StoryObj; + +// Basic skeleton +export const Default: Story = { + render: () => ( +
+ + +
+ ), +}; + +// Recipe card skeleton +export const RecipeCard: Story = { + render: () => ( +
+ {/* Image skeleton */} + + + {/* Content skeleton */} +
+ {/* Title */} + + + {/* Description */} +
+ + +
+ + {/* Metadata */} +
+ + +
+ + {/* Tags */} +
+ + + +
+ + {/* Action buttons */} +
+ + + +
+
+
+ ), + parameters: { + docs: { + description: { + story: 'Recipe card loading skeleton showing all typical elements: image, title, description, metadata, tags, and action buttons.' + } + } + } +}; + +// Recipe grid skeleton +export const RecipeGrid: Story = { + render: () => ( +
+ {Array.from({ length: 6 }, (_, i) => ( +
+ +
+ +
+ + +
+
+ + +
+
+ + +
+
+
+ ))} +
+ ), + parameters: { + layout: 'fullscreen', + docs: { + description: { + story: 'Recipe grid loading skeleton showing multiple recipe cards in a responsive layout.' + } + } + } +}; + +// Nutrition facts skeleton +export const NutritionFacts: Story = { + render: () => ( +
+ {/* Title */} + + + {/* Serving info */} + + + {/* Main nutrition values */} +
+
+ + +
+ +
+ +
+ + +
+
+ +
+ +
+ + +
+
+
+ + {/* Additional nutrients */} +
+ {Array.from({ length: 6 }, (_, i) => ( +
+ +
+ + +
+
+ ))} +
+ + {/* Daily values note */} + + +
+ ), + parameters: { + docs: { + description: { + story: 'Nutrition facts panel loading skeleton showing the typical layout of nutritional information.' + } + } + } +}; + +// Recipe instructions skeleton +export const RecipeInstructions: Story = { + render: () => ( +
+ {/* Ingredients section */} +
+ +
+ {Array.from({ length: 6 }, (_, i) => ( +
+ +
+ +
+ +
+ ))} +
+
+ + {/* Instructions section */} +
+ +
+ {Array.from({ length: 5 }, (_, i) => ( +
+ +
+
+ + +
+ + +
+
+ ))} +
+
+
+ ), + parameters: { + layout: 'padded', + docs: { + description: { + story: 'Recipe instructions loading skeleton showing ingredients list and step-by-step instructions.' + } + } + } +}; + +// User profile skeleton +export const UserProfile: Story = { + render: () => ( +
+ {/* Profile header */} +
+ +
+ + + +
+
+ + {/* Stats */} +
+
+ + +
+
+ + +
+
+ + +
+
+ + {/* Bio */} +
+ + + +
+
+ ), + parameters: { + docs: { + description: { + story: 'User profile loading skeleton showing avatar, name, stats, and bio information.' + } + } + } +}; + +// Search results skeleton +export const SearchResults: Story = { + render: () => ( +
+ {/* Search header */} +
+ + +
+ + {/* Filter bar */} +
+ + + + +
+ + {/* Results list */} +
+ {Array.from({ length: 5 }, (_, i) => ( +
+ +
+ + + +
+ + + +
+
+ + +
+
+
+ + +
+
+ ))} +
+
+ ), + parameters: { + layout: 'fullscreen', + docs: { + description: { + story: 'Search results loading skeleton showing search header, filters, and result items.' + } + } + } +}; + +// Mobile optimized skeleton +export const MobileOptimized: Story = { + render: () => ( +
+ {/* Mobile recipe card */} +
+ +
+ + + + +
+ + +
+ +
+ + +
+
+
+ + {/* Mobile list item */} +
+ +
+ + +
+ + +
+
+
+
+ ), + parameters: { + docs: { + description: { + story: 'Mobile-optimized skeleton layouts for recipe cards and list items on small screens.' + } + } + } +}; \ No newline at end of file diff --git a/src/components/ui/skeleton/index.ts b/src/components/ui/skeleton/index.ts new file mode 100644 index 0000000..a82e3fa --- /dev/null +++ b/src/components/ui/skeleton/index.ts @@ -0,0 +1 @@ +export { Skeleton } from './skeleton' \ No newline at end of file diff --git a/src/components/ui/skeleton/skeleton.tsx b/src/components/ui/skeleton/skeleton.tsx new file mode 100644 index 0000000..a76cf46 --- /dev/null +++ b/src/components/ui/skeleton/skeleton.tsx @@ -0,0 +1,16 @@ +import * as React from "react" +import { cn } from "@/lib/utils" + +function Skeleton({ + className, + ...props +}: React.HTMLAttributes) { + return ( +
+ ) +} + +export { Skeleton } \ No newline at end of file diff --git a/src/components/ui/table/Table.stories.tsx b/src/components/ui/table/Table.stories.tsx index fd3f011..219526b 100644 --- a/src/components/ui/table/Table.stories.tsx +++ b/src/components/ui/table/Table.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react-vite'; import { Table, TableBody, diff --git a/src/components/ui/tabs/Tabs.stories.tsx b/src/components/ui/tabs/Tabs.stories.tsx new file mode 100644 index 0000000..738b527 --- /dev/null +++ b/src/components/ui/tabs/Tabs.stories.tsx @@ -0,0 +1,530 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from './tabs'; +import { Badge } from '../badge'; +import { Progress } from '../progress'; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '../table'; +import { Star, Clock, Users, ChefHat, Heart, Share2, Bookmark } from 'lucide-react'; + +const meta: Meta = { + title: 'Design System/Components/Data Display/Tabs', + component: Tabs, + parameters: { + layout: 'centered', + docs: { + description: { + component: ` +# Tabs Component + +A tabbed interface component for organizing recipe information including variations, nutritional info, reviews, and related content. + +## Features + +- **Keyboard Navigation**: Full keyboard accessibility support +- **Smooth Transitions**: CSS-based tab switching animations +- **Responsive Design**: Mobile-optimized tab layout +- **Flexible Content**: Support for any content type within tabs +- **Focus Management**: Proper focus handling for accessibility + +## Use Cases + +- **Recipe Information**: "Recipe", "Nutrition", "Reviews", "Variations" +- **Recipe Details**: Organize different aspects of recipe information +- **User Dashboards**: "My Recipes", "Saved", "Shopping Lists" +- **Recipe Comparison**: Side-by-side recipe analysis +- **Content Organization**: Group related recipe content efficiently + ` + } + } + }, + tags: ['autodocs'], + argTypes: { + defaultValue: { + control: 'text', + description: 'The default active tab' + }, + orientation: { + control: 'select', + options: ['horizontal', 'vertical'], + description: 'Tab orientation' + } + } +}; + +export default meta; +type Story = StoryObj; + +// Default tabs +export const Default: Story = { + render: () => ( +
+ + + Recipe + Nutrition + Reviews + + +

Pasta Carbonara Recipe

+
+

A classic Roman pasta dish with eggs, cheese, and pancetta.

+
+ + + 35 min + + + + 4 servings + +
+
+
+ +

Nutritional Information

+
+
+

425

+

Calories

+
+
+

18g

+

Protein

+
+
+
+ +

User Reviews

+
+
+ {[1, 2, 3, 4, 5].map((star) => ( + + ))} +
+ 4.8 out of 5 (127 reviews) +
+
+
+
+ ), +}; + +// Recipe detail tabs +export const RecipeDetails: Story = { + render: () => ( +
+ + + Overview + Ingredients + Instructions + Nutrition + + + +
+
+
+

Classic Pasta Carbonara

+

+ A traditional Roman pasta dish made with eggs, cheese, and guanciale. + Simple ingredients combined with perfect technique create this iconic comfort food. +

+
+
+ + + +
+
+ +
+
+ +

35 minutes

+

Total time

+
+
+ +

4 servings

+

Serves

+
+
+ +

Medium

+

Difficulty

+
+
+ +

4.8/5

+

Rating

+
+
+ +
+

Tags

+
+ Italian + Medium + Comfort Food + Pasta + Contains Eggs + 30 min or less +
+
+
+
+ + +
+
+

Ingredients

+
Serves 4
+
+ +
+ {[ + { name: 'Spaghetti or linguine', amount: '400g', note: 'preferably bronze-cut' }, + { name: 'Guanciale', amount: '200g', note: 'diced (or pancetta)' }, + { name: 'Large eggs', amount: '4', note: 'room temperature' }, + { name: 'Pecorino Romano cheese', amount: '100g', note: 'freshly grated' }, + { name: 'Freshly ground black pepper', amount: 'to taste', note: 'coarsely ground' }, + { name: 'Sea salt', amount: 'for pasta water', note: 'coarse' } + ].map((ingredient, i) => ( +
+
+ +
+

{ingredient.name}

+

{ingredient.note}

+
+
+ {ingredient.amount} +
+ ))} +
+
+
+ + +
+

Instructions

+ +
+ {[ + { step: 1, title: 'Prepare the water', instruction: 'Bring a large pot of salted water to a rolling boil. Use plenty of water and salt generously - it should taste like the sea.', time: '5 min' }, + { step: 2, title: 'Cook the pasta', instruction: 'Add the pasta to the boiling water and cook according to package directions until al dente. Reserve 1 cup of pasta water before draining.', time: '8-12 min' }, + { step: 3, title: 'Render the guanciale', instruction: 'While pasta cooks, place diced guanciale in a large, cold pan. Cook over medium heat until fat renders and guanciale is golden and crispy.', time: '5-7 min' }, + { step: 4, title: 'Prepare the egg mixture', instruction: 'In a bowl, whisk together whole eggs, grated Pecorino Romano, and plenty of freshly ground black pepper.', time: '2 min' }, + { step: 5, title: 'Combine everything', instruction: 'Remove pan from heat. Add hot, drained pasta to the pan with guanciale. Toss to coat. Add egg mixture and toss vigorously, adding pasta water as needed to create a creamy sauce.', time: '2-3 min' } + ].map((step) => ( +
+
+ {step.step} +
+
+
+

{step.title}

+ {step.time} +
+

{step.instruction}

+
+
+ ))} +
+
+
+ + +
+

Nutrition Facts

+

Per serving (1/4 of recipe)

+ + + + + Nutrient + Amount + % Daily Value + + + + + Calories + 425 + 21% + + + Total Fat + 18g + 23% + + + Saturated Fat + 8g + 40% + + + Cholesterol + 95mg + 32% + + + Sodium + 650mg + 28% + + + Total Carbs + 42g + 15% + + + Protein + 18g + 36% + + +
+ +
+

Dietary Information

+
+ High Protein + Contains Eggs + Contains Dairy + Gluten +
+
+
+
+
+
+ ), + parameters: { + layout: 'fullscreen', + docs: { + description: { + story: 'Comprehensive recipe detail tabs showing all aspects of a recipe with rich content.' + } + } + } +}; + +// User dashboard tabs +export const UserDashboard: Story = { + render: () => ( +
+ + + My Recipes + Saved + Shopping Lists + Meal Plans + + + +
+

My Recipes

+ 12 recipes +
+ +
+ {Array.from({ length: 6 }, (_, i) => ( +
+
+ 🍝 +
+

Pasta Recipe {i + 1}

+

Updated 2 days ago

+
+ Italian + Medium +
+
+ ))} +
+
+ + +
+

Saved Recipes

+ 24 recipes +
+ +
+ {Array.from({ length: 5 }, (_, i) => ( +
+
+ 🥗 +
+
+

Saved Recipe {i + 1}

+

By Chef Name • Saved 3 days ago

+
+ Vegetarian + Easy +
+
+
+ + 4.5 +
+
+ ))} +
+
+ + +
+

Shopping Lists

+ 3 lists +
+ +
+
+
+

Weekly Meal Prep

+ +
+

12 items • 3 completed

+
+
+ + 400g pasta +
+
+ + 200g guanciale +
+
+ + Eggs (dozen) +
+
+
+
+
+ + +
+

Meal Plans

+ This week +
+ +
+ {['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'].map((day) => ( +
+
{day}
+
+
+ 🍝 +
+
+ 🥗 +
+
+ 🍲 +
+
+
+ ))} +
+
+
+
+ ), + parameters: { + layout: 'fullscreen', + docs: { + description: { + story: 'User dashboard with tabs for managing personal recipes, saved items, shopping lists, and meal planning.' + } + } + } +}; + +// Mobile responsive tabs +export const MobileResponsive: Story = { + render: () => ( +
+ + + Recipe + Nutrition + Reviews + + + +

Quick Recipe

+
+
+ Prep Time: + 10 min +
+
+ Cook Time: + 25 min +
+
+ Servings: + 4 people +
+
+
+ Italian + Medium +
+
+ + +

Nutrition

+
+
+

425

+

Calories

+
+
+

18g

+

Protein

+
+
+
+ + +

Reviews

+
+
+
+ {[1, 2, 3, 4, 5].map((star) => ( + + ))} +
+ 4.8 (127) +
+
+
+ Sarah: "Perfect recipe!" +
+
+ Mike: "Easy to follow" +
+
+
+
+
+
+ ), + parameters: { + docs: { + description: { + story: 'Mobile-optimized tabs with compact layout and touch-friendly interactions.' + } + } + } +}; \ No newline at end of file From 52890b3ca84b0aabc86d0e409bb3afad7fff58b0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Jun 2025 20:56:58 +0000 Subject: [PATCH 4/4] Fix icon sizes in AspectRatio stories - increase video control icons from 16px to 20px Co-authored-by: wtfzdotnet <639376+wtfzdotnet@users.noreply.github.com> --- src/components/ui/aspect-ratio/AspectRatio.stories.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/ui/aspect-ratio/AspectRatio.stories.tsx b/src/components/ui/aspect-ratio/AspectRatio.stories.tsx index b780334..bc1b83e 100644 --- a/src/components/ui/aspect-ratio/AspectRatio.stories.tsx +++ b/src/components/ui/aspect-ratio/AspectRatio.stories.tsx @@ -182,13 +182,13 @@ export const VideoContent: Story = {
- +
5:12 / 15:32 - - + +