create-wakii-stack 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -2
- package/dist/chunk-7XGW4UJ5.js +75 -0
- package/dist/chunk-AKPWS7FJ.js +76 -0
- package/dist/chunk-ELM6GGC5.js +50 -0
- package/dist/chunk-ON2WTGZH.js +77 -0
- package/dist/index.js +385 -0
- package/dist/manifest-2JVLRMDB.js +10 -0
- package/dist/manifest-O35TPW73.js +10 -0
- package/dist/manifest-TXTY67BF.js +10 -0
- package/dist/manifest-XWHR7Y3F.js +10 -0
- package/package.json +23 -4
- package/stack-template/.changeset/config.json +15 -0
- package/stack-template/.changeset/vu53-ui-redesign.md +11 -0
- package/stack-template/.claude/agents/be-dev.md +56 -0
- package/stack-template/.claude/agents/contracts-keeper.md +51 -0
- package/stack-template/.claude/agents/e2e-runner.md +61 -0
- package/stack-template/.claude/agents/fe-dev.md +53 -0
- package/stack-template/.claude/settings.json +77 -0
- package/stack-template/.claude/settings.local.json +9 -0
- package/stack-template/.claude/skills/add-service/SKILL.md +81 -0
- package/stack-template/.claude/skills/sync-contracts/SKILL.md +74 -0
- package/stack-template/.editorconfig +21 -0
- package/stack-template/.gitattributes +10 -0
- package/stack-template/.github/pull_request_template.md +26 -0
- package/stack-template/.github/workflow-templates/services-ci.yml.example +69 -0
- package/stack-template/.github/workflows/contracts.yml +133 -0
- package/stack-template/.github/workflows/convergence.yml +209 -0
- package/stack-template/.github/workflows/git-guard.yml +44 -0
- package/stack-template/.github/workflows/publish-uikit.yml +96 -0
- package/stack-template/.github/workflows/remotes-activity.yml +82 -0
- package/stack-template/.github/workflows/remotes-tasks.yml +82 -0
- package/stack-template/.github/workflows/services-auth-svc.yml +153 -0
- package/stack-template/.github/workflows/services-go-svc.yml +281 -0
- package/stack-template/.github/workflows/services-node-svc.yml +173 -0
- package/stack-template/.github/workflows/services-spring-svc.yml +132 -0
- package/stack-template/.github/workflows/shell.yml +78 -0
- package/stack-template/.github/workflows/ui-kit.yml +51 -0
- package/stack-template/.husky/commit-msg +14 -0
- package/stack-template/.husky/pre-push +51 -0
- package/stack-template/.spectral.yaml +17 -0
- package/stack-template/AGENTS.md +197 -0
- package/stack-template/CLAUDE.md +114 -0
- package/stack-template/README.md +172 -0
- package/stack-template/app/admin/index.html +12 -0
- package/stack-template/app/admin/package.json +40 -0
- package/stack-template/app/admin/scripts/verify-chart.mjs +49 -0
- package/stack-template/app/admin/src/App.test.tsx +70 -0
- package/stack-template/app/admin/src/App.tsx +33 -0
- package/stack-template/app/admin/src/data/users.ts +47 -0
- package/stack-template/app/admin/src/layout/AdminLayout.tsx +212 -0
- package/stack-template/app/admin/src/main.tsx +17 -0
- package/stack-template/app/admin/src/pages/ComponentsDemo.tsx +138 -0
- package/stack-template/app/admin/src/pages/Dashboard.tsx +182 -0
- package/stack-template/app/admin/src/pages/HooksDemo.tsx +153 -0
- package/stack-template/app/admin/src/pages/Settings.tsx +156 -0
- package/stack-template/app/admin/src/pages/UploadPage.tsx +34 -0
- package/stack-template/app/admin/src/pages/Users.test.tsx +66 -0
- package/stack-template/app/admin/src/pages/Users.tsx +245 -0
- package/stack-template/app/admin/src/styles.css +9 -0
- package/stack-template/app/admin/src/tests/setup.ts +15 -0
- package/stack-template/app/admin/tsconfig.json +18 -0
- package/stack-template/app/admin/vite.config.ts +11 -0
- package/stack-template/app/admin/vitest.config.ts +14 -0
- package/stack-template/app/create-wakii-app/README.md +71 -0
- package/stack-template/app/create-wakii-app/package.json +27 -0
- package/stack-template/app/create-wakii-app/scripts/publish-npm.sh +38 -0
- package/stack-template/app/create-wakii-app/src/index.ts +127 -0
- package/stack-template/app/create-wakii-app/src/options.test.ts +80 -0
- package/stack-template/app/create-wakii-app/src/options.ts +117 -0
- package/stack-template/app/create-wakii-app/src/scaffold.test.ts +143 -0
- package/stack-template/app/create-wakii-app/src/scaffold.ts +194 -0
- package/stack-template/app/create-wakii-app/tsconfig.json +17 -0
- package/stack-template/app/create-wakii-app/vitest.config.ts +9 -0
- package/stack-template/app/create-wakii-stack/package.json +27 -0
- package/stack-template/app/create-wakii-stack/src/manifest.ts +101 -0
- package/stack-template/app/create-wakii-stack/src/options.ts +106 -0
- package/stack-template/app/create-wakii-stack/src/scaffold.ts +315 -0
- package/stack-template/app/create-wakii-stack/tsconfig.json +17 -0
- package/stack-template/app/create-wakii-stack/vitest.config.ts +9 -0
- package/stack-template/app/remotes/activity/Dockerfile +36 -0
- package/stack-template/app/remotes/activity/README.md +59 -0
- package/stack-template/app/remotes/activity/index.html +11 -0
- package/stack-template/app/remotes/activity/nginx.conf +35 -0
- package/stack-template/app/remotes/activity/package.json +40 -0
- package/stack-template/app/remotes/activity/rspack.config.mjs +152 -0
- package/stack-template/app/remotes/activity/src/ActivityPage.tsx +139 -0
- package/stack-template/app/remotes/activity/src/api.ts +63 -0
- package/stack-template/app/remotes/activity/src/app.css +7 -0
- package/stack-template/app/remotes/activity/src/components/FeedItem.tsx +39 -0
- package/stack-template/app/remotes/activity/src/components/StatTile.tsx +24 -0
- package/stack-template/app/remotes/activity/src/components/Timeline.tsx +53 -0
- package/stack-template/app/remotes/activity/src/components/time.ts +30 -0
- package/stack-template/app/remotes/activity/src/env.d.ts +5 -0
- package/stack-template/app/remotes/activity/src/index.tsx +12 -0
- package/stack-template/app/remotes/activity/src/standalone.tsx +36 -0
- package/stack-template/app/remotes/activity/tests/ActivityPage.test.tsx +260 -0
- package/stack-template/app/remotes/activity/tests/api.test.ts +61 -0
- package/stack-template/app/remotes/activity/tests/setup.ts +1 -0
- package/stack-template/app/remotes/activity/tests/test-utils.tsx +16 -0
- package/stack-template/app/remotes/activity/tsconfig.json +26 -0
- package/stack-template/app/remotes/activity/vitest.config.ts +38 -0
- package/stack-template/app/remotes/tasks/Dockerfile +36 -0
- package/stack-template/app/remotes/tasks/README.md +59 -0
- package/stack-template/app/remotes/tasks/index.html +11 -0
- package/stack-template/app/remotes/tasks/nginx.conf +35 -0
- package/stack-template/app/remotes/tasks/package.json +41 -0
- package/stack-template/app/remotes/tasks/rspack.config.mjs +153 -0
- package/stack-template/app/remotes/tasks/src/TasksPage.tsx +284 -0
- package/stack-template/app/remotes/tasks/src/api.ts +109 -0
- package/stack-template/app/remotes/tasks/src/app.css +7 -0
- package/stack-template/app/remotes/tasks/src/components/StatusBadge.tsx +20 -0
- package/stack-template/app/remotes/tasks/src/components/StatusSelect.tsx +27 -0
- package/stack-template/app/remotes/tasks/src/components/TaskForm.tsx +74 -0
- package/stack-template/app/remotes/tasks/src/components/TaskItem.tsx +199 -0
- package/stack-template/app/remotes/tasks/src/components/TaskList.tsx +60 -0
- package/stack-template/app/remotes/tasks/src/components/status.ts +23 -0
- package/stack-template/app/remotes/tasks/src/env.d.ts +5 -0
- package/stack-template/app/remotes/tasks/src/index.tsx +12 -0
- package/stack-template/app/remotes/tasks/src/standalone.tsx +38 -0
- package/stack-template/app/remotes/tasks/tests/TasksPage.test.tsx +445 -0
- package/stack-template/app/remotes/tasks/tests/api.test.ts +159 -0
- package/stack-template/app/remotes/tasks/tests/setup.ts +1 -0
- package/stack-template/app/remotes/tasks/tests/test-utils.tsx +16 -0
- package/stack-template/app/remotes/tasks/tsconfig.json +26 -0
- package/stack-template/app/remotes/tasks/vitest.config.ts +34 -0
- package/stack-template/app/shared/README.md +21 -0
- package/stack-template/app/shared/package.json +37 -0
- package/stack-template/app/shared/src/api-client.ts +66 -0
- package/stack-template/app/shared/src/error-envelope.ts +38 -0
- package/stack-template/app/shared/src/index.ts +21 -0
- package/stack-template/app/shared/src/mf-bootstrap.ts +89 -0
- package/stack-template/app/shared/src/refresh-client.ts +170 -0
- package/stack-template/app/shared/src/token-storage.ts +57 -0
- package/stack-template/app/shell/Dockerfile +37 -0
- package/stack-template/app/shell/README.md +79 -0
- package/stack-template/app/shell/index.html +11 -0
- package/stack-template/app/shell/nginx.conf +23 -0
- package/stack-template/app/shell/package.json +43 -0
- package/stack-template/app/shell/rspack.config.mjs +147 -0
- package/stack-template/app/shell/src/App.tsx +57 -0
- package/stack-template/app/shell/src/api.ts +36 -0
- package/stack-template/app/shell/src/app.css +11 -0
- package/stack-template/app/shell/src/bootstrap.tsx +24 -0
- package/stack-template/app/shell/src/components/Layout.tsx +35 -0
- package/stack-template/app/shell/src/components/NavBar.tsx +118 -0
- package/stack-template/app/shell/src/components/RemoteSlot.tsx +82 -0
- package/stack-template/app/shell/src/components/RequireAuth.tsx +14 -0
- package/stack-template/app/shell/src/env.d.ts +7 -0
- package/stack-template/app/shell/src/index.tsx +3 -0
- package/stack-template/app/shell/src/pages/HomePage.tsx +70 -0
- package/stack-template/app/shell/src/pages/LoginPage.tsx +217 -0
- package/stack-template/app/shell/src/remote-urls.ts +18 -0
- package/stack-template/app/shell/tests/a11y.test.tsx +53 -0
- package/stack-template/app/shell/tests/api-timeout.test.ts +43 -0
- package/stack-template/app/shell/tests/integration.test.tsx +93 -0
- package/stack-template/app/shell/tests/login.test.tsx +227 -0
- package/stack-template/app/shell/tests/mf-retry.test.ts +76 -0
- package/stack-template/app/shell/tests/refresh-client.test.ts +301 -0
- package/stack-template/app/shell/tests/remote-slot.test.tsx +109 -0
- package/stack-template/app/shell/tests/remote-urls.test.ts +26 -0
- package/stack-template/app/shell/tests/setup.ts +15 -0
- package/stack-template/app/shell/tests/shell-chrome.test.tsx +126 -0
- package/stack-template/app/shell/tsconfig.json +26 -0
- package/stack-template/app/shell/vitest.config.ts +42 -0
- package/stack-template/app/spike/README.md +35 -0
- package/stack-template/app/spike/host/index.html +11 -0
- package/stack-template/app/spike/host/package.json +17 -0
- package/stack-template/app/spike/host/rspack.config.mjs +34 -0
- package/stack-template/app/spike/host/src/bootstrap.jsx +26 -0
- package/stack-template/app/spike/host/src/index.jsx +3 -0
- package/stack-template/app/spike/remote/package.json +17 -0
- package/stack-template/app/spike/remote/rspack.config.mjs +36 -0
- package/stack-template/app/spike/remote/src/Hello.jsx +7 -0
- package/stack-template/app/spike/remote/src/index.js +2 -0
- package/stack-template/app/spike/serve.mjs +31 -0
- package/stack-template/app/starter-templates/template-feats/extras/carousel/src/DemoCarousel.tsx +99 -0
- package/stack-template/app/starter-templates/template-feats/extras/chart/src/DemoChart.tsx +84 -0
- package/stack-template/app/starter-templates/template-feats/extras/editor/src/DemoEditor.tsx +110 -0
- package/stack-template/app/starter-templates/template-feats/extras/schedule/src/DemoSchedule.tsx +81 -0
- package/stack-template/app/starter-templates/template-feats/router/src/App.tsx +332 -0
- package/stack-template/app/starter-templates/template-feats/router/src/main.tsx +14 -0
- package/stack-template/app/starter-templates/vite-js/README.md +72 -0
- package/stack-template/app/starter-templates/vite-js/index.html +12 -0
- package/stack-template/app/starter-templates/vite-js/jsconfig.json +10 -0
- package/stack-template/app/starter-templates/vite-js/package.json +22 -0
- package/stack-template/app/starter-templates/vite-js/src/App.jsx +158 -0
- package/stack-template/app/starter-templates/vite-js/src/main.jsx +10 -0
- package/stack-template/app/starter-templates/vite-js/src/styles.css +15 -0
- package/stack-template/app/starter-templates/vite-js/src/theme.js +21 -0
- package/stack-template/app/starter-templates/vite-js/vite.config.mjs +8 -0
- package/stack-template/app/starter-templates/vite-ts/README.md +53 -0
- package/stack-template/app/starter-templates/vite-ts/index.html +12 -0
- package/stack-template/app/starter-templates/vite-ts/package.json +29 -0
- package/stack-template/app/starter-templates/vite-ts/src/App.tsx +181 -0
- package/stack-template/app/starter-templates/vite-ts/src/main.tsx +12 -0
- package/stack-template/app/starter-templates/vite-ts/src/styles.css +21 -0
- package/stack-template/app/starter-templates/vite-ts/src/theme.ts +16 -0
- package/stack-template/app/starter-templates/vite-ts/tsconfig.json +25 -0
- package/stack-template/app/starter-templates/vite-ts/tsconfig.node.json +11 -0
- package/stack-template/app/starter-templates/vite-ts/vite.config.ts +10 -0
- package/stack-template/app/ui-kit/.storybook/main.ts +29 -0
- package/stack-template/app/ui-kit/.storybook/manager-head.html +97 -0
- package/stack-template/app/ui-kit/.storybook/manager.ts +138 -0
- package/stack-template/app/ui-kit/.storybook/preview-head.html +57 -0
- package/stack-template/app/ui-kit/.storybook/preview.ts +62 -0
- package/stack-template/app/ui-kit/.storybook/tailwind-preview.css +3 -0
- package/stack-template/app/ui-kit/.storybook/test-runner.config.ts +20 -0
- package/stack-template/app/ui-kit/CHANGELOG.md +137 -0
- package/stack-template/app/ui-kit/LICENSE +21 -0
- package/stack-template/app/ui-kit/MIGRATION.md +84 -0
- package/stack-template/app/ui-kit/README.md +173 -0
- package/stack-template/app/ui-kit/RELEASE.md +77 -0
- package/stack-template/app/ui-kit/USAGE.md +380 -0
- package/stack-template/app/ui-kit/eslint.config.js +47 -0
- package/stack-template/app/ui-kit/package.json +166 -0
- package/stack-template/app/ui-kit/scripts/publish-github.sh +39 -0
- package/stack-template/app/ui-kit/scripts/publish-npm.sh +76 -0
- package/stack-template/app/ui-kit/scripts/stamp-build.cjs +13 -0
- package/stack-template/app/ui-kit/src/KitchenSink.stories.tsx +576 -0
- package/stack-template/app/ui-kit/src/Tokens.stories.tsx +193 -0
- package/stack-template/app/ui-kit/src/entries/carousel.ts +7 -0
- package/stack-template/app/ui-kit/src/entries/chart.ts +4 -0
- package/stack-template/app/ui-kit/src/entries/editor.ts +4 -0
- package/stack-template/app/ui-kit/src/entries/experience3d.ts +5 -0
- package/stack-template/app/ui-kit/src/entries/schedule.ts +5 -0
- package/stack-template/app/ui-kit/src/env.d.ts +3 -0
- package/stack-template/app/ui-kit/src/hooks/form.stories.tsx +80 -0
- package/stack-template/app/ui-kit/src/hooks/hooks-new.stories.tsx +392 -0
- package/stack-template/app/ui-kit/src/hooks/hooks.stories.tsx +167 -0
- package/stack-template/app/ui-kit/src/hooks/useClickAway.test.tsx +63 -0
- package/stack-template/app/ui-kit/src/hooks/useClickAway.ts +30 -0
- package/stack-template/app/ui-kit/src/hooks/useControllableState.test.ts +36 -0
- package/stack-template/app/ui-kit/src/hooks/useControllableState.ts +41 -0
- package/stack-template/app/ui-kit/src/hooks/useCopyToClipboard.test.ts +89 -0
- package/stack-template/app/ui-kit/src/hooks/useCopyToClipboard.ts +20 -0
- package/stack-template/app/ui-kit/src/hooks/useCountdown.test.ts +88 -0
- package/stack-template/app/ui-kit/src/hooks/useCountdown.ts +38 -0
- package/stack-template/app/ui-kit/src/hooks/useDebounce.test.ts +71 -0
- package/stack-template/app/ui-kit/src/hooks/useDebounce.ts +14 -0
- package/stack-template/app/ui-kit/src/hooks/useDefault.test.ts +37 -0
- package/stack-template/app/ui-kit/src/hooks/useDefault.ts +15 -0
- package/stack-template/app/ui-kit/src/hooks/useEventListener.test.ts +47 -0
- package/stack-template/app/ui-kit/src/hooks/useEventListener.ts +28 -0
- package/stack-template/app/ui-kit/src/hooks/useFieldArray.test.tsx +160 -0
- package/stack-template/app/ui-kit/src/hooks/useFieldArray.ts +85 -0
- package/stack-template/app/ui-kit/src/hooks/useFocusTrap.ts +67 -0
- package/stack-template/app/ui-kit/src/hooks/useForm.test.tsx +192 -0
- package/stack-template/app/ui-kit/src/hooks/useForm.ts +119 -0
- package/stack-template/app/ui-kit/src/hooks/useFullscreen.test.tsx +71 -0
- package/stack-template/app/ui-kit/src/hooks/useFullscreen.ts +68 -0
- package/stack-template/app/ui-kit/src/hooks/useHover.test.tsx +30 -0
- package/stack-template/app/ui-kit/src/hooks/useHover.ts +23 -0
- package/stack-template/app/ui-kit/src/hooks/useInView.test.tsx +70 -0
- package/stack-template/app/ui-kit/src/hooks/useInView.ts +55 -0
- package/stack-template/app/ui-kit/src/hooks/useInfiniteScroll.test.tsx +72 -0
- package/stack-template/app/ui-kit/src/hooks/useInfiniteScroll.ts +45 -0
- package/stack-template/app/ui-kit/src/hooks/useIsFirstRender.test.ts +30 -0
- package/stack-template/app/ui-kit/src/hooks/useIsFirstRender.ts +14 -0
- package/stack-template/app/ui-kit/src/hooks/useKeyPress.test.ts +45 -0
- package/stack-template/app/ui-kit/src/hooks/useKeyPress.ts +31 -0
- package/stack-template/app/ui-kit/src/hooks/useLocalStorage.test.ts +102 -0
- package/stack-template/app/ui-kit/src/hooks/useLocalStorage.ts +34 -0
- package/stack-template/app/ui-kit/src/hooks/useLockBodyScroll.test.ts +42 -0
- package/stack-template/app/ui-kit/src/hooks/useLockBodyScroll.ts +13 -0
- package/stack-template/app/ui-kit/src/hooks/useMeasure.test.tsx +57 -0
- package/stack-template/app/ui-kit/src/hooks/useMeasure.ts +32 -0
- package/stack-template/app/ui-kit/src/hooks/useMediaQuery.test.ts +73 -0
- package/stack-template/app/ui-kit/src/hooks/useMediaQuery.ts +20 -0
- package/stack-template/app/ui-kit/src/hooks/useMountEffect.test.ts +22 -0
- package/stack-template/app/ui-kit/src/hooks/useMountEffect.ts +10 -0
- package/stack-template/app/ui-kit/src/hooks/useOnlineStatus.test.ts +36 -0
- package/stack-template/app/ui-kit/src/hooks/useOnlineStatus.ts +21 -0
- package/stack-template/app/ui-kit/src/hooks/usePrevious.test.ts +35 -0
- package/stack-template/app/ui-kit/src/hooks/usePrevious.ts +13 -0
- package/stack-template/app/ui-kit/src/hooks/useScrollProgress.test.ts +59 -0
- package/stack-template/app/ui-kit/src/hooks/useScrollProgress.ts +31 -0
- package/stack-template/app/ui-kit/src/hooks/useSessionStorage.test.ts +80 -0
- package/stack-template/app/ui-kit/src/hooks/useSessionStorage.ts +34 -0
- package/stack-template/app/ui-kit/src/hooks/useThrottle.test.ts +61 -0
- package/stack-template/app/ui-kit/src/hooks/useThrottle.ts +30 -0
- package/stack-template/app/ui-kit/src/hooks/useTimeout.test.ts +95 -0
- package/stack-template/app/ui-kit/src/hooks/useTimeout.ts +38 -0
- package/stack-template/app/ui-kit/src/hooks/useTitle.test.ts +34 -0
- package/stack-template/app/ui-kit/src/hooks/useTitle.ts +24 -0
- package/stack-template/app/ui-kit/src/hooks/useToggle.test.ts +46 -0
- package/stack-template/app/ui-kit/src/hooks/useToggle.ts +16 -0
- package/stack-template/app/ui-kit/src/hooks/useUpdateEffect.test.ts +25 -0
- package/stack-template/app/ui-kit/src/hooks/useUpdateEffect.ts +18 -0
- package/stack-template/app/ui-kit/src/hooks/useWindowSize.test.ts +33 -0
- package/stack-template/app/ui-kit/src/hooks/useWindowSize.ts +23 -0
- package/stack-template/app/ui-kit/src/index.ts +218 -0
- package/stack-template/app/ui-kit/src/intro-showcase.stories.tsx +272 -0
- package/stack-template/app/ui-kit/src/intro.docs.mdx +247 -0
- package/stack-template/app/ui-kit/src/motion/motion-showcase.stories.tsx +234 -0
- package/stack-template/app/ui-kit/src/motion/motion.md +38 -0
- package/stack-template/app/ui-kit/src/provider/index.tsx +77 -0
- package/stack-template/app/ui-kit/src/provider/provider.stories.tsx +87 -0
- package/stack-template/app/ui-kit/src/provider/provider.test.tsx +121 -0
- package/stack-template/app/ui-kit/src/styles/base.css +171 -0
- package/stack-template/app/ui-kit/src/styles/dark-theme.test.ts +21 -0
- package/stack-template/app/ui-kit/src/styles/tailwind.css +220 -0
- package/stack-template/app/ui-kit/src/styles/themes/dark.css +71 -0
- package/stack-template/app/ui-kit/src/styles/tokens.css +133 -0
- package/stack-template/app/ui-kit/src/styles/ui-kit.css +10 -0
- package/stack-template/app/ui-kit/src/templates/auth.stories.tsx +425 -0
- package/stack-template/app/ui-kit/src/templates/crud.stories.tsx +416 -0
- package/stack-template/app/ui-kit/src/templates/dashboard.stories.tsx +504 -0
- package/stack-template/app/ui-kit/src/templates/experience/chapters/ch1-deep-focus.tsx +96 -0
- package/stack-template/app/ui-kit/src/templates/experience/chapters/ch2-living-docs.tsx +134 -0
- package/stack-template/app/ui-kit/src/templates/experience/chapters/ch3-team-velocity.tsx +148 -0
- package/stack-template/app/ui-kit/src/templates/experience/chapters/ch4-ship-your-app.tsx +100 -0
- package/stack-template/app/ui-kit/src/templates/experience/chapters/ch5-future.tsx +67 -0
- package/stack-template/app/ui-kit/src/templates/experience/chapters/hero.tsx +434 -0
- package/stack-template/app/ui-kit/src/templates/experience/chapters/nav-footer.tsx +85 -0
- package/stack-template/app/ui-kit/src/templates/experience/hero.stories.tsx +27 -0
- package/stack-template/app/ui-kit/src/templates/experience/hero.test.tsx +108 -0
- package/stack-template/app/ui-kit/src/templates/experience/index.stories.tsx +51 -0
- package/stack-template/app/ui-kit/src/templates/experience/lib/ChapterShell.tsx +84 -0
- package/stack-template/app/ui-kit/src/templates/experience/lib/SideNav.tsx +173 -0
- package/stack-template/app/ui-kit/src/templates/experience/lib/chapter-shell.test.tsx +266 -0
- package/stack-template/app/ui-kit/src/templates/experience/lib/scroll-mood.tsx +143 -0
- package/stack-template/app/ui-kit/src/templates/experience/lib/starfield.test.tsx +31 -0
- package/stack-template/app/ui-kit/src/templates/experience/lib/starfield.tsx +190 -0
- package/stack-template/app/ui-kit/src/templates/experience/mood-engine.stories.tsx +114 -0
- package/stack-template/app/ui-kit/src/templates/experience/shell-nav.stories.tsx +113 -0
- package/stack-template/app/ui-kit/src/templates/kanban-project.stories.tsx +352 -0
- package/stack-template/app/ui-kit/src/templates/landing.stories.tsx +380 -0
- package/stack-template/app/ui-kit/src/templates/parallax-landing.stories.tsx +633 -0
- package/stack-template/app/ui-kit/src/templates/settings.stories.tsx +474 -0
- package/stack-template/app/ui-kit/src/ui/accordion/accordion.stories.tsx +91 -0
- package/stack-template/app/ui-kit/src/ui/accordion/accordion.test.tsx +99 -0
- package/stack-template/app/ui-kit/src/ui/accordion/index.tsx +107 -0
- package/stack-template/app/ui-kit/src/ui/affix/affix.stories.tsx +104 -0
- package/stack-template/app/ui-kit/src/ui/affix/affix.test.tsx +138 -0
- package/stack-template/app/ui-kit/src/ui/affix/index.tsx +89 -0
- package/stack-template/app/ui-kit/src/ui/alert/alert.stories.tsx +149 -0
- package/stack-template/app/ui-kit/src/ui/alert/alert.test.tsx +85 -0
- package/stack-template/app/ui-kit/src/ui/alert/index.tsx +118 -0
- package/stack-template/app/ui-kit/src/ui/anchor/anchor.stories.tsx +96 -0
- package/stack-template/app/ui-kit/src/ui/anchor/anchor.test.tsx +191 -0
- package/stack-template/app/ui-kit/src/ui/anchor/index.tsx +144 -0
- package/stack-template/app/ui-kit/src/ui/auto-complete/auto-complete.stories.tsx +67 -0
- package/stack-template/app/ui-kit/src/ui/auto-complete/auto-complete.test.tsx +94 -0
- package/stack-template/app/ui-kit/src/ui/auto-complete/index.tsx +54 -0
- package/stack-template/app/ui-kit/src/ui/avatar/avatar.stories.tsx +132 -0
- package/stack-template/app/ui-kit/src/ui/avatar/avatar.test.tsx +65 -0
- package/stack-template/app/ui-kit/src/ui/avatar/index.tsx +48 -0
- package/stack-template/app/ui-kit/src/ui/avatar-group/avatar-group.stories.tsx +113 -0
- package/stack-template/app/ui-kit/src/ui/avatar-group/avatar-group.test.tsx +71 -0
- package/stack-template/app/ui-kit/src/ui/avatar-group/index.tsx +66 -0
- package/stack-template/app/ui-kit/src/ui/backdrop/backdrop.stories.tsx +66 -0
- package/stack-template/app/ui-kit/src/ui/backdrop/backdrop.test.tsx +37 -0
- package/stack-template/app/ui-kit/src/ui/backdrop/index.tsx +29 -0
- package/stack-template/app/ui-kit/src/ui/badge/badge.stories.tsx +105 -0
- package/stack-template/app/ui-kit/src/ui/badge/badge.test.tsx +115 -0
- package/stack-template/app/ui-kit/src/ui/badge/index.tsx +154 -0
- package/stack-template/app/ui-kit/src/ui/blockui/blockui.stories.tsx +34 -0
- package/stack-template/app/ui-kit/src/ui/blockui/blockui.test.tsx +29 -0
- package/stack-template/app/ui-kit/src/ui/blockui/index.tsx +25 -0
- package/stack-template/app/ui-kit/src/ui/bottom-navigation/bottom-navigation.stories.tsx +70 -0
- package/stack-template/app/ui-kit/src/ui/bottom-navigation/bottom-navigation.test.tsx +68 -0
- package/stack-template/app/ui-kit/src/ui/bottom-navigation/index.tsx +82 -0
- package/stack-template/app/ui-kit/src/ui/branch-fill.test.tsx +245 -0
- package/stack-template/app/ui-kit/src/ui/breadcrumb/breadcrumb.stories.tsx +103 -0
- package/stack-template/app/ui-kit/src/ui/breadcrumb/breadcrumb.test.tsx +49 -0
- package/stack-template/app/ui-kit/src/ui/breadcrumb/index.tsx +37 -0
- package/stack-template/app/ui-kit/src/ui/busy-box/busy-box.stories.tsx +30 -0
- package/stack-template/app/ui-kit/src/ui/busy-box/busy-box.test.tsx +29 -0
- package/stack-template/app/ui-kit/src/ui/busy-box/index.tsx +25 -0
- package/stack-template/app/ui-kit/src/ui/button/button.stories.tsx +121 -0
- package/stack-template/app/ui-kit/src/ui/button/button.test.tsx +207 -0
- package/stack-template/app/ui-kit/src/ui/button/index.tsx +128 -0
- package/stack-template/app/ui-kit/src/ui/calendar/calendar.stories.tsx +100 -0
- package/stack-template/app/ui-kit/src/ui/calendar/calendar.test.tsx +92 -0
- package/stack-template/app/ui-kit/src/ui/calendar/index.tsx +163 -0
- package/stack-template/app/ui-kit/src/ui/card/card.stories.tsx +203 -0
- package/stack-template/app/ui-kit/src/ui/card/card.test.tsx +135 -0
- package/stack-template/app/ui-kit/src/ui/card/index.tsx +84 -0
- package/stack-template/app/ui-kit/src/ui/carousel/carousel.stories.tsx +533 -0
- package/stack-template/app/ui-kit/src/ui/carousel/carousel.test.tsx +148 -0
- package/stack-template/app/ui-kit/src/ui/carousel/index.tsx +195 -0
- package/stack-template/app/ui-kit/src/ui/cascade-select/cascade-select.stories.tsx +126 -0
- package/stack-template/app/ui-kit/src/ui/cascade-select/cascade-select.test.tsx +132 -0
- package/stack-template/app/ui-kit/src/ui/cascade-select/index.tsx +87 -0
- package/stack-template/app/ui-kit/src/ui/chart/chart-theme.test.tsx +79 -0
- package/stack-template/app/ui-kit/src/ui/chart/chart.stories.tsx +278 -0
- package/stack-template/app/ui-kit/src/ui/chart/chart.test.tsx +103 -0
- package/stack-template/app/ui-kit/src/ui/chart/index.tsx +172 -0
- package/stack-template/app/ui-kit/src/ui/checkbox/checkbox.stories.tsx +67 -0
- package/stack-template/app/ui-kit/src/ui/checkbox/checkbox.test.tsx +99 -0
- package/stack-template/app/ui-kit/src/ui/checkbox/index.tsx +52 -0
- package/stack-template/app/ui-kit/src/ui/chips/chips.stories.tsx +53 -0
- package/stack-template/app/ui-kit/src/ui/chips/chips.test.tsx +86 -0
- package/stack-template/app/ui-kit/src/ui/chips/index.tsx +44 -0
- package/stack-template/app/ui-kit/src/ui/collapse/collapse.stories.tsx +93 -0
- package/stack-template/app/ui-kit/src/ui/collapse/collapse.test.tsx +128 -0
- package/stack-template/app/ui-kit/src/ui/collapse/index.tsx +75 -0
- package/stack-template/app/ui-kit/src/ui/color-picker/color-picker.stories.tsx +48 -0
- package/stack-template/app/ui-kit/src/ui/color-picker/color-picker.test.tsx +68 -0
- package/stack-template/app/ui-kit/src/ui/color-picker/index.tsx +38 -0
- package/stack-template/app/ui-kit/src/ui/context-menu/context-menu.stories.tsx +73 -0
- package/stack-template/app/ui-kit/src/ui/context-menu/context-menu.test.tsx +93 -0
- package/stack-template/app/ui-kit/src/ui/context-menu/index.tsx +79 -0
- package/stack-template/app/ui-kit/src/ui/count-up/count-up.stories.tsx +80 -0
- package/stack-template/app/ui-kit/src/ui/count-up/count-up.test.tsx +121 -0
- package/stack-template/app/ui-kit/src/ui/count-up/index.tsx +70 -0
- package/stack-template/app/ui-kit/src/ui/date-picker/date-picker.stories.tsx +154 -0
- package/stack-template/app/ui-kit/src/ui/date-picker/date-picker.test.tsx +221 -0
- package/stack-template/app/ui-kit/src/ui/date-picker/index.tsx +287 -0
- package/stack-template/app/ui-kit/src/ui/date-picker/locale-constants.ts +90 -0
- package/stack-template/app/ui-kit/src/ui/date-picker/locale.tsx +15 -0
- package/stack-template/app/ui-kit/src/ui/deferred-content/deferred-content.stories.tsx +62 -0
- package/stack-template/app/ui-kit/src/ui/deferred-content/deferred-content.test.tsx +94 -0
- package/stack-template/app/ui-kit/src/ui/deferred-content/index.tsx +61 -0
- package/stack-template/app/ui-kit/src/ui/descriptions/descriptions.stories.tsx +114 -0
- package/stack-template/app/ui-kit/src/ui/descriptions/descriptions.test.tsx +75 -0
- package/stack-template/app/ui-kit/src/ui/descriptions/index.tsx +48 -0
- package/stack-template/app/ui-kit/src/ui/divider/divider.stories.tsx +66 -0
- package/stack-template/app/ui-kit/src/ui/divider/divider.test.tsx +83 -0
- package/stack-template/app/ui-kit/src/ui/divider/index.tsx +65 -0
- package/stack-template/app/ui-kit/src/ui/drag-drop/drag-drop.stories.tsx +145 -0
- package/stack-template/app/ui-kit/src/ui/drag-drop/drag-drop.test.tsx +352 -0
- package/stack-template/app/ui-kit/src/ui/drag-drop/index.tsx +456 -0
- package/stack-template/app/ui-kit/src/ui/drawer/drawer.stories.tsx +108 -0
- package/stack-template/app/ui-kit/src/ui/drawer/drawer.test.tsx +132 -0
- package/stack-template/app/ui-kit/src/ui/drawer/index.tsx +113 -0
- package/stack-template/app/ui-kit/src/ui/dropdown/dropdown.stories.tsx +61 -0
- package/stack-template/app/ui-kit/src/ui/dropdown/dropdown.test.tsx +101 -0
- package/stack-template/app/ui-kit/src/ui/dropdown/index.tsx +162 -0
- package/stack-template/app/ui-kit/src/ui/editor/EditorInner.tsx +93 -0
- package/stack-template/app/ui-kit/src/ui/editor/editor.stories.tsx +37 -0
- package/stack-template/app/ui-kit/src/ui/editor/editor.test.tsx +84 -0
- package/stack-template/app/ui-kit/src/ui/editor/index.tsx +58 -0
- package/stack-template/app/ui-kit/src/ui/empty-state/empty-state.stories.tsx +24 -0
- package/stack-template/app/ui-kit/src/ui/empty-state/empty-state.test.tsx +48 -0
- package/stack-template/app/ui-kit/src/ui/empty-state/index.tsx +22 -0
- package/stack-template/app/ui-kit/src/ui/experience3d/experience3d.stories.tsx +66 -0
- package/stack-template/app/ui-kit/src/ui/experience3d/experience3d.test.tsx +50 -0
- package/stack-template/app/ui-kit/src/ui/experience3d/index.tsx +457 -0
- package/stack-template/app/ui-kit/src/ui/field/field-array.stories.tsx +214 -0
- package/stack-template/app/ui-kit/src/ui/field/field.stories.tsx +158 -0
- package/stack-template/app/ui-kit/src/ui/field/field.test.tsx +68 -0
- package/stack-template/app/ui-kit/src/ui/field/index.tsx +48 -0
- package/stack-template/app/ui-kit/src/ui/fieldset/fieldset.stories.tsx +49 -0
- package/stack-template/app/ui-kit/src/ui/fieldset/fieldset.test.tsx +88 -0
- package/stack-template/app/ui-kit/src/ui/fieldset/index.tsx +62 -0
- package/stack-template/app/ui-kit/src/ui/flex/flex.stories.tsx +96 -0
- package/stack-template/app/ui-kit/src/ui/flex/flex.test.tsx +79 -0
- package/stack-template/app/ui-kit/src/ui/flex/index.tsx +45 -0
- package/stack-template/app/ui-kit/src/ui/float-button/float-button.stories.tsx +82 -0
- package/stack-template/app/ui-kit/src/ui/float-button/float-button.test.tsx +107 -0
- package/stack-template/app/ui-kit/src/ui/float-button/index.tsx +124 -0
- package/stack-template/app/ui-kit/src/ui/galleria/galleria.stories.tsx +78 -0
- package/stack-template/app/ui-kit/src/ui/galleria/galleria.test.tsx +67 -0
- package/stack-template/app/ui-kit/src/ui/galleria/index.tsx +47 -0
- package/stack-template/app/ui-kit/src/ui/grid/grid.stories.tsx +179 -0
- package/stack-template/app/ui-kit/src/ui/grid/grid.test.tsx +286 -0
- package/stack-template/app/ui-kit/src/ui/grid/index.tsx +104 -0
- package/stack-template/app/ui-kit/src/ui/icons/icons.stories.tsx +52 -0
- package/stack-template/app/ui-kit/src/ui/icons/icons.test.tsx +60 -0
- package/stack-template/app/ui-kit/src/ui/icons/index.tsx +294 -0
- package/stack-template/app/ui-kit/src/ui/image/image.stories.tsx +231 -0
- package/stack-template/app/ui-kit/src/ui/image/image.test.tsx +79 -0
- package/stack-template/app/ui-kit/src/ui/image/index.tsx +76 -0
- package/stack-template/app/ui-kit/src/ui/inplace/index.tsx +33 -0
- package/stack-template/app/ui-kit/src/ui/inplace/inplace.stories.tsx +75 -0
- package/stack-template/app/ui-kit/src/ui/inplace/inplace.test.tsx +58 -0
- package/stack-template/app/ui-kit/src/ui/input/index.tsx +133 -0
- package/stack-template/app/ui-kit/src/ui/input/input-search.stories.tsx +112 -0
- package/stack-template/app/ui-kit/src/ui/input/input-search.test.tsx +116 -0
- package/stack-template/app/ui-kit/src/ui/input/input-search.tsx +116 -0
- package/stack-template/app/ui-kit/src/ui/input/input.stories.tsx +176 -0
- package/stack-template/app/ui-kit/src/ui/input/input.test.tsx +106 -0
- package/stack-template/app/ui-kit/src/ui/input-mask/index.tsx +39 -0
- package/stack-template/app/ui-kit/src/ui/input-mask/input-mask.stories.tsx +46 -0
- package/stack-template/app/ui-kit/src/ui/input-mask/input-mask.test.tsx +57 -0
- package/stack-template/app/ui-kit/src/ui/input-number/index.tsx +125 -0
- package/stack-template/app/ui-kit/src/ui/input-number/input-number.stories.tsx +190 -0
- package/stack-template/app/ui-kit/src/ui/input-number/input-number.test.tsx +157 -0
- package/stack-template/app/ui-kit/src/ui/input-otp/index.tsx +57 -0
- package/stack-template/app/ui-kit/src/ui/input-otp/input-otp.stories.tsx +90 -0
- package/stack-template/app/ui-kit/src/ui/input-otp/input-otp.test.tsx +66 -0
- package/stack-template/app/ui-kit/src/ui/kanban/index.tsx +277 -0
- package/stack-template/app/ui-kit/src/ui/kanban/kanban.stories.tsx +219 -0
- package/stack-template/app/ui-kit/src/ui/kanban/kanban.test.tsx +234 -0
- package/stack-template/app/ui-kit/src/ui/knob/index.tsx +58 -0
- package/stack-template/app/ui-kit/src/ui/knob/knob.stories.tsx +49 -0
- package/stack-template/app/ui-kit/src/ui/knob/knob.test.tsx +42 -0
- package/stack-template/app/ui-kit/src/ui/layout/index.tsx +145 -0
- package/stack-template/app/ui-kit/src/ui/layout/layout.stories.tsx +158 -0
- package/stack-template/app/ui-kit/src/ui/layout/layout.test.tsx +154 -0
- package/stack-template/app/ui-kit/src/ui/link/index.tsx +61 -0
- package/stack-template/app/ui-kit/src/ui/link/link.stories.tsx +68 -0
- package/stack-template/app/ui-kit/src/ui/link/link.test.tsx +69 -0
- package/stack-template/app/ui-kit/src/ui/list/index.tsx +135 -0
- package/stack-template/app/ui-kit/src/ui/list/list.stories.tsx +194 -0
- package/stack-template/app/ui-kit/src/ui/list/list.test.tsx +164 -0
- package/stack-template/app/ui-kit/src/ui/listbox/index.tsx +27 -0
- package/stack-template/app/ui-kit/src/ui/listbox/listbox.stories.tsx +57 -0
- package/stack-template/app/ui-kit/src/ui/listbox/listbox.test.tsx +49 -0
- package/stack-template/app/ui-kit/src/ui/mega-menu/index.tsx +142 -0
- package/stack-template/app/ui-kit/src/ui/mega-menu/mega-menu.stories.tsx +137 -0
- package/stack-template/app/ui-kit/src/ui/mega-menu/mega-menu.test.tsx +130 -0
- package/stack-template/app/ui-kit/src/ui/mention/index.tsx +76 -0
- package/stack-template/app/ui-kit/src/ui/mention/mention.stories.tsx +44 -0
- package/stack-template/app/ui-kit/src/ui/mention/mention.test.tsx +87 -0
- package/stack-template/app/ui-kit/src/ui/menu/index.tsx +256 -0
- package/stack-template/app/ui-kit/src/ui/menu/menu.stories.tsx +124 -0
- package/stack-template/app/ui-kit/src/ui/menu/menu.test.tsx +251 -0
- package/stack-template/app/ui-kit/src/ui/menubar/index.tsx +42 -0
- package/stack-template/app/ui-kit/src/ui/menubar/menubar.stories.tsx +66 -0
- package/stack-template/app/ui-kit/src/ui/menubar/menubar.test.tsx +65 -0
- package/stack-template/app/ui-kit/src/ui/message/index.tsx +123 -0
- package/stack-template/app/ui-kit/src/ui/message/message.stories.tsx +104 -0
- package/stack-template/app/ui-kit/src/ui/message/message.test.tsx +109 -0
- package/stack-template/app/ui-kit/src/ui/modal/index.tsx +142 -0
- package/stack-template/app/ui-kit/src/ui/modal/modal.stories.tsx +128 -0
- package/stack-template/app/ui-kit/src/ui/modal/modal.test.tsx +166 -0
- package/stack-template/app/ui-kit/src/ui/notification/index.tsx +142 -0
- package/stack-template/app/ui-kit/src/ui/notification/notification.stories.tsx +155 -0
- package/stack-template/app/ui-kit/src/ui/notification/notification.test.tsx +83 -0
- package/stack-template/app/ui-kit/src/ui/order-list/index.tsx +38 -0
- package/stack-template/app/ui-kit/src/ui/order-list/order-list.stories.tsx +38 -0
- package/stack-template/app/ui-kit/src/ui/order-list/order-list.test.tsx +54 -0
- package/stack-template/app/ui-kit/src/ui/org-chart/index.tsx +138 -0
- package/stack-template/app/ui-kit/src/ui/org-chart/org-chart.stories.tsx +76 -0
- package/stack-template/app/ui-kit/src/ui/org-chart/org-chart.test.tsx +98 -0
- package/stack-template/app/ui-kit/src/ui/pagination/index.tsx +102 -0
- package/stack-template/app/ui-kit/src/ui/pagination/pagination.stories.tsx +103 -0
- package/stack-template/app/ui-kit/src/ui/pagination/pagination.test.tsx +112 -0
- package/stack-template/app/ui-kit/src/ui/panel-menu/index.tsx +59 -0
- package/stack-template/app/ui-kit/src/ui/panel-menu/panel-menu.stories.tsx +96 -0
- package/stack-template/app/ui-kit/src/ui/panel-menu/panel-menu.test.tsx +64 -0
- package/stack-template/app/ui-kit/src/ui/parallax/index.tsx +62 -0
- package/stack-template/app/ui-kit/src/ui/parallax/parallax.stories.tsx +115 -0
- package/stack-template/app/ui-kit/src/ui/parallax/parallax.test.tsx +148 -0
- package/stack-template/app/ui-kit/src/ui/password/index.tsx +67 -0
- package/stack-template/app/ui-kit/src/ui/password/password.stories.tsx +43 -0
- package/stack-template/app/ui-kit/src/ui/password/password.test.tsx +45 -0
- package/stack-template/app/ui-kit/src/ui/pick-list/index.tsx +103 -0
- package/stack-template/app/ui-kit/src/ui/pick-list/pick-list.stories.tsx +59 -0
- package/stack-template/app/ui-kit/src/ui/pick-list/pick-list.test.tsx +94 -0
- package/stack-template/app/ui-kit/src/ui/popconfirm/index.tsx +102 -0
- package/stack-template/app/ui-kit/src/ui/popconfirm/popconfirm.stories.tsx +73 -0
- package/stack-template/app/ui-kit/src/ui/popconfirm/popconfirm.test.tsx +127 -0
- package/stack-template/app/ui-kit/src/ui/popover/index.tsx +136 -0
- package/stack-template/app/ui-kit/src/ui/popover/popover.stories.tsx +114 -0
- package/stack-template/app/ui-kit/src/ui/popover/popover.test.tsx +172 -0
- package/stack-template/app/ui-kit/src/ui/progress/index.tsx +46 -0
- package/stack-template/app/ui-kit/src/ui/progress/progress.stories.tsx +57 -0
- package/stack-template/app/ui-kit/src/ui/progress/progress.test.tsx +90 -0
- package/stack-template/app/ui-kit/src/ui/qr-code/index.tsx +88 -0
- package/stack-template/app/ui-kit/src/ui/qr-code/qr-code.stories.tsx +109 -0
- package/stack-template/app/ui-kit/src/ui/qr-code/qr-code.test.tsx +155 -0
- package/stack-template/app/ui-kit/src/ui/qr-code/qrcode-core.ts +56 -0
- package/stack-template/app/ui-kit/src/ui/qr-code/qrcode-generator.vendor.ts +2300 -0
- package/stack-template/app/ui-kit/src/ui/radio/index.tsx +62 -0
- package/stack-template/app/ui-kit/src/ui/radio/radio.stories.tsx +94 -0
- package/stack-template/app/ui-kit/src/ui/radio/radio.test.tsx +83 -0
- package/stack-template/app/ui-kit/src/ui/rating/index.tsx +36 -0
- package/stack-template/app/ui-kit/src/ui/rating/rating.stories.tsx +42 -0
- package/stack-template/app/ui-kit/src/ui/rating/rating.test.tsx +45 -0
- package/stack-template/app/ui-kit/src/ui/result/index.tsx +72 -0
- package/stack-template/app/ui-kit/src/ui/result/result.stories.tsx +82 -0
- package/stack-template/app/ui-kit/src/ui/result/result.test.tsx +57 -0
- package/stack-template/app/ui-kit/src/ui/reveal/index.tsx +83 -0
- package/stack-template/app/ui-kit/src/ui/reveal/reveal.stories.tsx +91 -0
- package/stack-template/app/ui-kit/src/ui/reveal/reveal.test.tsx +148 -0
- package/stack-template/app/ui-kit/src/ui/schedule/index.tsx +104 -0
- package/stack-template/app/ui-kit/src/ui/schedule/schedule.fc.test.tsx +95 -0
- package/stack-template/app/ui-kit/src/ui/schedule/schedule.stories.tsx +61 -0
- package/stack-template/app/ui-kit/src/ui/schedule/schedule.test.tsx +41 -0
- package/stack-template/app/ui-kit/src/ui/scroll-page/index.tsx +46 -0
- package/stack-template/app/ui-kit/src/ui/scroll-page/scroll-page.stories.tsx +92 -0
- package/stack-template/app/ui-kit/src/ui/scroll-page/scroll-page.test.tsx +51 -0
- package/stack-template/app/ui-kit/src/ui/scroll-panel/index.tsx +16 -0
- package/stack-template/app/ui-kit/src/ui/scroll-panel/scroll-panel.stories.tsx +37 -0
- package/stack-template/app/ui-kit/src/ui/scroll-panel/scroll-panel.test.tsx +41 -0
- package/stack-template/app/ui-kit/src/ui/scrolltop/index.tsx +27 -0
- package/stack-template/app/ui-kit/src/ui/scrolltop/scrolltop.stories.tsx +41 -0
- package/stack-template/app/ui-kit/src/ui/scrolltop/scrolltop.test.tsx +37 -0
- package/stack-template/app/ui-kit/src/ui/segmented/index.tsx +47 -0
- package/stack-template/app/ui-kit/src/ui/segmented/segmented.stories.tsx +126 -0
- package/stack-template/app/ui-kit/src/ui/segmented/segmented.test.tsx +77 -0
- package/stack-template/app/ui-kit/src/ui/select/index.tsx +302 -0
- package/stack-template/app/ui-kit/src/ui/select/select.stories.tsx +268 -0
- package/stack-template/app/ui-kit/src/ui/select/select.test.tsx +226 -0
- package/stack-template/app/ui-kit/src/ui/sidebar/index.tsx +83 -0
- package/stack-template/app/ui-kit/src/ui/sidebar/sidebar.stories.tsx +71 -0
- package/stack-template/app/ui-kit/src/ui/sidebar/sidebar.test.tsx +101 -0
- package/stack-template/app/ui-kit/src/ui/skeleton/index.tsx +55 -0
- package/stack-template/app/ui-kit/src/ui/skeleton/skeleton.stories.tsx +110 -0
- package/stack-template/app/ui-kit/src/ui/skeleton/skeleton.test.tsx +74 -0
- package/stack-template/app/ui-kit/src/ui/slide-menu/index.tsx +103 -0
- package/stack-template/app/ui-kit/src/ui/slide-menu/slide-menu.stories.tsx +92 -0
- package/stack-template/app/ui-kit/src/ui/slide-menu/slide-menu.test.tsx +120 -0
- package/stack-template/app/ui-kit/src/ui/slider/index.tsx +90 -0
- package/stack-template/app/ui-kit/src/ui/slider/slider.stories.tsx +60 -0
- package/stack-template/app/ui-kit/src/ui/slider/slider.test.tsx +66 -0
- package/stack-template/app/ui-kit/src/ui/space/index.tsx +32 -0
- package/stack-template/app/ui-kit/src/ui/space/space.stories.tsx +66 -0
- package/stack-template/app/ui-kit/src/ui/space/space.test.tsx +56 -0
- package/stack-template/app/ui-kit/src/ui/speed-dial/index.tsx +32 -0
- package/stack-template/app/ui-kit/src/ui/speed-dial/speed-dial.stories.tsx +34 -0
- package/stack-template/app/ui-kit/src/ui/speed-dial/speed-dial.test.tsx +34 -0
- package/stack-template/app/ui-kit/src/ui/spinner/index.tsx +74 -0
- package/stack-template/app/ui-kit/src/ui/spinner/spinner.stories.tsx +64 -0
- package/stack-template/app/ui-kit/src/ui/spinner/spinner.test.tsx +66 -0
- package/stack-template/app/ui-kit/src/ui/split-button/index.tsx +155 -0
- package/stack-template/app/ui-kit/src/ui/split-button/split-button.stories.tsx +137 -0
- package/stack-template/app/ui-kit/src/ui/split-button/split-button.test.tsx +152 -0
- package/stack-template/app/ui-kit/src/ui/splitter/index.tsx +49 -0
- package/stack-template/app/ui-kit/src/ui/splitter/splitter.stories.tsx +24 -0
- package/stack-template/app/ui-kit/src/ui/splitter/splitter.test.tsx +21 -0
- package/stack-template/app/ui-kit/src/ui/statistic/index.tsx +28 -0
- package/stack-template/app/ui-kit/src/ui/statistic/statistic.stories.tsx +112 -0
- package/stack-template/app/ui-kit/src/ui/statistic/statistic.test.tsx +39 -0
- package/stack-template/app/ui-kit/src/ui/steps/index.tsx +50 -0
- package/stack-template/app/ui-kit/src/ui/steps/steps.stories.tsx +57 -0
- package/stack-template/app/ui-kit/src/ui/steps/steps.test.tsx +55 -0
- package/stack-template/app/ui-kit/src/ui/switch/index.tsx +87 -0
- package/stack-template/app/ui-kit/src/ui/switch/switch.stories.tsx +123 -0
- package/stack-template/app/ui-kit/src/ui/switch/switch.test.tsx +98 -0
- package/stack-template/app/ui-kit/src/ui/tab-menu/index.tsx +33 -0
- package/stack-template/app/ui-kit/src/ui/tab-menu/tab-menu.stories.tsx +49 -0
- package/stack-template/app/ui-kit/src/ui/tab-menu/tab-menu.test.tsx +50 -0
- package/stack-template/app/ui-kit/src/ui/table/index.tsx +690 -0
- package/stack-template/app/ui-kit/src/ui/table/table-advanced.stories.tsx +91 -0
- package/stack-template/app/ui-kit/src/ui/table/table-advanced.test.tsx +188 -0
- package/stack-template/app/ui-kit/src/ui/table/table-expandable.stories.tsx +91 -0
- package/stack-template/app/ui-kit/src/ui/table/table-expandable.test.tsx +143 -0
- package/stack-template/app/ui-kit/src/ui/table/table.stories.tsx +342 -0
- package/stack-template/app/ui-kit/src/ui/table/table.test.tsx +465 -0
- package/stack-template/app/ui-kit/src/ui/table/types.ts +173 -0
- package/stack-template/app/ui-kit/src/ui/tabs/index.tsx +118 -0
- package/stack-template/app/ui-kit/src/ui/tabs/tabs.stories.tsx +113 -0
- package/stack-template/app/ui-kit/src/ui/tabs/tabs.test.tsx +77 -0
- package/stack-template/app/ui-kit/src/ui/tag/index.tsx +68 -0
- package/stack-template/app/ui-kit/src/ui/tag/tag.stories.tsx +88 -0
- package/stack-template/app/ui-kit/src/ui/tag/tag.test.tsx +126 -0
- package/stack-template/app/ui-kit/src/ui/team/debt-board/debt-board.stories.tsx +52 -0
- package/stack-template/app/ui-kit/src/ui/team/debt-board/debt-board.test.tsx +72 -0
- package/stack-template/app/ui-kit/src/ui/team/debt-board/index.tsx +125 -0
- package/stack-template/app/ui-kit/src/ui/team/inbox-thread/inbox-thread.stories.tsx +70 -0
- package/stack-template/app/ui-kit/src/ui/team/inbox-thread/inbox-thread.test.tsx +56 -0
- package/stack-template/app/ui-kit/src/ui/team/inbox-thread/index.tsx +107 -0
- package/stack-template/app/ui-kit/src/ui/team/ping-feed/index.tsx +102 -0
- package/stack-template/app/ui-kit/src/ui/team/ping-feed/ping-feed.stories.tsx +57 -0
- package/stack-template/app/ui-kit/src/ui/team/ping-feed/ping-feed.test.tsx +60 -0
- package/stack-template/app/ui-kit/src/ui/team/stage-pipeline/index.tsx +60 -0
- package/stack-template/app/ui-kit/src/ui/team/stage-pipeline/stage-pipeline.stories.tsx +68 -0
- package/stack-template/app/ui-kit/src/ui/team/stage-pipeline/stage-pipeline.test.tsx +50 -0
- package/stack-template/app/ui-kit/src/ui/team/team-dashboard/index.tsx +103 -0
- package/stack-template/app/ui-kit/src/ui/team/team-dashboard/team-dashboard.stories.tsx +72 -0
- package/stack-template/app/ui-kit/src/ui/team/team-dashboard/team-dashboard.test.tsx +72 -0
- package/stack-template/app/ui-kit/src/ui/time-picker/index.tsx +143 -0
- package/stack-template/app/ui-kit/src/ui/time-picker/time-picker.stories.tsx +90 -0
- package/stack-template/app/ui-kit/src/ui/time-picker/time-picker.test.tsx +64 -0
- package/stack-template/app/ui-kit/src/ui/timeline/index.tsx +91 -0
- package/stack-template/app/ui-kit/src/ui/timeline/timeline.stories.tsx +209 -0
- package/stack-template/app/ui-kit/src/ui/timeline/timeline.test.tsx +116 -0
- package/stack-template/app/ui-kit/src/ui/timesheet/index.tsx +277 -0
- package/stack-template/app/ui-kit/src/ui/timesheet/timesheet.stories.tsx +97 -0
- package/stack-template/app/ui-kit/src/ui/timesheet/timesheet.test.tsx +103 -0
- package/stack-template/app/ui-kit/src/ui/toggle-button/index.tsx +28 -0
- package/stack-template/app/ui-kit/src/ui/toggle-button/toggle-button-group.stories.tsx +85 -0
- package/stack-template/app/ui-kit/src/ui/toggle-button/toggle-button-group.test.tsx +97 -0
- package/stack-template/app/ui-kit/src/ui/toggle-button/toggle-button-group.tsx +81 -0
- package/stack-template/app/ui-kit/src/ui/toggle-button/toggle-button.stories.tsx +46 -0
- package/stack-template/app/ui-kit/src/ui/toggle-button/toggle-button.test.tsx +38 -0
- package/stack-template/app/ui-kit/src/ui/toolbar/index.tsx +18 -0
- package/stack-template/app/ui-kit/src/ui/toolbar/toolbar.stories.tsx +35 -0
- package/stack-template/app/ui-kit/src/ui/toolbar/toolbar.test.tsx +11 -0
- package/stack-template/app/ui-kit/src/ui/tooltip/index.tsx +61 -0
- package/stack-template/app/ui-kit/src/ui/tooltip/tooltip.stories.tsx +81 -0
- package/stack-template/app/ui-kit/src/ui/tooltip/tooltip.test.tsx +120 -0
- package/stack-template/app/ui-kit/src/ui/tour/index.tsx +210 -0
- package/stack-template/app/ui-kit/src/ui/tour/tour.stories.tsx +153 -0
- package/stack-template/app/ui-kit/src/ui/tour/tour.test.tsx +131 -0
- package/stack-template/app/ui-kit/src/ui/transition/index.tsx +377 -0
- package/stack-template/app/ui-kit/src/ui/transition/transition.stories.tsx +254 -0
- package/stack-template/app/ui-kit/src/ui/transition/transition.test.tsx +285 -0
- package/stack-template/app/ui-kit/src/ui/tree/index.tsx +68 -0
- package/stack-template/app/ui-kit/src/ui/tree/tree.stories.tsx +139 -0
- package/stack-template/app/ui-kit/src/ui/tree/tree.test.tsx +77 -0
- package/stack-template/app/ui-kit/src/ui/tree-select/index.tsx +251 -0
- package/stack-template/app/ui-kit/src/ui/tree-select/tree-select.stories.tsx +105 -0
- package/stack-template/app/ui-kit/src/ui/tree-select/tree-select.test.tsx +174 -0
- package/stack-template/app/ui-kit/src/ui/tree-table/index.tsx +229 -0
- package/stack-template/app/ui-kit/src/ui/tree-table/tree-table.stories.tsx +111 -0
- package/stack-template/app/ui-kit/src/ui/tree-table/tree-table.test.tsx +137 -0
- package/stack-template/app/ui-kit/src/ui/typography/index.tsx +124 -0
- package/stack-template/app/ui-kit/src/ui/typography/typography.stories.tsx +65 -0
- package/stack-template/app/ui-kit/src/ui/typography/typography.test.tsx +117 -0
- package/stack-template/app/ui-kit/src/ui/upload/index.tsx +39 -0
- package/stack-template/app/ui-kit/src/ui/upload/upload-core.tsx +204 -0
- package/stack-template/app/ui-kit/src/ui/upload/upload-dropzone.tsx +79 -0
- package/stack-template/app/ui-kit/src/ui/upload/upload.stories.tsx +67 -0
- package/stack-template/app/ui-kit/src/ui/upload/upload.test.tsx +204 -0
- package/stack-template/app/ui-kit/src/ui/virtual-scroll/index.tsx +86 -0
- package/stack-template/app/ui-kit/src/ui/virtual-scroll/virtual-scroll.stories.tsx +111 -0
- package/stack-template/app/ui-kit/src/ui/virtual-scroll/virtual-scroll.test.tsx +125 -0
- package/stack-template/app/ui-kit/src/ui/watermark/index.tsx +182 -0
- package/stack-template/app/ui-kit/src/ui/watermark/watermark.stories.tsx +96 -0
- package/stack-template/app/ui-kit/src/ui/watermark/watermark.test.tsx +120 -0
- package/stack-template/app/ui-kit/src/utils/array.test.ts +88 -0
- package/stack-template/app/ui-kit/src/utils/array.ts +51 -0
- package/stack-template/app/ui-kit/src/utils/dom.test.tsx +69 -0
- package/stack-template/app/ui-kit/src/utils/dom.ts +38 -0
- package/stack-template/app/ui-kit/src/utils/format.test.ts +63 -0
- package/stack-template/app/ui-kit/src/utils/format.ts +81 -0
- package/stack-template/app/ui-kit/tests/setup.ts +15 -0
- package/stack-template/app/ui-kit/tsconfig.json +18 -0
- package/stack-template/app/ui-kit/tsup.config.ts +37 -0
- package/stack-template/app/ui-kit/vercel.json +6 -0
- package/stack-template/app/ui-kit/vitest.config.ts +22 -0
- package/stack-template/contracts/auth/token-contract.md +119 -0
- package/stack-template/contracts/auth/users.json +35 -0
- package/stack-template/contracts/db/tasks-schema.sql +62 -0
- package/stack-template/contracts/events/fixtures/task.assigned.json +11 -0
- package/stack-template/contracts/events/fixtures/task.assignment_failed.json +11 -0
- package/stack-template/contracts/events/fixtures/task.created.json +12 -0
- package/stack-template/contracts/events/fixtures/task.deleted.json +10 -0
- package/stack-template/contracts/events/fixtures/task.updated.json +12 -0
- package/stack-template/contracts/events/task-events.md +80 -0
- package/stack-template/contracts/openapi/activity.yaml +196 -0
- package/stack-template/contracts/openapi/auth.yaml +310 -0
- package/stack-template/contracts/openapi/tasks.yaml +316 -0
- package/stack-template/contracts/openapi/users.yaml +345 -0
- package/stack-template/docs/adr/0001-mixed-stack.md +14 -0
- package/stack-template/docs/adr/0002-mf-rspack.md +14 -0
- package/stack-template/docs/adr/0003-contract-first.md +14 -0
- package/stack-template/docs/adr/0004-readonly-schema-tradeoff.md +14 -0
- package/stack-template/docs/adr/0005-fail-closed.md +14 -0
- package/stack-template/docs/adr/0006-fire-and-forget-events.md +14 -0
- package/stack-template/docs/adr/0007-fork-not-upgrade.md +14 -0
- package/stack-template/docs/adr/0008-kafka-outbox-saga.md +24 -0
- package/stack-template/docs/adr/0009-ui-kit-split.md +39 -0
- package/stack-template/docs/adr/README.md +15 -0
- package/stack-template/docs/ai/recipes.md +956 -0
- package/stack-template/docs/ai/table-Theming.md +25 -0
- package/stack-template/docs/ai/table-datadisplay-mz.md +20 -0
- package/stack-template/docs/ai/table-feedback.md +20 -0
- package/stack-template/docs/ai/table-forms.md +26 -0
- package/stack-template/docs/git-flow.md +70 -0
- package/stack-template/docs/parity-matrix.md +102 -0
- package/stack-template/docs/superpowers/contexts/auth-real/gap-answer.md +195 -0
- package/stack-template/docs/superpowers/contexts/auth-real/sf-1.md +21 -0
- package/stack-template/docs/superpowers/contexts/auth-real/sf-2.md +21 -0
- package/stack-template/docs/superpowers/contexts/auth-real/sf-3.md +17 -0
- package/stack-template/docs/superpowers/contexts/auth-real/sf-4.md +20 -0
- package/stack-template/docs/superpowers/contexts/auth-real/sf-5.md +21 -0
- package/stack-template/docs/superpowers/contexts/experience-design.md +85 -0
- package/stack-template/docs/superpowers/contexts/sf-1.md +39 -0
- package/stack-template/docs/superpowers/contexts/sf-2.md +42 -0
- package/stack-template/docs/superpowers/contexts/sf-3.md +42 -0
- package/stack-template/docs/superpowers/contexts/sf-4.md +26 -0
- package/stack-template/docs/superpowers/contexts/sf-5.md +36 -0
- package/stack-template/docs/superpowers/contexts/sf-6.md +36 -0
- package/stack-template/docs/superpowers/contexts/sf-7.md +35 -0
- package/stack-template/docs/superpowers/contexts/sf-cin-overview.md +21 -0
- package/stack-template/docs/superpowers/contexts/ui-redesign/sf-1.md +46 -0
- package/stack-template/docs/superpowers/contexts/ui-redesign/sf-2.md +44 -0
- package/stack-template/docs/superpowers/contexts/ui-redesign/sf-3.md +47 -0
- package/stack-template/docs/superpowers/contexts/ui-redesign/sf-4.md +44 -0
- package/stack-template/docs/superpowers/contexts/ui-redesign/sf-5.md +36 -0
- package/stack-template/docs/superpowers/designs/direction-approved.md +11 -0
- package/stack-template/docs/superpowers/designs/ui-redesign/drafts/a-roulette-soundviz.html +353 -0
- package/stack-template/docs/superpowers/designs/ui-redesign/drafts/b-reference.html +347 -0
- package/stack-template/docs/superpowers/designs/ui-redesign/drafts/c-designer.html +375 -0
- package/stack-template/docs/superpowers/designs/ui-redesign/ds-demo.html +140 -0
- package/stack-template/docs/superpowers/designs/ui-redesign-direction.md +93 -0
- package/stack-template/docs/superpowers/improvements-log.md +158 -0
- package/stack-template/docs/superpowers/mindmaps/auth-real.wakii +115 -0
- package/stack-template/docs/superpowers/mindmaps/kafka-outbox-saga.wakii +110 -0
- package/stack-template/docs/superpowers/mindmaps/micro-boilerplate.wakii +171 -0
- package/stack-template/docs/superpowers/mindmaps/ui-redesign.wakii +116 -0
- package/stack-template/docs/superpowers/mindmaps/vu53-cinematic-experience.wakii +32 -0
- package/stack-template/docs/superpowers/plans/2026-10-12-sf-1-contracts-platform-plan.md +44 -0
- package/stack-template/docs/superpowers/plans/2026-10-12-sf-3-spring-svc-impact.md +69 -0
- package/stack-template/docs/superpowers/plans/2026-10-12-sf-3-spring-svc-plan.md +27 -0
- package/stack-template/docs/superpowers/plans/2026-10-12-sf-4-go-svc-plan.md +99 -0
- package/stack-template/docs/superpowers/plans/2026-10-12-sf-5-fe-shell-plan.md +64 -0
- package/stack-template/docs/superpowers/plans/2026-10-12-sf-6-fe-remotes-plan.md +83 -0
- package/stack-template/docs/superpowers/plans/2026-10-12-sf-7-convergence-plan.md +59 -0
- package/stack-template/docs/superpowers/plans/2026-10-12-sf2-node-svc-plan.md +19 -0
- package/stack-template/docs/superpowers/plans/2026-10-13-sf-1-kafka-foundation-plan.md +30 -0
- package/stack-template/docs/superpowers/plans/2026-10-13-sf-1-ui-redesign-plan.md +28 -0
- package/stack-template/docs/superpowers/plans/2026-10-13-sf-2-node-outbox-plan.md +46 -0
- package/stack-template/docs/superpowers/plans/2026-10-13-sf-3-go-consumer-plan.md +31 -0
- package/stack-template/docs/superpowers/plans/2026-10-13-sf-4-spring-tx-plan.md +96 -0
- package/stack-template/docs/superpowers/plans/2026-10-13-sf-5-convergence-plan.md +21 -0
- package/stack-template/docs/superpowers/plans/2026-10-14-sf-2-ui-redesign-plan.md +32 -0
- package/stack-template/docs/superpowers/plans/2026-10-14-sf-3-ui-redesign-plan.md +34 -0
- package/stack-template/docs/superpowers/plans/2026-10-14-sf-5-ui-redesign-plan.md +35 -0
- package/stack-template/docs/superpowers/plans/2026-10-17-auth-real-plan.md +112 -0
- package/stack-template/docs/superpowers/plans/2026-10-17-ui-kit-split-plan.md +18 -0
- package/stack-template/docs/superpowers/reviews/2026-10-18-data-display-coverage.md +52 -0
- package/stack-template/docs/superpowers/reviews/code-reviewer-sf-5-r1.md +65 -0
- package/stack-template/docs/superpowers/reviews/security-audit-sf-5-auth-real.md +39 -0
- package/stack-template/docs/superpowers/specs/2026-10-12-boilerplate-microservice-mfe.md +110 -0
- package/stack-template/docs/superpowers/specs/2026-10-12-sf-3-spring-svc-design.md +160 -0
- package/stack-template/docs/superpowers/specs/2026-10-12-sf-7-convergence-spec.md +114 -0
- package/stack-template/docs/superpowers/specs/2026-10-12-sf2-node-svc-spec.md +42 -0
- package/stack-template/docs/superpowers/specs/2026-10-13-kafka-outbox-saga.md +111 -0
- package/stack-template/docs/superpowers/specs/2026-10-13-ui-redesign.md +173 -0
- package/stack-template/docs/superpowers/specs/2026-10-14-sf-3-ui-redesign.md +46 -0
- package/stack-template/docs/superpowers/specs/2026-10-14-sf-5-ui-redesign.md +30 -0
- package/stack-template/docs/superpowers/specs/2026-10-17-antd-look-tokens.md +28 -0
- package/stack-template/docs/superpowers/specs/2026-10-17-auth-real-design.md +124 -0
- package/stack-template/docs/superpowers/specs/2026-10-17-tailwind-migration.md +32 -0
- package/stack-template/docs/superpowers/specs/2026-10-17-ui-kit-split.md +44 -0
- package/stack-template/docs/superpowers/specs/2026-10-18-0.3.0-release-notes.md +77 -0
- package/stack-template/docs/toolchain.md +28 -0
- package/stack-template/docs/wakii-playbook.md +172 -0
- package/stack-template/docs/walkthrough.md +204 -0
- package/stack-template/orca.yaml +146 -0
- package/stack-template/package.json +19 -0
- package/stack-template/platform/.env.example +72 -0
- package/stack-template/platform/conventions/01-dockerfile-pattern.md +78 -0
- package/stack-template/platform/conventions/02-healthcheck.md +26 -0
- package/stack-template/platform/conventions/03-logging.md +41 -0
- package/stack-template/platform/conventions/04-error-envelope.md +41 -0
- package/stack-template/platform/conventions/05-ports.md +38 -0
- package/stack-template/platform/conventions/06-configuration.md +25 -0
- package/stack-template/platform/conventions/07-cors.md +23 -0
- package/stack-template/platform/conventions/08-gateway-and-remotes.md +33 -0
- package/stack-template/platform/conventions/09-async-messaging.md +42 -0
- package/stack-template/platform/conventions/README.md +15 -0
- package/stack-template/platform/docker-compose.yml +255 -0
- package/stack-template/platform/gateway/nginx/nginx.conf +137 -0
- package/stack-template/platform/postgres/init/01-init.sh +55 -0
- package/stack-template/pnpm-lock.yaml +15992 -0
- package/stack-template/pnpm-workspace.yaml +24 -0
- package/stack-template/scripts/scaffold/scaffold.sh +110 -0
- package/stack-template/scripts/smoke/e2e.sh +305 -0
- package/stack-template/scripts/snapshot-stack-template.sh +84 -0
- package/stack-template/services/auth-svc/.dockerignore +5 -0
- package/stack-template/services/auth-svc/Dockerfile +32 -0
- package/stack-template/services/auth-svc/README.md +101 -0
- package/stack-template/services/auth-svc/drizzle/0000_init.sql +25 -0
- package/stack-template/services/auth-svc/drizzle/meta/0000_snapshot.json +190 -0
- package/stack-template/services/auth-svc/drizzle/meta/_journal.json +13 -0
- package/stack-template/services/auth-svc/drizzle.config.ts +10 -0
- package/stack-template/services/auth-svc/package.json +35 -0
- package/stack-template/services/auth-svc/pnpm-lock.yaml +2425 -0
- package/stack-template/services/auth-svc/src/app.test.ts +403 -0
- package/stack-template/services/auth-svc/src/app.ts +103 -0
- package/stack-template/services/auth-svc/src/auth-crypto.test.ts +32 -0
- package/stack-template/services/auth-svc/src/auth-crypto.ts +25 -0
- package/stack-template/services/auth-svc/src/db/client.ts +24 -0
- package/stack-template/services/auth-svc/src/db/migrate.ts +72 -0
- package/stack-template/services/auth-svc/src/db/schema.ts +39 -0
- package/stack-template/services/auth-svc/src/db/seed.ts +60 -0
- package/stack-template/services/auth-svc/src/env.test.ts +55 -0
- package/stack-template/services/auth-svc/src/env.ts +57 -0
- package/stack-template/services/auth-svc/src/logger.ts +31 -0
- package/stack-template/services/auth-svc/src/plugins/auth.ts +70 -0
- package/stack-template/services/auth-svc/src/repo.drizzle.integration.test.ts +182 -0
- package/stack-template/services/auth-svc/src/repo.drizzle.ts +144 -0
- package/stack-template/services/auth-svc/src/repo.test-support.ts +168 -0
- package/stack-template/services/auth-svc/src/repo.ts +60 -0
- package/stack-template/services/auth-svc/src/routes/auth.ts +193 -0
- package/stack-template/services/auth-svc/src/routes/health.ts +40 -0
- package/stack-template/services/auth-svc/src/server.ts +54 -0
- package/stack-template/services/auth-svc/tsconfig.json +20 -0
- package/stack-template/services/go-svc/.dockerignore +5 -0
- package/stack-template/services/go-svc/Dockerfile +19 -0
- package/stack-template/services/go-svc/README.md +93 -0
- package/stack-template/services/go-svc/cmd/server/main.go +105 -0
- package/stack-template/services/go-svc/go.mod +12 -0
- package/stack-template/services/go-svc/internal/api/auth.go +83 -0
- package/stack-template/services/go-svc/internal/api/auth_test.go +92 -0
- package/stack-template/services/go-svc/internal/api/envelope.go +61 -0
- package/stack-template/services/go-svc/internal/api/envelope_test.go +51 -0
- package/stack-template/services/go-svc/internal/api/fakes_test.go +105 -0
- package/stack-template/services/go-svc/internal/api/feed.go +98 -0
- package/stack-template/services/go-svc/internal/api/feed_test.go +118 -0
- package/stack-template/services/go-svc/internal/api/health.go +34 -0
- package/stack-template/services/go-svc/internal/api/health_test.go +46 -0
- package/stack-template/services/go-svc/internal/api/json.go +13 -0
- package/stack-template/services/go-svc/internal/api/middleware.go +82 -0
- package/stack-template/services/go-svc/internal/api/middleware_test.go +48 -0
- package/stack-template/services/go-svc/internal/api/openapi/activity.yaml +196 -0
- package/stack-template/services/go-svc/internal/api/openapi.go +29 -0
- package/stack-template/services/go-svc/internal/api/openapi_test.go +38 -0
- package/stack-template/services/go-svc/internal/api/router.go +86 -0
- package/stack-template/services/go-svc/internal/api/stats.go +63 -0
- package/stack-template/services/go-svc/internal/api/stats_test.go +83 -0
- package/stack-template/services/go-svc/internal/api/types.go +32 -0
- package/stack-template/services/go-svc/internal/apperr/apperr.go +9 -0
- package/stack-template/services/go-svc/internal/config/config.go +115 -0
- package/stack-template/services/go-svc/internal/config/config_test.go +92 -0
- package/stack-template/services/go-svc/internal/consumer/envelope.go +78 -0
- package/stack-template/services/go-svc/internal/consumer/envelope_test.go +154 -0
- package/stack-template/services/go-svc/internal/consumer/handler.go +91 -0
- package/stack-template/services/go-svc/internal/consumer/handler_test.go +136 -0
- package/stack-template/services/go-svc/internal/consumer/producer.go +117 -0
- package/stack-template/services/go-svc/internal/consumer/runner.go +194 -0
- package/stack-template/services/go-svc/internal/consumer/runner_test.go +351 -0
- package/stack-template/services/go-svc/internal/logging/logging.go +49 -0
- package/stack-template/services/go-svc/internal/model/model.go +37 -0
- package/stack-template/services/go-svc/internal/resolve/resolver.go +134 -0
- package/stack-template/services/go-svc/internal/resolve/resolver_test.go +82 -0
- package/stack-template/services/go-svc/internal/resolve/token.go +130 -0
- package/stack-template/services/go-svc/internal/resolve/token_test.go +151 -0
- package/stack-template/services/go-svc/internal/store/events.go +71 -0
- package/stack-template/services/go-svc/internal/store/migrate.go +90 -0
- package/stack-template/services/go-svc/internal/store/migrate_test.go +34 -0
- package/stack-template/services/go-svc/internal/store/migrations/0001_create_events.sql +20 -0
- package/stack-template/services/go-svc/internal/store/migrations/0002_widen_action_cascade.sql +14 -0
- package/stack-template/services/go-svc/internal/store/store.go +65 -0
- package/stack-template/services/go-svc/internal/store/tasks.go +109 -0
- package/stack-template/services/go-svc/internal/swagger/assets/LICENSE +202 -0
- package/stack-template/services/go-svc/internal/swagger/assets/NOTICE +2 -0
- package/stack-template/services/go-svc/internal/swagger/assets/favicon-16x16.png +0 -0
- package/stack-template/services/go-svc/internal/swagger/assets/favicon-32x32.png +0 -0
- package/stack-template/services/go-svc/internal/swagger/assets/swagger-ui-bundle.js +2 -0
- package/stack-template/services/go-svc/internal/swagger/assets/swagger-ui.css +3 -0
- package/stack-template/services/go-svc/internal/swagger/index.html +28 -0
- package/stack-template/services/go-svc/internal/swagger/swagger.go +38 -0
- package/stack-template/services/go-svc/test/integration/integration_test.go +523 -0
- package/stack-template/services/node-svc/.dockerignore +5 -0
- package/stack-template/services/node-svc/Dockerfile +27 -0
- package/stack-template/services/node-svc/README.md +155 -0
- package/stack-template/services/node-svc/drizzle/0000_init.sql +17 -0
- package/stack-template/services/node-svc/drizzle/0001_status-assignment-failed.sql +2 -0
- package/stack-template/services/node-svc/drizzle/0002_outbox-table.sql +7 -0
- package/stack-template/services/node-svc/drizzle/meta/0000_snapshot.json +136 -0
- package/stack-template/services/node-svc/drizzle/meta/0001_snapshot.json +136 -0
- package/stack-template/services/node-svc/drizzle/meta/0002_snapshot.json +180 -0
- package/stack-template/services/node-svc/drizzle/meta/_journal.json +27 -0
- package/stack-template/services/node-svc/drizzle.config.ts +10 -0
- package/stack-template/services/node-svc/package.json +39 -0
- package/stack-template/services/node-svc/pnpm-lock.yaml +2706 -0
- package/stack-template/services/node-svc/src/app.test.ts +611 -0
- package/stack-template/services/node-svc/src/app.ts +126 -0
- package/stack-template/services/node-svc/src/clients/spring.ts +53 -0
- package/stack-template/services/node-svc/src/compensation-consumer.test.ts +157 -0
- package/stack-template/services/node-svc/src/compensation-consumer.ts +170 -0
- package/stack-template/services/node-svc/src/db/client.ts +23 -0
- package/stack-template/services/node-svc/src/db/migrate.ts +63 -0
- package/stack-template/services/node-svc/src/db/schema-diff.test.ts +252 -0
- package/stack-template/services/node-svc/src/db/schema.ts +64 -0
- package/stack-template/services/node-svc/src/env.ts +52 -0
- package/stack-template/services/node-svc/src/logger.ts +18 -0
- package/stack-template/services/node-svc/src/outbox-relay.test.ts +141 -0
- package/stack-template/services/node-svc/src/outbox-relay.ts +182 -0
- package/stack-template/services/node-svc/src/plugins/auth.ts +76 -0
- package/stack-template/services/node-svc/src/repo.drizzle.test.ts +182 -0
- package/stack-template/services/node-svc/src/repo.drizzle.ts +158 -0
- package/stack-template/services/node-svc/src/repo.ts +73 -0
- package/stack-template/services/node-svc/src/routes/health.ts +42 -0
- package/stack-template/services/node-svc/src/routes/tasks.ts +287 -0
- package/stack-template/services/node-svc/src/server.ts +91 -0
- package/stack-template/services/node-svc/src/types/api.d.ts +391 -0
- package/stack-template/services/node-svc/src/types/log.ts +3 -0
- package/stack-template/services/node-svc/tsconfig.json +20 -0
- package/stack-template/services/spring-svc/.dockerignore +5 -0
- package/stack-template/services/spring-svc/.mvn/wrapper/maven-wrapper.properties +20 -0
- package/stack-template/services/spring-svc/Dockerfile +22 -0
- package/stack-template/services/spring-svc/README.md +121 -0
- package/stack-template/services/spring-svc/mvnw +332 -0
- package/stack-template/services/spring-svc/mvnw.cmd +206 -0
- package/stack-template/services/spring-svc/pom.xml +144 -0
- package/stack-template/services/spring-svc/src/main/java/com/example/springsvc/SpringSvcApplication.java +12 -0
- package/stack-template/services/spring-svc/src/main/java/com/example/springsvc/domain/Team.java +64 -0
- package/stack-template/services/spring-svc/src/main/java/com/example/springsvc/domain/TeamRepository.java +34 -0
- package/stack-template/services/spring-svc/src/main/java/com/example/springsvc/domain/User.java +72 -0
- package/stack-template/services/spring-svc/src/main/java/com/example/springsvc/domain/UserRepository.java +10 -0
- package/stack-template/services/spring-svc/src/main/java/com/example/springsvc/logging/LowercaseLevelJsonProvider.java +24 -0
- package/stack-template/services/spring-svc/src/main/java/com/example/springsvc/security/JwtAuthFilter.java +89 -0
- package/stack-template/services/spring-svc/src/main/java/com/example/springsvc/security/JwtVerifier.java +48 -0
- package/stack-template/services/spring-svc/src/main/java/com/example/springsvc/service/TeamService.java +84 -0
- package/stack-template/services/spring-svc/src/main/java/com/example/springsvc/web/ApiError.java +15 -0
- package/stack-template/services/spring-svc/src/main/java/com/example/springsvc/web/ApiException.java +37 -0
- package/stack-template/services/spring-svc/src/main/java/com/example/springsvc/web/DevCorsConfig.java +41 -0
- package/stack-template/services/spring-svc/src/main/java/com/example/springsvc/web/ErrorEnvelope.java +27 -0
- package/stack-template/services/spring-svc/src/main/java/com/example/springsvc/web/GlobalExceptionHandler.java +90 -0
- package/stack-template/services/spring-svc/src/main/java/com/example/springsvc/web/HealthController.java +46 -0
- package/stack-template/services/spring-svc/src/main/java/com/example/springsvc/web/ReadinessService.java +40 -0
- package/stack-template/services/spring-svc/src/main/java/com/example/springsvc/web/TeamController.java +79 -0
- package/stack-template/services/spring-svc/src/main/java/com/example/springsvc/web/UserController.java +187 -0
- package/stack-template/services/spring-svc/src/main/java/db/migration/V2__SeedUsers.java +66 -0
- package/stack-template/services/spring-svc/src/main/resources/application.yml +42 -0
- package/stack-template/services/spring-svc/src/main/resources/db/migration/V1__init.sql +24 -0
- package/stack-template/services/spring-svc/src/main/resources/logback-spring.xml +34 -0
- package/stack-template/services/spring-svc/src/main/resources/openapi/users.yaml +345 -0
- package/stack-template/services/spring-svc/src/test/java/com/example/springsvc/logging/LogShapeTest.java +39 -0
- package/stack-template/services/spring-svc/src/test/java/com/example/springsvc/security/JwtAuthFilterTest.java +106 -0
- package/stack-template/services/spring-svc/src/test/java/com/example/springsvc/seed/SeedMatchIntegrationTest.java +68 -0
- package/stack-template/services/spring-svc/src/test/java/com/example/springsvc/service/TeamServiceRollbackTest.java +122 -0
- package/stack-template/services/spring-svc/src/test/java/com/example/springsvc/web/DevCorsTest.java +45 -0
- package/stack-template/services/spring-svc/src/test/java/com/example/springsvc/web/HealthCheckTest.java +56 -0
- package/stack-template/services/spring-svc/src/test/java/com/example/springsvc/web/TeamApiTest.java +139 -0
- package/stack-template/services/spring-svc/src/test/java/com/example/springsvc/web/TestPingController.java +21 -0
- package/stack-template/services/spring-svc/src/test/java/com/example/springsvc/web/UserApiTest.java +182 -0
- package/stack-template/stack-manifest.json +285 -0
|
@@ -0,0 +1,956 @@
|
|
|
1
|
+
# AI Recipes — 19 công thức UI dùng `@wakii/ui-kit`
|
|
2
|
+
|
|
3
|
+
Code dưới dùng **API thật đã verify theo source** (audit 2026-10-18). Import gốc:
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { /* components */ } from '@wakii/ui-kit';
|
|
7
|
+
import { z } from 'zod'; // peer-dep cho useForm
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Quy tắc chung xem [AGENTS.md](../../AGENTS.md). Mỗi recipe tự chứa đủ imports riêng để copy-chạy.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 1. LoginForm — useForm + zod + Field + Input + Button
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
import { useState } from 'react';
|
|
18
|
+
import { z } from 'zod';
|
|
19
|
+
import { Button, Field, Input, useForm } from '@wakii/ui-kit';
|
|
20
|
+
|
|
21
|
+
const schema = z.object({
|
|
22
|
+
email: z.string().min(1, 'Email bắt buộc').email('Email không hợp lệ'),
|
|
23
|
+
password: z.string().min(8, 'Mật khẩu cần 8–72 ký tự.').max(72, 'Mật khẩu cần 8–72 ký tự.'),
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
export function LoginForm({ onLogin }: { onLogin: (d: { email: string; password: string }) => Promise<void> }) {
|
|
27
|
+
const form = useForm({ schema, initialValues: { email: '', password: '' }, mode: 'onSubmit' });
|
|
28
|
+
const [serverError, setServerError] = useState<string | null>(null);
|
|
29
|
+
const [submitting, setSubmitting] = useState(false);
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<form
|
|
33
|
+
className="flex w-80 flex-col gap-3"
|
|
34
|
+
onSubmit={form.handleSubmit(async (data) => {
|
|
35
|
+
setSubmitting(true);
|
|
36
|
+
try {
|
|
37
|
+
await onLogin(data); // sai → throw Error('Email hoặc mật khẩu không đúng.')
|
|
38
|
+
} catch (e) {
|
|
39
|
+
setServerError((e as Error).message);
|
|
40
|
+
} finally {
|
|
41
|
+
setSubmitting(false);
|
|
42
|
+
}
|
|
43
|
+
})}
|
|
44
|
+
>
|
|
45
|
+
<Field label="Email" htmlFor="email" error={form.errors.email}>
|
|
46
|
+
<Input id="email" name="email" type="email" autoComplete="username" placeholder="you@demo.local"
|
|
47
|
+
value={form.values.email} invalid={!!form.errors.email}
|
|
48
|
+
onChange={form.handleChange('email')} onBlur={form.handleBlur('email')} />
|
|
49
|
+
</Field>
|
|
50
|
+
<Field label="Mật khẩu" htmlFor="password" error={form.errors.password}>
|
|
51
|
+
<Input id="password" name="password" type="password" autoComplete="current-password"
|
|
52
|
+
value={form.values.password} invalid={!!form.errors.password}
|
|
53
|
+
onChange={form.handleChange('password')} />
|
|
54
|
+
</Field>
|
|
55
|
+
{serverError && <p role="alert" className="text-meta text-st-fail">{serverError}</p>}
|
|
56
|
+
<Button htmlType="submit" loading={submitting} disabled={submitting} block>Đăng nhập</Button>
|
|
57
|
+
</form>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Ghi chú: sai schema → `handleSubmit` tự set `errors` + focus field đầu (`[name=…]`); `mode` đổi thành `'onBlur' | 'onChange'` nếu muốn validate sớm.
|
|
63
|
+
|
|
64
|
+
## 2. CrudUserPage — Table + Select filter + Pagination + Modal + message
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
import { useMemo, useState } from 'react';
|
|
68
|
+
import { Button, Field, Input, message, Modal, Pagination, Select, Table } from '@wakii/ui-kit';
|
|
69
|
+
|
|
70
|
+
type User = { id: number; name: string; role: 'admin' | 'user' };
|
|
71
|
+
const USERS: User[] = [
|
|
72
|
+
{ id: 1, name: 'Alice', role: 'admin' },
|
|
73
|
+
{ id: 2, name: 'Bob', role: 'user' },
|
|
74
|
+
{ id: 3, name: 'Carol', role: 'user' },
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
export function CrudUserPage() {
|
|
78
|
+
const [users, setUsers] = useState(USERS);
|
|
79
|
+
const [roleFilter, setRoleFilter] = useState<string>('all');
|
|
80
|
+
const [page, setPage] = useState(1);
|
|
81
|
+
const [editing, setEditing] = useState<User | null>(null);
|
|
82
|
+
|
|
83
|
+
const filtered = useMemo(
|
|
84
|
+
() => users.filter((u) => roleFilter === 'all' || u.role === roleFilter),
|
|
85
|
+
[users, roleFilter],
|
|
86
|
+
);
|
|
87
|
+
const pageSize = 2;
|
|
88
|
+
const paged = filtered.slice((page - 1) * pageSize, page * pageSize);
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<div className="flex flex-col gap-4">
|
|
92
|
+
{/* FilterBar */}
|
|
93
|
+
<div className="flex items-center gap-2">
|
|
94
|
+
<Select
|
|
95
|
+
ariaLabel="Lọc theo vai trò"
|
|
96
|
+
className="w-44"
|
|
97
|
+
value={roleFilter}
|
|
98
|
+
onChange={(v) => { setRoleFilter(v); setPage(1); }}
|
|
99
|
+
options={[
|
|
100
|
+
{ label: 'Tất cả', value: 'all' },
|
|
101
|
+
{ label: 'Admin', value: 'admin' },
|
|
102
|
+
{ label: 'User', value: 'user' },
|
|
103
|
+
]}
|
|
104
|
+
/>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<Table
|
|
108
|
+
columns={[
|
|
109
|
+
{ key: 'name', title: 'Tên', sortable: true },
|
|
110
|
+
{ key: 'role', title: 'Vai trò' },
|
|
111
|
+
{
|
|
112
|
+
key: 'act', title: '',
|
|
113
|
+
render: (row: User) => <Button size="small" onClick={() => setEditing(row)}>Sửa</Button>,
|
|
114
|
+
},
|
|
115
|
+
]}
|
|
116
|
+
dataSource={paged}
|
|
117
|
+
rowKey="id"
|
|
118
|
+
emptyMessage={<p className="p-4 text-body text-ink3">Không có user phù hợp.</p>}
|
|
119
|
+
/>
|
|
120
|
+
<Pagination current={page} pageSize={pageSize}
|
|
121
|
+
total={filtered.length} onChange={setPage} />
|
|
122
|
+
|
|
123
|
+
<Modal open={!!editing} title={`Sửa: ${editing?.name ?? ''}`}
|
|
124
|
+
onOk={() => { message.success('Đã lưu'); setEditing(null); }}
|
|
125
|
+
onCancel={() => setEditing(null)}>
|
|
126
|
+
<Field label="Tên" htmlFor="u-name">
|
|
127
|
+
<Input id="u-name" value={editing?.name ?? ''} onChange={(e) =>
|
|
128
|
+
setEditing(editing ? { ...editing, name: e.target.value } : editing)} />
|
|
129
|
+
</Field>
|
|
130
|
+
</Modal>
|
|
131
|
+
</div>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## 3. FilterBar — Select + Input + Button
|
|
137
|
+
|
|
138
|
+
```tsx
|
|
139
|
+
import { Button, Icon, Input, Select } from '@wakii/ui-kit';
|
|
140
|
+
|
|
141
|
+
export function FilterBar({ q, setQ, status, setStatus, onReset }: {
|
|
142
|
+
q: string; setQ: (v: string) => void;
|
|
143
|
+
status: string; setStatus: (v: string) => void;
|
|
144
|
+
onReset: () => void;
|
|
145
|
+
}) {
|
|
146
|
+
return (
|
|
147
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
148
|
+
<Input className="w-56" placeholder="Tìm theo tên…" allowClear prefix={<Icon name="search" size={14} />}
|
|
149
|
+
value={q} onChange={setQ} />
|
|
150
|
+
<Select className="w-40" ariaLabel="Trạng thái" value={status} onChange={setStatus}
|
|
151
|
+
options={[{ label: 'Tất cả', value: 'all' }, { label: 'Đang làm', value: 'doing' }, { label: 'Xong', value: 'done' }]} />
|
|
152
|
+
<Button type="text" icon={<Icon name="refresh" size={14} />} onClick={onReset}>Đặt lại</Button>
|
|
153
|
+
</div>
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## 4. Wizard — Steps
|
|
159
|
+
|
|
160
|
+
```tsx
|
|
161
|
+
import { useState } from 'react';
|
|
162
|
+
import { Button, Steps } from '@wakii/ui-kit';
|
|
163
|
+
|
|
164
|
+
const STEPS = [
|
|
165
|
+
{ title: 'Chọn loại', description: 'Task hay bug' },
|
|
166
|
+
{ title: 'Điền thông tin', description: 'Tiêu đề + assignee' },
|
|
167
|
+
{ title: 'Xác nhận', description: 'Review rồi tạo' },
|
|
168
|
+
];
|
|
169
|
+
|
|
170
|
+
export function Wizard() {
|
|
171
|
+
const [current, setCurrent] = useState(0);
|
|
172
|
+
return (
|
|
173
|
+
<div className="flex max-w-xl flex-col gap-6">
|
|
174
|
+
<Steps items={STEPS} current={current} />
|
|
175
|
+
<div className="min-h-24 rounded-xl border border-line bg-s2 p-4 text-body text-ink2">
|
|
176
|
+
Nội dung bước {current + 1} — {STEPS[current]?.description}
|
|
177
|
+
</div>
|
|
178
|
+
<div className="flex gap-2">
|
|
179
|
+
<Button disabled={current === 0} onClick={() => setCurrent((c) => c - 1)}>Quay lại</Button>
|
|
180
|
+
{current < STEPS.length - 1
|
|
181
|
+
? <Button type="primary" onClick={() => setCurrent((c) => c + 1)}>Tiếp</Button>
|
|
182
|
+
: <Button type="primary" onClick={() => setCurrent(0)}>Hoàn tất</Button>}
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## 5. Upload kèm preview — UploadDropzone + Progress
|
|
190
|
+
|
|
191
|
+
```tsx
|
|
192
|
+
import { useRef, useState } from 'react';
|
|
193
|
+
import { UploadDropzone } from '@wakii/ui-kit';
|
|
194
|
+
|
|
195
|
+
export function UploadKemPreview({ onChange }: { onChange?: (files: File[]) => void }) {
|
|
196
|
+
const [previews, setPreviews] = useState<{ name: string; url: string }[]>([]);
|
|
197
|
+
|
|
198
|
+
return (
|
|
199
|
+
<div className="flex flex-col gap-3">
|
|
200
|
+
<UploadDropzone
|
|
201
|
+
accept="image/*"
|
|
202
|
+
multiple
|
|
203
|
+
maxCount={5}
|
|
204
|
+
onChange={(files) => {
|
|
205
|
+
onChange?.(files);
|
|
206
|
+
// component tự render danh sách file chuẩn (tên + size + × remove);
|
|
207
|
+
// preview ảnh tự dựng từ File[]
|
|
208
|
+
setPreviews(files.map((f) => ({ name: f.name, url: URL.createObjectURL(f) })));
|
|
209
|
+
}}
|
|
210
|
+
/>
|
|
211
|
+
{previews.length > 0 && (
|
|
212
|
+
<div className="flex gap-2">
|
|
213
|
+
{previews.map((u) => (
|
|
214
|
+
<img key={u.url} src={u.url} alt={u.name} className="size-16 rounded-lg object-cover" />
|
|
215
|
+
))}
|
|
216
|
+
</div>
|
|
217
|
+
)}
|
|
218
|
+
</div>
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
*(Danh sách `UploadItem` (tên + size + Progress) do `UploadDropzone` tự render; `onChange` trả `File[]` để consumer dựng preview.)*
|
|
224
|
+
|
|
225
|
+
## 6. DashboardLayout — stat cards + Chart + Timeline
|
|
226
|
+
|
|
227
|
+
```tsx
|
|
228
|
+
import { Card, Chart, EmptyState, Icon, Tag, Timeline, type ChartConfig } from '@wakii/ui-kit';
|
|
229
|
+
|
|
230
|
+
// ⚠️ Shape NATIVE chart.js v4 — data lồng trong data:{labels,datasets}
|
|
231
|
+
const config = {
|
|
232
|
+
type: 'line' as const,
|
|
233
|
+
data: {
|
|
234
|
+
labels: ['T2', 'T3', 'T4', 'T5', 'T6', 'T7', 'CN'],
|
|
235
|
+
datasets: [{ label: 'Tasks', data: [12, 19, 8, 22, 30, 25, 18], borderColor: 'var(--accent)', tension: 0.35, fill: true }],
|
|
236
|
+
},
|
|
237
|
+
options: { responsive: true, maintainAspectRatio: false, animation: false },
|
|
238
|
+
} as unknown as ChartConfig;
|
|
239
|
+
|
|
240
|
+
const STATS = [
|
|
241
|
+
{ title: 'Người dùng', value: '1.284', tag: '+12%', icon: 'users' as const },
|
|
242
|
+
{ title: 'Tasks mở', value: '316', tag: '+5%', icon: 'check-circle' as const },
|
|
243
|
+
];
|
|
244
|
+
|
|
245
|
+
export function DashboardLayout() {
|
|
246
|
+
return (
|
|
247
|
+
<div className="flex flex-col gap-6">
|
|
248
|
+
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 xl:grid-cols-4">
|
|
249
|
+
{STATS.map((s) => (
|
|
250
|
+
<Card key={s.title} hoverable>
|
|
251
|
+
<div className="flex items-center gap-3">
|
|
252
|
+
<span aria-hidden="true" className="grid size-10 shrink-0 place-items-center rounded-lg bg-accent/10 text-accent">
|
|
253
|
+
<Icon name={s.icon} size={18} />
|
|
254
|
+
</span>
|
|
255
|
+
<div className="min-w-0 flex-1">
|
|
256
|
+
<div className="text-body text-ink2">{s.title}</div>
|
|
257
|
+
<div className="mt-0.5 flex items-center gap-2">
|
|
258
|
+
<span className="text-2xl font-semibold leading-tight text-ink">{s.value}</span>
|
|
259
|
+
<span className="rounded-pill bg-st-done/10 px-1.5 py-0.5 text-[10px] font-medium text-st-done">{s.tag}</span>
|
|
260
|
+
</div>
|
|
261
|
+
</div>
|
|
262
|
+
</div>
|
|
263
|
+
</Card>
|
|
264
|
+
))}
|
|
265
|
+
</div>
|
|
266
|
+
<div className="grid grid-cols-1 gap-6 xl:grid-cols-5">
|
|
267
|
+
<Card title="Hoạt động 7 ngày" extra={<Tag>Tuần này</Tag>} className="xl:col-span-3">
|
|
268
|
+
<div className="h-64"><Chart config={config} height={256} /></div>
|
|
269
|
+
</Card>
|
|
270
|
+
<Card title="Timeline" extra={<Tag>real-time</Tag>} className="xl:col-span-2">
|
|
271
|
+
<Timeline items={[
|
|
272
|
+
{ key: 't1', children: 'Alice tạo task', label: '10 phút trước' },
|
|
273
|
+
{ key: 't2', color: 'var(--st-done)', children: 'Bot sync Kafka', label: '1 giờ trước' },
|
|
274
|
+
]} />
|
|
275
|
+
</Card>
|
|
276
|
+
</div>
|
|
277
|
+
</div>
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
## 7. MasterDetailDrawer — List + Avatar + Drawer
|
|
283
|
+
|
|
284
|
+
```tsx
|
|
285
|
+
import { useState } from 'react';
|
|
286
|
+
import { Avatar, Button, Drawer, List } from '@wakii/ui-kit';
|
|
287
|
+
|
|
288
|
+
const PEOPLE = [
|
|
289
|
+
{ id: 1, name: 'Alice', role: 'Admin' },
|
|
290
|
+
{ id: 2, name: 'Bob', role: 'User' },
|
|
291
|
+
];
|
|
292
|
+
|
|
293
|
+
export function MasterDetailDrawer() {
|
|
294
|
+
const [open, setOpen] = useState(false);
|
|
295
|
+
const [active, setActive] = useState(PEOPLE[0]);
|
|
296
|
+
|
|
297
|
+
return (
|
|
298
|
+
<>
|
|
299
|
+
<List
|
|
300
|
+
items={PEOPLE}
|
|
301
|
+
renderItem={(p) => (
|
|
302
|
+
<button type="button" className="flex w-full items-center gap-2 rounded-lg px-2 py-2 hover:bg-s3"
|
|
303
|
+
onClick={() => { setActive(p); setOpen(true); }}>
|
|
304
|
+
<Avatar size={28}>{p.name.charAt(0)}</Avatar>
|
|
305
|
+
<span className="text-body text-ink">{p.name}</span>
|
|
306
|
+
<span className="ml-auto text-meta text-ink3">{p.role}</span>
|
|
307
|
+
</button>
|
|
308
|
+
)}
|
|
309
|
+
/>
|
|
310
|
+
<Drawer open={open} title={active.name} width={360} onClose={() => setOpen(false)}
|
|
311
|
+
footer={<Button type="primary" onClick={() => setOpen(false)}>Đóng</Button>}>
|
|
312
|
+
<p className="text-body text-ink2">Chi tiết của {active.name} ({active.role}).</p>
|
|
313
|
+
</Drawer>
|
|
314
|
+
</>
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
## 8. NotificationFlow — message + Badge bell
|
|
320
|
+
|
|
321
|
+
```tsx
|
|
322
|
+
import { Badge, Button, Icon, message, notification } from '@wakii/ui-kit';
|
|
323
|
+
|
|
324
|
+
export function NotificationFlow() {
|
|
325
|
+
const notify = () => {
|
|
326
|
+
message.success('Đã lưu task');
|
|
327
|
+
notification.info('Kafka event', 'task.updated — feed activity đã cập nhật.');
|
|
328
|
+
};
|
|
329
|
+
return (
|
|
330
|
+
<div className="flex items-center gap-3">
|
|
331
|
+
<button type="button" aria-label="Thông báo (2 mới)"
|
|
332
|
+
className="relative cursor-pointer rounded-2 border-none bg-transparent p-1.5 text-ink2 hover:bg-s3">
|
|
333
|
+
<Icon name="bell" size={18} />
|
|
334
|
+
<span className="absolute -right-1 -top-1"><Badge variant="failed">2</Badge></span>
|
|
335
|
+
</button>
|
|
336
|
+
<Button type="primary" onClick={notify}>Kích hoạt sự kiện</Button>
|
|
337
|
+
</div>
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
## 9. FormSettings — Tabs + Switch + Field
|
|
343
|
+
|
|
344
|
+
```tsx
|
|
345
|
+
import { useState } from 'react';
|
|
346
|
+
import { Field, Input, Switch, Tabs } from '@wakii/ui-kit';
|
|
347
|
+
|
|
348
|
+
export function FormSettings() {
|
|
349
|
+
const [dark, setDark] = useState(false);
|
|
350
|
+
const [name, setName] = useState('Alice');
|
|
351
|
+
return (
|
|
352
|
+
<Tabs
|
|
353
|
+
items={[
|
|
354
|
+
{
|
|
355
|
+
key: 'profile', label: 'Hồ sơ',
|
|
356
|
+
children: (
|
|
357
|
+
<div className="flex max-w-sm flex-col gap-3 pt-4">
|
|
358
|
+
<Field label="Tên hiển thị" htmlFor="s-name">
|
|
359
|
+
<Input id="s-name" value={name} onChange={setName} />
|
|
360
|
+
</Field>
|
|
361
|
+
</div>
|
|
362
|
+
),
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
key: 'appearance', label: 'Giao diện',
|
|
366
|
+
children: (
|
|
367
|
+
<div className="flex items-center gap-3 pt-4">
|
|
368
|
+
<Switch checked={dark} onChange={setDark} aria-label="Dark mode" />
|
|
369
|
+
<span className="text-body text-ink2">Chủ đề tối</span>
|
|
370
|
+
</div>
|
|
371
|
+
),
|
|
372
|
+
},
|
|
373
|
+
]}
|
|
374
|
+
onChange={(k) => console.log('tab:', k)}
|
|
375
|
+
/>
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
## 10. HeroLanding — Button + Tag + Typography + Divider
|
|
381
|
+
|
|
382
|
+
```tsx
|
|
383
|
+
import { Button, Divider, Icon, Tag } from '@wakii/ui-kit';
|
|
384
|
+
|
|
385
|
+
export function HeroLanding() {
|
|
386
|
+
return (
|
|
387
|
+
<section className="flex min-h-[420px] flex-col items-center justify-center gap-4 text-center">
|
|
388
|
+
<Tag icon={<Icon name="check-circle" size={12} />}>v1.0 — production ready</Tag>
|
|
389
|
+
<h1 className="m-0 text-h1 font-semibold tracking-tight text-ink">
|
|
390
|
+
Bộ quản lý công việc cho team của bạn
|
|
391
|
+
</h1>
|
|
392
|
+
<p className="max-w-md text-body text-ink2">
|
|
393
|
+
Quản lý công việc, upload, hoạt động real-time — một bộ design system nhất quán.
|
|
394
|
+
</p>
|
|
395
|
+
<div className="flex gap-2">
|
|
396
|
+
<Button type="primary" size="large">Bắt đầu</Button>
|
|
397
|
+
<Button size="large">Xem demo</Button>
|
|
398
|
+
</div>
|
|
399
|
+
<Divider dashed>hoặc</Divider>
|
|
400
|
+
<span className="text-meta text-ink3">alice@demo.local / demo-password</span>
|
|
401
|
+
</section>
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
---
|
|
407
|
+
|
|
408
|
+
## 11. DashboardGrid — Row/Col + Card + Statistic
|
|
409
|
+
|
|
410
|
+
```tsx
|
|
411
|
+
import { Card, Col, Row, Statistic, type StatisticProps } from '@wakii/ui-kit';
|
|
412
|
+
|
|
413
|
+
const STATS: { title: string; value: string; delta: string; tone: StatisticProps['valueStyle'] }[] = [
|
|
414
|
+
{ title: 'Người dùng', value: '1,284', delta: '+12%', tone: { color: 'var(--st-done)' } },
|
|
415
|
+
{ title: 'Tasks mở', value: '316', delta: '+5%', tone: { color: 'var(--st-doing)' } },
|
|
416
|
+
{ title: 'Quá hạn', value: '18', delta: '-3', tone: { color: 'var(--st-fail)' } },
|
|
417
|
+
{ title: 'Uptime', value: '99.98%', delta: '30 ngày', tone: { color: 'var(--ink)' } },
|
|
418
|
+
];
|
|
419
|
+
|
|
420
|
+
export function DashboardGrid() {
|
|
421
|
+
return (
|
|
422
|
+
<Row gutter={[16, 16]}>
|
|
423
|
+
{STATS.map((s) => (
|
|
424
|
+
<Col key={s.title} xs={24} md={12} xl={6}>
|
|
425
|
+
<Card>
|
|
426
|
+
<Statistic title={s.title} value={s.value} valueStyle={s.tone} suffix={s.delta} />
|
|
427
|
+
</Card>
|
|
428
|
+
</Col>
|
|
429
|
+
))}
|
|
430
|
+
<Col span={24}>
|
|
431
|
+
<Card title="Hoạt động tuần này">…chart/list body…</Card>
|
|
432
|
+
</Col>
|
|
433
|
+
</Row>
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
Ghi chú: `gutter={[ngang, dọc]}` px chảy qua negative-margin + col padding (mechanic antd — không cần tự tính margin). Span **mobile-first**: `xs={24} md={12} xl={6}` = 1 cột mobile / 2 cột tablet / 4 cột desktop; bậc breakpoint không truyền thì kế thừa bậc dưới. Layout 1 trục không cần cột → dùng `Flex` (`gap` thang `small|middle|large`) cho gọn.
|
|
439
|
+
|
|
440
|
+
## 12. Popover hay Tooltip — chọn overlay nào
|
|
441
|
+
|
|
442
|
+
```tsx
|
|
443
|
+
import { Button, Popover, Tooltip } from '@wakii/ui-kit';
|
|
444
|
+
|
|
445
|
+
// Tooltip — hint TEXT thuần, hover/focus là đủ.
|
|
446
|
+
<Tooltip title="Đồng bộ qua gateway, retry 3 lần rồi vào DLQ">
|
|
447
|
+
<Button icon>?</Button>
|
|
448
|
+
</Tooltip>
|
|
449
|
+
|
|
450
|
+
// Popover — nội dung GIÀU: title + ReactNode (list, link, action), trigger click.
|
|
451
|
+
<Popover
|
|
452
|
+
trigger="click"
|
|
453
|
+
title="Người thực hiện"
|
|
454
|
+
content={
|
|
455
|
+
<div className="flex w-56 flex-col gap-2">
|
|
456
|
+
<span>Alice — admin</span>
|
|
457
|
+
<span>Bob — member</span>
|
|
458
|
+
<Button size="small" type="primary">+ Mời</Button>
|
|
459
|
+
</div>
|
|
460
|
+
}
|
|
461
|
+
>
|
|
462
|
+
<Button>Quản lý (3)</Button>
|
|
463
|
+
</Popover>
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
Ghi chú: **Tooltip** = 1 câu hint ≤250px, đóng theo hover/focus, không tương tác được (`pointer-events-none`). **Popover** = panel `role="dialog"` có `title` divider + body ReactNode tự do; mở bằng `trigger` `hover|click|focus` (click dùng cho panel có action — hover làm Panel khó bấm); đóng bằng ESC + click ngoài + (controlled) `onOpenChange`. Popconfirm vẫn là lựa chọn riêng cho xác nhận OK/Huỷ — đừng dựng confirm bằng Popover.
|
|
467
|
+
|
|
468
|
+
---
|
|
469
|
+
|
|
470
|
+
## 13. CrudTableExpandable — Table expandable + Input.Search filter
|
|
471
|
+
|
|
472
|
+
Bảng CRUD có row chi tiết mở được (master-detail) + search filter dùng `Input.Search`.
|
|
473
|
+
Cột expander **tự inject** khi truyền `expandable` — không cần khai cột `expander` thủ công.
|
|
474
|
+
|
|
475
|
+
```tsx
|
|
476
|
+
import { useMemo, useState } from 'react';
|
|
477
|
+
import { Input, Table, Tag } from '@wakii/ui-kit';
|
|
478
|
+
|
|
479
|
+
type Task = { id: string; title: string; status: 'todo' | 'doing' | 'done'; priority: number; note: string };
|
|
480
|
+
const TASKS: Task[] = [
|
|
481
|
+
{ id: '1', title: 'Setup repo', status: 'done', priority: 1, note: 'Scaffold + CI xanh.' },
|
|
482
|
+
{ id: '2', title: 'Thêm endpoint tasks', status: 'doing', priority: 3, note: 'Drizzle + outbox pattern, chưa có retry.' },
|
|
483
|
+
{ id: '3', title: 'E2E saga demo', status: 'todo', priority: 2, note: 'Chạy qua gateway :8080, assert DLQ.' },
|
|
484
|
+
];
|
|
485
|
+
|
|
486
|
+
export function CrudTableExpandable() {
|
|
487
|
+
const [q, setQ] = useState('');
|
|
488
|
+
|
|
489
|
+
const filtered = useMemo(
|
|
490
|
+
() => TASKS.filter((t) => t.title.toLowerCase().includes(q.trim().toLowerCase())),
|
|
491
|
+
[q],
|
|
492
|
+
);
|
|
493
|
+
|
|
494
|
+
return (
|
|
495
|
+
<div className="flex flex-col gap-3">
|
|
496
|
+
{/* Search filter — onSearch fire khi Enter HOẶC click nút (antd parity) */}
|
|
497
|
+
<div className="w-72">
|
|
498
|
+
<Input.Search
|
|
499
|
+
aria-label="Tìm task"
|
|
500
|
+
placeholder="Tìm task…"
|
|
501
|
+
enterButton // true → nút primary icon search; hoặc enterButton="Tìm"
|
|
502
|
+
onSearch={setQ}
|
|
503
|
+
/>
|
|
504
|
+
</div>
|
|
505
|
+
|
|
506
|
+
<Table
|
|
507
|
+
columns={[
|
|
508
|
+
{ key: 'title', title: 'Task', sortable: true },
|
|
509
|
+
{ key: 'status', title: 'Trạng thái', render: (r: Task) => <Tag>{r.status}</Tag> },
|
|
510
|
+
{ key: 'priority', title: 'Ưu tiên', dataType: 'number', align: 'right' },
|
|
511
|
+
]}
|
|
512
|
+
dataSource={filtered}
|
|
513
|
+
rowKey="id"
|
|
514
|
+
emptyMessage={<p className="p-4 text-body text-ink3">Không có task phù hợp.</p>}
|
|
515
|
+
expandable={{
|
|
516
|
+
// Row con chiếm full colspan, nền bg-s3 — render mọi ReactNode
|
|
517
|
+
expandedRowRender: (r) => (
|
|
518
|
+
<div className="text-label text-ink2">
|
|
519
|
+
<p className="font-medium text-ink">{r.title}</p>
|
|
520
|
+
<p className="mt-1">{r.note}</p>
|
|
521
|
+
</div>
|
|
522
|
+
),
|
|
523
|
+
defaultExpandedRowKeys: ['2'], // mở sẵn lúc mount (uncontrolled)
|
|
524
|
+
rowExpandable: (r) => r.status !== 'done', // done không có chi tiết → không render nút chevron
|
|
525
|
+
}}
|
|
526
|
+
/>
|
|
527
|
+
</div>
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
Ghi chú: `onSearch` là **trigger-style** (Enter/click) — muốn lọc live theo từng phím dùng `onChange` của input (hoặc `useDebounce` + `onChange` cho query đắt). Chevron xoay 90° khi mở, nút mang `aria-expanded`; row `rowExpandable: false` không render nút. `expandable` kết hợp được với `paginator` + `selectionMode="checkbox"` (state mở giữ qua trang). Filter-bar cần nổi khi scroll dài → bọc trong `Affix` (`offsetTop={0}`) ngay phía trên `Table`.
|
|
533
|
+
|
|
534
|
+
---
|
|
535
|
+
|
|
536
|
+
## 14. KanbanTaskBoard — KanbanBoard + Tag + Avatar + WIP-limit
|
|
537
|
+
|
|
538
|
+
```tsx
|
|
539
|
+
import { useState } from 'react';
|
|
540
|
+
import { Avatar, KanbanBoard, type KanbanColumnData } from '@wakii/ui-kit';
|
|
541
|
+
|
|
542
|
+
const INITIAL: KanbanColumnData[] = [
|
|
543
|
+
{
|
|
544
|
+
key: 'todo',
|
|
545
|
+
title: 'Cần làm',
|
|
546
|
+
cards: [
|
|
547
|
+
{ key: 't1', title: 'Sửa token contrast', description: 'Dark mode lệch 1 chỗ', tag: 'design' },
|
|
548
|
+
{ key: 't2', title: 'Outbox retry', left: <Avatar size={24}>B</Avatar> },
|
|
549
|
+
],
|
|
550
|
+
},
|
|
551
|
+
{ key: 'doing', title: 'Đang làm', wipLimit: 2, cards: [{ key: 't3', title: 'Rate-limit gateway', tag: 'infra' }] },
|
|
552
|
+
{ key: 'done', title: 'Xong', cards: [] },
|
|
553
|
+
];
|
|
554
|
+
|
|
555
|
+
export function KanbanTaskBoard() {
|
|
556
|
+
const [columns, setColumns] = useState(INITIAL);
|
|
557
|
+
return (
|
|
558
|
+
<KanbanBoard
|
|
559
|
+
columns={columns}
|
|
560
|
+
onCardMove={(cardKey, from, to, toIndex) => {
|
|
561
|
+
setColumns((prev) => {
|
|
562
|
+
const card = prev.flatMap((c) => c.cards).find((c) => c.key === cardKey);
|
|
563
|
+
if (!card) return prev;
|
|
564
|
+
return prev.map((col) => {
|
|
565
|
+
const cards = col.cards.filter((c) => c.key !== cardKey);
|
|
566
|
+
if (col.key === from) return { ...col, cards };
|
|
567
|
+
if (col.key === to) {
|
|
568
|
+
const next = [...cards];
|
|
569
|
+
next.splice(toIndex, 0, card); // toIndex = vị trí SAU khi card rời nguồn
|
|
570
|
+
return { ...col, cards: next };
|
|
571
|
+
}
|
|
572
|
+
return col;
|
|
573
|
+
});
|
|
574
|
+
});
|
|
575
|
+
}}
|
|
576
|
+
/>
|
|
577
|
+
);
|
|
578
|
+
}
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
Ghi chú: **controlled** — board không giữ state, mọi move (kéo chuột HOẶC keyboard) chỉ bắn `onCardMove(cardKey, fromColKey, toColKey, toIndex)`; `toIndex` là vị trí **cuối** trong cột đích sau khi card rời nguồn → dùng đúng công thức remove-then-splice như trên. `tag` tự render `Tag`, slot `left` cho `Avatar` assignee; card hoàn toàn khác → `renderCard(card, columnKey)`. Keyboard đầy đủ: Tab tới card → Space nhặt → ↑↓ đổi vị trí, ←→ đổi cột → Enter thả, Esc hủy — aria-live tự thông báo "Đã chuyển X tới cột Y, vị trí Z". Cột `wipLimit` đầy **từ chối** drop từ cột khác (viền `border-st-fail` + aria-live) nhưng vẫn cho đổi thứ tự trong chính cột đó (count không đổi). ⚠️ Native HTML5 DnD **không hỗ trợ touch** (mobile) — hướng nâng cấp tương lai là peer-dep dnd-kit, đừng tự viết pointer engine.
|
|
582
|
+
|
|
583
|
+
---
|
|
584
|
+
|
|
585
|
+
## 15. WeeklyTimesheet — Timesheet log giờ tuần + Statistic tổng
|
|
586
|
+
|
|
587
|
+
```tsx
|
|
588
|
+
import { useMemo, useState } from 'react';
|
|
589
|
+
import { Card, Statistic, Timesheet, type TimesheetRow } from '@wakii/ui-kit';
|
|
590
|
+
|
|
591
|
+
// values ĐÚNG 7 phần tử theo cột Thứ 2 → Chủ nhật; null = ô trống
|
|
592
|
+
const INITIAL_ROWS: TimesheetRow[] = [
|
|
593
|
+
{ key: 't1', label: 'Task #101', meta: 'Design system', values: [8, 8, 8, 8, 8, null, null] },
|
|
594
|
+
{ key: 't2', label: 'Task #102', meta: 'Gateway', values: [1, 2, 0.5, null, null, null, null] },
|
|
595
|
+
{ key: 't3', label: 'Task #103', meta: 'e2e', values: [null, null, null, 4, 6, 3, null] },
|
|
596
|
+
];
|
|
597
|
+
|
|
598
|
+
export function WeeklyTimesheetPage() {
|
|
599
|
+
const [rows, setRows] = useState(INITIAL_ROWS);
|
|
600
|
+
const [week, setWeek] = useState(new Date()); // bất kỳ ngày nào — component tự chuẩn về Thứ 2
|
|
601
|
+
|
|
602
|
+
const total = useMemo(
|
|
603
|
+
() => rows.reduce((sum, r) => sum + r.values.reduce((a, v) => a + (v ?? 0), 0), 0),
|
|
604
|
+
[rows],
|
|
605
|
+
);
|
|
606
|
+
|
|
607
|
+
return (
|
|
608
|
+
<div className="flex flex-col gap-4">
|
|
609
|
+
<Card>
|
|
610
|
+
<Statistic title="Tổng giờ tuần này" value={total} suffix="h" />
|
|
611
|
+
</Card>
|
|
612
|
+
<Timesheet
|
|
613
|
+
rows={rows}
|
|
614
|
+
week={week}
|
|
615
|
+
editable
|
|
616
|
+
locale="vi"
|
|
617
|
+
onWeekChange={setWeek}
|
|
618
|
+
onCellChange={(rowKey, dayIndex, value) =>
|
|
619
|
+
setRows((prev) =>
|
|
620
|
+
prev.map((r) =>
|
|
621
|
+
r.key === rowKey
|
|
622
|
+
? { ...r, values: r.values.map((v, i) => (i === dayIndex ? value : v)) }
|
|
623
|
+
: r,
|
|
624
|
+
),
|
|
625
|
+
)
|
|
626
|
+
}
|
|
627
|
+
/>
|
|
628
|
+
</div>
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
Ghi chú: tuần **luôn bắt đầu Thứ 2** cả vi/en — `week` nhận bất kỳ Date nào trong tuần; nav prev/next bắn `onWeekChange` (Date = Thứ 2 tuần mới) và grid tự replay fade-in. Ô nhập clamp **0–24, step 0.5**; xoá ô → `onCellChange(…, null)` — state `rows` là nguồn sự thật, footer tổng cột + grand tự tính lại (bỏ `editable` → readonly text). Cột T7/CN đã nền nhẹ sẵn; `locale="en"` đổi headers + nhãn tuần sang `Oct 19–25, 2026`. Đang fetch → `loading` thay thân bảng bằng skeleton (header + nhãn tuần giữ nguyên). Tổng tuần trên `Statistic` tính consumer-side từ `rows` (tabular-nums built in).
|
|
634
|
+
|
|
635
|
+
## 16. LandingAlive — Reveal + Parallax hero + CountUp KPI (Statistic + Grid)
|
|
636
|
+
|
|
637
|
+
```tsx
|
|
638
|
+
import { CountUp, Parallax, Reveal, Col, Row, Statistic } from '@wakii/ui-kit';
|
|
639
|
+
|
|
640
|
+
type Kpi = { title: string; value: number; decimals?: number; suffix: string };
|
|
641
|
+
|
|
642
|
+
const KPIS: Kpi[] = [
|
|
643
|
+
{ title: 'Tasks completed', value: 1284, suffix: 'tasks' },
|
|
644
|
+
{ title: 'On-time rate', value: 96.4, decimals: 1, suffix: '%' },
|
|
645
|
+
{ title: 'Active teams', value: 327, suffix: 'teams' },
|
|
646
|
+
];
|
|
647
|
+
|
|
648
|
+
export function LandingAlive() {
|
|
649
|
+
return (
|
|
650
|
+
<div className="flex flex-col gap-10">
|
|
651
|
+
{/* Hero — ảnh trôi chậm hơn trang (speed 0.3 + scale nhẹ); tự tắt khi reduced-motion */}
|
|
652
|
+
<Parallax speed={0.3} scale className="overflow-hidden rounded-xl">
|
|
653
|
+
<img src="/hero.jpg" alt="Product hero" className="aspect-video w-full object-cover" />
|
|
654
|
+
</Parallax>
|
|
655
|
+
|
|
656
|
+
{/* KPI row — card trôi vào lệch pha, số đếm ease-out trong Statistic */}
|
|
657
|
+
<Row gutter={[24, 24]}>
|
|
658
|
+
{KPIS.map((kpi, i) => (
|
|
659
|
+
<Col key={kpi.title} xs={24} md={8}>
|
|
660
|
+
<Reveal direction="up" delay={i * 150}>
|
|
661
|
+
<div className="h-full rounded-xl border border-line bg-s1 p-5">
|
|
662
|
+
<Statistic
|
|
663
|
+
title={kpi.title}
|
|
664
|
+
value={<CountUp value={kpi.value} decimals={kpi.decimals} />}
|
|
665
|
+
suffix={kpi.suffix}
|
|
666
|
+
/>
|
|
667
|
+
</div>
|
|
668
|
+
</Reveal>
|
|
669
|
+
</Col>
|
|
670
|
+
))}
|
|
671
|
+
</Row>
|
|
672
|
+
|
|
673
|
+
{/* Feature pair — vào lần lượt từ 2 bên */}
|
|
674
|
+
<div className="grid gap-6 md:grid-cols-2">
|
|
675
|
+
<Reveal direction="left">
|
|
676
|
+
<div className="rounded-xl border border-line bg-s1 p-5 text-body text-ink2">
|
|
677
|
+
Real-time qua Kafka — activity feed cập nhật tức thì.
|
|
678
|
+
</div>
|
|
679
|
+
</Reveal>
|
|
680
|
+
<Reveal direction="right">
|
|
681
|
+
<div className="rounded-xl border border-line bg-s1 p-5 text-body text-ink2">
|
|
682
|
+
Module Federation — remote độc lập deploy, shell ghép runtime.
|
|
683
|
+
</div>
|
|
684
|
+
</Reveal>
|
|
685
|
+
</div>
|
|
686
|
+
</div>
|
|
687
|
+
);
|
|
688
|
+
}
|
|
689
|
+
```
|
|
690
|
+
|
|
691
|
+
Ghi chú: **an toàn a11y có sẵn** — `Parallax` tự tắt khi `prefers-reduced-motion` (qua `useMediaQuery`), animation Reveal/CountUp rơi về tức thì nhờ block global; không cần nhánh UI riêng. `Reveal once` mặc định chỉ chạy 1 lần (IO disconnect sau khi vào) — thêm `once={false}` nếu muốn replay khi cuộn lại. `CountUp` đổi `value` giữa chừng sẽ animate tiếp **từ số đang hiển thị** (hợp poll/refresh số liệu); `decimals`/`format` định dạng hiển thị (`format={(n) => n.toLocaleString('vi-VN')}` cho grouping). `Parallax` chỉ track cửa sổ — đặt ngoài container `overflow: hidden` cuộn riêng. Tất cả 0 deps, chỉ animate `transform`/`opacity` (GPU).
|
|
692
|
+
|
|
693
|
+
## 17. DynamicEmailsForm — useForm + useFieldArray (mảng động + zod validate từng row)
|
|
694
|
+
|
|
695
|
+
Form động kiểu antd `Form.List`: danh sách email thêm/xoá/đảo, zod validate **từng row** qua
|
|
696
|
+
path lồng `emails.<i>.email`. `nameAt` đặt name đúng path → focus-first-invalid của
|
|
697
|
+
`form.handleSubmit` tự vào đúng ô sai.
|
|
698
|
+
|
|
699
|
+
```tsx
|
|
700
|
+
import { useState } from 'react';
|
|
701
|
+
import { z } from 'zod';
|
|
702
|
+
import { Button, Field, Input, useForm, useFieldArray } from '@wakii/ui-kit';
|
|
703
|
+
|
|
704
|
+
const schema = z.object({
|
|
705
|
+
emails: z
|
|
706
|
+
.array(z.object({ email: z.string().email('Email không hợp lệ') }))
|
|
707
|
+
.min(1, 'Cần ít nhất 1 email'),
|
|
708
|
+
});
|
|
709
|
+
type InviteForm = z.infer<typeof schema>;
|
|
710
|
+
type EmailRow = InviteForm['emails'][number];
|
|
711
|
+
|
|
712
|
+
export function DynamicEmailsForm({ onInvite }: { onInvite: (emails: string[]) => Promise<void> }) {
|
|
713
|
+
const form = useForm<InviteForm>({ schema, mode: 'onChange', initialValues: { emails: [{ email: '' }] } });
|
|
714
|
+
const arr = useFieldArray<InviteForm, 'emails', EmailRow>(form, 'emails');
|
|
715
|
+
const [sending, setSending] = useState(false);
|
|
716
|
+
|
|
717
|
+
return (
|
|
718
|
+
<form
|
|
719
|
+
className="flex w-96 flex-col gap-2"
|
|
720
|
+
onSubmit={form.handleSubmit(async (d) => {
|
|
721
|
+
setSending(true);
|
|
722
|
+
try {
|
|
723
|
+
await onInvite(d.emails.map((r) => r.email));
|
|
724
|
+
} finally {
|
|
725
|
+
setSending(false);
|
|
726
|
+
}
|
|
727
|
+
})}
|
|
728
|
+
>
|
|
729
|
+
{arr.fields.map((row, i) => (
|
|
730
|
+
<Field key={i} label={i === 0 ? 'Email người nhận' : undefined} htmlFor={`inv-${i}`}
|
|
731
|
+
error={form.errors[`emails.${i}.email`]}>
|
|
732
|
+
<div className="flex gap-2">
|
|
733
|
+
<Input id={`inv-${i}`} name={arr.nameAt(i, 'email')} placeholder="name@company.vn"
|
|
734
|
+
value={row.email} invalid={!!form.errors[`emails.${i}.email`]}
|
|
735
|
+
onChange={(e) => arr.update(i, { email: e.target.value })} />
|
|
736
|
+
<Button type="text" aria-label={`Xoá email ${i + 1}`} onClick={() => arr.remove(i)}>×</Button>
|
|
737
|
+
</div>
|
|
738
|
+
</Field>
|
|
739
|
+
))}
|
|
740
|
+
<div className="flex items-center gap-2">
|
|
741
|
+
<Button type="default" onClick={() => arr.append({ email: '' })}>+ Thêm email</Button>
|
|
742
|
+
<Button type="text" disabled={arr.count < 2} onClick={() => arr.swap(0, arr.count - 1)}>Đảo đầu–cuối</Button>
|
|
743
|
+
<Button htmlType="submit" type="primary" loading={sending}>Gửi mời</Button>
|
|
744
|
+
</div>
|
|
745
|
+
</form>
|
|
746
|
+
);
|
|
747
|
+
}
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
Ghi chú: **path lồng** — zod issues tự map thành `errors['emails.<i>.email']` (useForm join
|
|
751
|
+
`'.'`), `Field error=` đọc đúng key đó. **Lỗi đi theo row**: mỗi op cấu trúc (`append`/
|
|
752
|
+
`remove`/`move`/`swap`/`update`) rebuild errors từ snapshot mới — xoá row giữa thì lỗi dồn
|
|
753
|
+
index đúng. **`mode: 'onChange'`** để validate từng row khi gõ (lỗi tự tắt khi sửa đúng);
|
|
754
|
+
`nameAt(i, key)` đặt name attr → submit sai tự focus ô đầu tiên. Mỗi event chỉ gọi **1 op**
|
|
755
|
+
(hook thuần closure — 2 op liên tiếp trong 1 handler thì op sau thấy state cũ). Reorder
|
|
756
|
+
nhiều row: `move(from, to)` là insert-at kiểu RHF; `swap` đổi chỗ 2 row.
|
|
757
|
+
|
|
758
|
+
---
|
|
759
|
+
|
|
760
|
+
## 18. NavAppĐaCấp — Menubar vs MegaMenu vs SlideMenu: khi nào cái nào
|
|
761
|
+
|
|
762
|
+
Ba mức nav ngang — chọn theo ĐỘ SÂU + ĐỘ RỘNG nội dung:
|
|
763
|
+
|
|
764
|
+
| | `Menubar` | `MegaMenu` | `SlideMenu` |
|
|
765
|
+
|---|---|---|---|
|
|
766
|
+
| Cấu trúc | dropdown **1 cấp** gọn | panel **multi-column** (group = cột → leaf) | các **cấp trượt ngang**, đệ quy không giới hạn |
|
|
767
|
+
| Khi nào | app header ít mục, mỗi mục vài action | landing/app header nhiều NHÓM chức năng (sản phẩm, tài nguyên, quản trị) | panel hẹp / mobile / hamburger / cây phân loại sâu |
|
|
768
|
+
| Mở/đóng | hover | hover HOẶC click · ESC/click-ngoài/leave | click vào cấp · back bar / ESC |
|
|
769
|
+
| Không dùng khi | cần panel nhóm nhiều cột → MegaMenu | không gian hẹp (mobile) → SlideMenu | top bar desktop nhiều cột song song → MegaMenu |
|
|
770
|
+
|
|
771
|
+
```tsx
|
|
772
|
+
import { useState } from 'react';
|
|
773
|
+
import { Card, MegaMenu, SlideMenu, type MegaMenuItem, type SlideMenuItem } from '@wakii/ui-kit';
|
|
774
|
+
|
|
775
|
+
const MEGA: MegaMenuItem[] = [
|
|
776
|
+
{
|
|
777
|
+
key: 'product',
|
|
778
|
+
label: 'Sản phẩm',
|
|
779
|
+
children: [
|
|
780
|
+
{
|
|
781
|
+
key: 'work',
|
|
782
|
+
label: 'Workspace',
|
|
783
|
+
items: [
|
|
784
|
+
{ key: 'board', label: 'Board' },
|
|
785
|
+
{ key: 'list', label: 'List view' },
|
|
786
|
+
],
|
|
787
|
+
},
|
|
788
|
+
{
|
|
789
|
+
key: 'insight',
|
|
790
|
+
label: 'Insights',
|
|
791
|
+
items: [
|
|
792
|
+
{ key: 'charts', label: 'Charts' },
|
|
793
|
+
{ key: 'reports', label: 'Reports' },
|
|
794
|
+
],
|
|
795
|
+
},
|
|
796
|
+
],
|
|
797
|
+
},
|
|
798
|
+
{ key: 'pricing', label: 'Giá', onClick: () => console.log('pricing') },
|
|
799
|
+
];
|
|
800
|
+
|
|
801
|
+
const SLIDE: SlideMenuItem[] = [
|
|
802
|
+
{
|
|
803
|
+
key: 'projects',
|
|
804
|
+
label: 'Dự án',
|
|
805
|
+
children: [
|
|
806
|
+
{
|
|
807
|
+
key: 'active',
|
|
808
|
+
label: 'Đang chạy',
|
|
809
|
+
children: [{ key: 'vu53', label: 'VU-53 redesign' }],
|
|
810
|
+
},
|
|
811
|
+
{ key: 'archived', label: 'Lưu trữ' },
|
|
812
|
+
],
|
|
813
|
+
},
|
|
814
|
+
{ key: 'settings', label: 'Cài đặt' },
|
|
815
|
+
];
|
|
816
|
+
|
|
817
|
+
export function NavAppDaCap({ isMobile }: { isMobile: boolean }) {
|
|
818
|
+
const [lastPick, setLastPick] = useState('—');
|
|
819
|
+
return isMobile ? (
|
|
820
|
+
// Mobile/panel hẹp: SlideMenu — cấp sâu trong footer drawer
|
|
821
|
+
<Card title="Menu">
|
|
822
|
+
<SlideMenu items={SLIDE} ariaLabel="Menu chính" className="w-full" />
|
|
823
|
+
</Card>
|
|
824
|
+
) : (
|
|
825
|
+
// Desktop: MegaMenu làm top bar — nhóm chức năng duyệt theo cột
|
|
826
|
+
<Card title="Sản phẩm & more">
|
|
827
|
+
<MegaMenu
|
|
828
|
+
items={MEGA.map((m) =>
|
|
829
|
+
m.key === 'pricing'
|
|
830
|
+
? { ...m, onClick: () => setLastPick('pricing') }
|
|
831
|
+
: m,
|
|
832
|
+
)}
|
|
833
|
+
className="rounded-lg"
|
|
834
|
+
/>
|
|
835
|
+
<p className="m-0 mt-3 text-meta text-ink3">Đã chọn: {lastPick}</p>
|
|
836
|
+
</Card>
|
|
837
|
+
);
|
|
838
|
+
}
|
|
839
|
+
```
|
|
840
|
+
|
|
841
|
+
Ghi chú: cả 3 cùng ngôn ngữ nav — item `key`/`label`/`onClick`, `danger` đỏ, đóng bằng ESC.
|
|
842
|
+
MegaMenu panel là `role=menu` + `role=menuitem`, trigger có `aria-expanded`; SlideMenu trang ẩn
|
|
843
|
+
`inert` + `aria-hidden` (không phá tab order), back bar kèm nhãn cấp cha. Menubar/MegaMenu mở
|
|
844
|
+
theo hover (desktop) — với touch cứ click. Cần nav đa cấp DỌC trong sidebar → dùng `Menu`
|
|
845
|
+
(đa cấp) hoặc `Sidebar` + `MenuCompactContext`, không nhét SlideMenu vào sidebar.
|
|
846
|
+
|
|
847
|
+
## 19. MotionComposable — Transition + Reveal + Parallax cộng hưởng trên landing
|
|
848
|
+
|
|
849
|
+
```tsx
|
|
850
|
+
import { useState } from 'react';
|
|
851
|
+
import { Button, CollapseTransition, CountUp, Parallax, Reveal, Row, Col, Statistic } from '@wakii/ui-kit';
|
|
852
|
+
|
|
853
|
+
// Mỗi motion primitiveOWN đúng 1 trục, không giành transform trên CÙNG element:
|
|
854
|
+
// Parallax = scroll · Reveal = viewport-enter · Transition = state in/out.
|
|
855
|
+
// Kết hợp bằng CÁCH BỌC NESTED (mỗi primitive một element riêng).
|
|
856
|
+
|
|
857
|
+
export function LandingMotionComposable() {
|
|
858
|
+
const [openFeature, setOpenFeature] = useState(false);
|
|
859
|
+
|
|
860
|
+
return (
|
|
861
|
+
<div className="flex flex-col gap-12">
|
|
862
|
+
{/* 1) Hero — Parallax trôi chậm khi scroll (self-tắt khi reduced-motion) */}
|
|
863
|
+
<Parallax speed={0.3} scale className="overflow-hidden rounded-xl">
|
|
864
|
+
<img src="/hero.jpg" alt="Product hero" className="aspect-video w-full object-cover" />
|
|
865
|
+
</Parallax>
|
|
866
|
+
|
|
867
|
+
{/* 2) KPI — Reveal stagger khi cuộn tới + CountUp đếm ease-out */}
|
|
868
|
+
<Row gutter={[24, 24]}>
|
|
869
|
+
{[
|
|
870
|
+
{ title: 'Tasks shipped', value: 1284, suffix: '' },
|
|
871
|
+
{ title: 'Uptime', value: 99.95, decimals: 2, suffix: '%' },
|
|
872
|
+
{ title: 'Teams onboard', value: 327, suffix: '+' },
|
|
873
|
+
].map((kpi, i) => (
|
|
874
|
+
<Col key={kpi.title} xs={24} md={8}>
|
|
875
|
+
<Reveal direction="up" delay={i * 150}>
|
|
876
|
+
<div className="h-full rounded-xl border border-line bg-s1 p-5">
|
|
877
|
+
<Statistic
|
|
878
|
+
title={kpi.title}
|
|
879
|
+
value={<CountUp value={kpi.value} decimals={kpi.decimals} />}
|
|
880
|
+
suffix={kpi.suffix}
|
|
881
|
+
/>
|
|
882
|
+
</div>
|
|
883
|
+
</Reveal>
|
|
884
|
+
</Col>
|
|
885
|
+
))}
|
|
886
|
+
</Row>
|
|
887
|
+
|
|
888
|
+
{/* 3) Feature card — Reveal đưa card vào, CollapseTransition mở details (state do user) */}
|
|
889
|
+
<Reveal direction="left" className="max-w-xl">
|
|
890
|
+
<div className="rounded-xl border border-line bg-s1 p-5">
|
|
891
|
+
<h3 className="m-0 text-h3 font-semibold text-ink">Zero-config motion</h3>
|
|
892
|
+
<p className="mt-1 text-body text-ink2">
|
|
893
|
+
Scroll-reveal từ Reveal, chiều cao đàn hồi từ CollapseTransition — hai primitive,
|
|
894
|
+
hai element, không xung đột transform.
|
|
895
|
+
</p>
|
|
896
|
+
<Button size="small" onClick={() => setOpenFeature((o) => !o)}>
|
|
897
|
+
{openFeature ? 'Hide details' : 'Show details'}
|
|
898
|
+
</Button>
|
|
899
|
+
{/* padding nằm trong children — tầng overflow-hidden phải clip sạch */}
|
|
900
|
+
<CollapseTransition in={openFeature}>
|
|
901
|
+
<p className="m-0 pt-3 text-body text-ink2">
|
|
902
|
+
CollapseTransition unmount sau khi exit animation chạy xong — DOM sạch,
|
|
903
|
+
không banner ẩn chiếm chỗ.
|
|
904
|
+
</p>
|
|
905
|
+
</CollapseTransition>
|
|
906
|
+
</div>
|
|
907
|
+
</Reveal>
|
|
908
|
+
</div>
|
|
909
|
+
);
|
|
910
|
+
}
|
|
911
|
+
```
|
|
912
|
+
|
|
913
|
+
Ghi chú: quy tắc **composable** — (1) mỗi primitive bọc một element riêng, KHÔNG gộp 2 hiệu ứng lên cùng div (transform/opacity ghi đè nhau); (2) `Reveal` quản entrance, `Transition`/`CollapseTransition` quản state in/out do user, `Parallax` quản scroll — trục nào về nó; (3) reduced-motion tắt cả ba nhờ block global (`Parallax` còn tự ngắt listener qua `useMediaQuery`). `CollapseTransition` là tên barrel của `Collapse` trong `ui/transition` (trùng tên `Collapse` — xem AGENTS.md nhóm Theming & Motion).
|
|
914
|
+
|
|
915
|
+
---
|
|
916
|
+
|
|
917
|
+
|
|
918
|
+
## Bảng "khi nào dùng" — nhóm Primitives + Navigation
|
|
919
|
+
|
|
920
|
+
| Component | Khi nào dùng | Đừng dùng khi |
|
|
921
|
+
|---|---|---|
|
|
922
|
+
| `Alert` | Thông báo ngữ cảnh nằm trong trang (filled mặc định) | toast tạm → dùng `message` |
|
|
923
|
+
| `Avatar` / `AvatarGroup` | Đại diện user; nhóm chồng + max "+N" | — |
|
|
924
|
+
| `Badge` | Số đếm/ trạng thái nhỏ trên icon, pill delta | nội dung dài → Tag |
|
|
925
|
+
| `BusyBox` | Bọc khối đang loading (thay Skeleton thủ công) | loading cả trang → Skeleton page |
|
|
926
|
+
| `Button` | Mọi action; `htmlType` mới là type HTML; `loading` chặn double-submit | link điều hướng → `<Link>`/nav |
|
|
927
|
+
| `Breadcrumb` | Đường phân cấp trang chi tiết | nav chính → Menu/Tabs |
|
|
928
|
+
| `Card` | Khối nội dung có viền/đề (anchor: rounded + title/extra) | layout vô hình → div |
|
|
929
|
+
| `ContextMenu` | Menu chuột phải | menu thường → Dropdown/Menu |
|
|
930
|
+
| `Divider` | Phân cách nội dung (ngang/dọc, text, dashed) | — |
|
|
931
|
+
| `Dropdown` | Menu gắn trigger bất kỳ; `---` làm separator | menu đa cấp sidebar → Menu |
|
|
932
|
+
| `EmptyState` | Danh sách/region rỗng (title + action) | lỗi → Result |
|
|
933
|
+
| `Icon` | Mọi glyph (48 tên primeicons) | — |
|
|
934
|
+
| `Image` | Ảnh có preview overlay | avatar → Avatar |
|
|
935
|
+
| `Input` / `Textarea` | Ô nhập; `invalid` + `Field` = form | số → InputNumber |
|
|
936
|
+
| `List` | Danh sách item + loading/empty/pagination | bảng dữ liệu → Table |
|
|
937
|
+
| `Menu` | Nav đa cấp sidebar (vertical) / thanh (horizontal) | action đơn → Dropdown |
|
|
938
|
+
| `Menubar` | Thanh menu desktop hover | trong trang → Tabs |
|
|
939
|
+
| `Modal` | Xác nhận/form chặn tương tác | phi-chặn → Drawer/Popconfirm |
|
|
940
|
+
| `Pagination` | Phân trang client-side | infinite scroll |
|
|
941
|
+
| `Popconfirm` | Xác nhận nhanh trước action nguy hiểm | form dài → Modal |
|
|
942
|
+
| `Segmented` | Đổi view cùng ngữ cảnh | nav routing → Tabs/TabMenu |
|
|
943
|
+
| `Sidebar` | Khung app chính, collapse icon-only | landing → nav thường |
|
|
944
|
+
| `Skeleton` | Placeholder trước khi có data | — |
|
|
945
|
+
| `Space` | Gap flex nhanh | layout phức tạp → grid |
|
|
946
|
+
| `Spinner` | Loading điểm lẻ | khối → Skeleton/BusyBox |
|
|
947
|
+
| `SplitButton` | Action chính + biến thể phụ | 2 action ngang hàng → 2 Button |
|
|
948
|
+
| `Steps` | Tiến trình nhiều bước | trạng thái đơn → Tag/Progress |
|
|
949
|
+
| `TabMenu` | Nav dạng tab (đổi view) | nội dung trong tab → Tabs |
|
|
950
|
+
| `Tabs` | Chia nội dung cùng trang | nav routing → TabMenu |
|
|
951
|
+
| `Tag` | Nhãn trạng thái/phân loại | số đếm → Badge |
|
|
952
|
+
| `Timeline` | Chuỗi sự kiện theo thời gian | — |
|
|
953
|
+
| `Toolbar` | Cụm công cụ trên cùng hàng | — |
|
|
954
|
+
| `Tooltip` | Hint khi hover/focus icon | nội dung dài → Popover (backlog) |
|
|
955
|
+
|
|
956
|
+
*Nhóm Forms / Data Display / Feedback — worker khác bổ sung sau.*
|