feat: Extract UI components from trakli/webui with Nuxt playground explorer#1
Conversation
Code Review SummaryThis PR successfully extracts a comprehensive set of UI components and provides a high-quality playground environment. The main focus for improvement should be decoupling the components from the financial application's specific domain logic to ensure they function as generic, reusable primitives. 🚀 Key Improvements
💡 Minor Suggestions
🚨 Critical Issues
|
| @@ -0,0 +1,142 @@ | |||
| <script setup> | |||
| import { computed, ref, h, onErrorCaptured, markRaw } from 'vue'; | |||
There was a problem hiding this comment.
The 'watch' function is imported later in the script block (line 27). For consistency and clarity, move all Vue core imports to the top of the file.
| import { computed, ref, h, onErrorCaptured, markRaw } from 'vue'; | |
| import { computed, ref, h, onErrorCaptured, markRaw, watch } from 'vue'; |
|
|
||
| // Reset any prior error whenever the selection changes. | ||
| const selectedKey = computed(() => props.selected); | ||
| import { watch } from 'vue'; |
There was a problem hiding this comment.
Move this import to the top of the script block to follow standard style guides and improve readability.
| import { watch } from 'vue'; | |
| watch(selectedKey, () => { renderError.value = null; }); |
| <script setup> | ||
| import { ref, computed, watch } from 'vue'; | ||
| import IconPicker from './IconPicker.vue'; | ||
| import * as lucideIcons from 'lucide-vue-next'; |
There was a problem hiding this comment.
Importing the entire Lucide icon library will significantly increase the bundle size for anyone consuming this UI kit, even if they only use a few components. Consider using dynamic imports or a pre-defined set of available icons to allow for better tree-shaking.
| import * as lucideIcons from 'lucide-vue-next'; | |
| // Consider importing specific icons or using a dynamic resolution helper | |
| import { ImagePlus, X } from 'lucide-vue-next'; |
| const chatWindow = ref<HTMLElement | null>(null); | ||
| const composerEl = ref<InstanceType<typeof ChatComposer> | null>(null); | ||
|
|
||
| watch( |
There was a problem hiding this comment.
Watching for both length changes and status changes to trigger a scroll-to-bottom might cause redundant render cycles or double-scrolling. Consider consolidating these into a single watcher or using a throttled version.
| watch( | |
| watch( | |
| () => props.currentSession?.messages, | |
| () => scrollToBottom(), | |
| { deep: true } | |
| ); |
nfebe
left a comment
There was a problem hiding this comment.
Looks good so far. Things to address before we can merge:
| Finding | Where | Severity |
|---|---|---|
The package doesn't carry its own assets. public/ isn't published, but Logo, EmptyState, TipsSection, AuthCarousel point at absolute paths like /logo.svg, so they 404 in a consumer; AuthCarousel points at /floating-docs-man.svg, which isn't in the repo at all. Logo should carry the mark: inline the Trakli SVG so it ships, add an icon-only variant and a default slot to swap it, and drive its fill from a token. |
package.json files; Logo.vue:1-30, EmptyState.vue, TipsSection.vue, AuthCarousel.vue |
Blocker |
Overriding the brand colour doesn't fully re-skin. The focus ring is a literal green rgba and the table header repeats the primary hex, so a tier that overrides --color-primary keeps both. Use the token: rgba(var(--color-primary-rgb), .5) and var(--color-primary). |
_vars.scss:40, tokens.css:37 |
Blocker for the rebrand |
The playground chrome doesn't use the kit: the sidebar and landing are hand-built with 61 hex values, one token reference, and no kit components, so it can't re-skin. Rebuild it from TPageShell/TPanel/TCard/TButton/SearchInput reading var(--color-*). |
index.vue, UsageLanding.vue |
Should fix |
| New components list but don't preview. Discovery is automatic, but a usable preview needs a hand-written entry in the demo map (99 of 101 filled); without one the component renders propless and a data-driven one lands in the error box. Co-locate the sample data with each component, or reuse the Storybook stories. | registry.ts:10,825, DashboardMain.vue:20-23 |
Should fix |
A few components write hex where the token exists (#dc2626 is --color-error; #b45309 isn't a token at all), so they don't re-skin or adapt to dark mode. |
GroupForm.vue:232, TransactionFilters.vue:371, TTableComponent.vue:567, TTransactionsCardList.vue:293 |
Should fix |
README is ahead of the code (TTopCard, ThemeToggleButton listed as not-yet-decoupled but both ship here), and there are em dashes in on-screen copy. |
README.md; UsageLanding.vue:8,16,20,32,77,95,104 |
Minor |
Land the first two before adoption; they carry the old brand into whatever consumes this. The playground chrome and the demo map are one rework. A CI check that fails on raw hex in components/*.vue (allowlisting brand-icon and chart-palette files) keeps the contract from drifting.
Caveat: I read the playground source but didn't run it, so the "can't re-skin" and "lands in the error box" notes are read off the code, not watched in a browser.
- Logo: inline SVG with variant prop (full/icon), slot override, fills driven by --color-primary and --color-accent tokens - EmptyState: inline box.svg with named icon slot for consumer override - TipsSection: inline bulbIcon.svg with named icon slot for consumer override - AuthCarousel: replace missing floating-docs-man.svg background with placeholder SVG and image slot - _vars.scss: replace hardcoded focus ring rgba with rgba(var(--color-primary-rgb), 0.5) - tokens.css: --color-table-header now references var(--color-primary) instead of duplicating hex literal - Update Cypress tests to match new inline SVG structure
…nts in playground - Add missing tokens: --color-accent-rgb, --color-accent-dark (light + dark mode) - GroupForm, TransactionFilters, TTableComponent, TTransactionsCardList: replace hardcoded hex with SCSS tokens - Playground index.vue: replace sidebar with TPanel, buttons with TButton, search with SearchInput - Playground UsageLanding.vue: replace keypoint cards with TCard - Remove ~100 lines of custom CSS (green gradient sidebar, button/input styles) - All remaining colors now use CSS custom properties Satisfies PR review Should fix trakli#1 (hardcoded colors) and #2 (dogfood kit components).
…d registry Satisfies PR review Should fix #4 — these 2 components previously rendered empty when selected.
Move TTopCard and ThemeToggleButton from Deferred to Primitives — both already ship in the kit.
|
|
||
| defineEmits(['edit', 'delete', 'recurrent', 'page-change', 'update:searchQuery', 'toggle-filters']); | ||
|
|
||
| const CURRENCY_RATES = { |
There was a problem hiding this comment.
Hardcoding currency exchange rates within a UI component is a significant architectural flaw for a reusable library. This logic belongs in the application layer. The table should receive pre-calculated values or a conversion function as a prop.
| const CURRENCY_RATES = { | |
| // Remove hardcoded CURRENCY_RATES. Instead, accept pre-calculated totals via props or pass a conversion handler. |
|
|
||
| defineEmits(['add', 'navigate-home']); | ||
|
|
||
| const iconMap = { |
There was a problem hiding this comment.
This internal mapping couples the UI component to specific application features (Wallets, Parties, etc.). To make this a generic UI kit primitive, accept the icon component directly via a prop or a named slot instead of maintaining an internal registry of app-specific strings.
| const iconMap = { | |
| const props = defineProps({ | |
| // ... other props | |
| icon: { type: [Object, Function], default: null } | |
| }); | |
| const resolvedIcon = computed(() => props.icon); |
| defineEmits(['drill']); | ||
|
|
||
| const fill = (template, vars) => | ||
| Object.entries(vars).reduce((s, [k, v]) => s.replace(`{${k}}`, v), template); |
There was a problem hiding this comment.
The current .replace() implementation only handles the first occurrence of a placeholder. Using .replaceAll() or a global regex is more robust for localization strings that may reuse a variable (e.g., '{count} items out of {count}').
| Object.entries(vars).reduce((s, [k, v]) => s.replace(`{${k}}`, v), template); | |
| Object.entries(vars).reduce((s, [k, v]) => s.replaceAll(`{${k}}`, String(v)), template); |
| import { Bot, User } from 'lucide-vue-next'; | ||
|
|
||
| interface ChatMessageResult { |
There was a problem hiding this comment.
These TypeScript interfaces are duplicated in multiple AI-related components. Move them to a shared types/chat.ts file to ensure consistency and easier maintenance across the kit.
| import { Bot, User } from 'lucide-vue-next'; | |
| interface ChatMessageResult { | |
| import type { ChatMessage, ChatSession, ChatMessageResult } from '../types/chat'; |
nfebe
left a comment
There was a problem hiding this comment.
There are still lots of issues that have transferred from the application itself but here is an opportunity to solve it.
Example there is GroupForm, BudgetForm, TransferForm all of those are the same elements. Should be TForm and TFormInput etc
Same with things like ReportTab we should need a tabbing system. So A LOT about the structure has not change to have real primitives that still rebuild the current UI correctly.
Summary
Extracted the reusable UI components (buttons, cards, tables, charts, forms, modals, auth, and AI chat) out of
trakli/webuiinto this UI-kit repo, and added a Nuxt playground so every component is live-previewable with realistic sample data.What changed
components/, covering:.stories.js) and Cypress component tests (.cy.js).playground/Nuxt app — a Nuxt layer host (extends: ['..']) that pulls in the kit's components and design tokens.app/pages/index.vue: sidebar dashboard listing every component grouped by category, with search and live rendering.app/registry.ts: auto-discovers allcomponents/*.vue, hand-tunes prop/slot demos with realistic sample data, and auto-buckets components into categories.app/components/UsageLanding.vue: the/page documenting installation & usage.package.json: addeddev(nuxi dev .playground) andbuild(nuxt build .playground) scripts.public/: static assets (logo, icons).SYNC.mdis intentionally left out of the commit (local sync notes).How to review
npm install npm run dev # opens the playground at / — landing page explains usage, sidebar previews each componentNotes
trakli/webui; this commit seeds them here as a standalone kit.@trakli/ui-kitalias locally; the published layer relies on the real npm alias.@nfebe