feat: Extract UI components from trakli/webui with Nuxt playground explorer - #1
Lantum-Brendan wants to merge 29 commits into
Conversation
Code Review SummaryThis PR successfully extracts nearly 100 components from the 🚀 Key Improvements
💡 Minor Suggestions
🚨 Critical Issues
|
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 trakli#2 (dogfood kit components).
…d registry Satisfies PR review Should fix trakli#4 — these 2 components previously rendered empty when selected.
Move TTopCard and ThemeToggleButton from Deferred to Primitives — both already ship in the kit.
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.
…main components - Extract form primitives (TForm, TFormField, TFormInput, TFormSelect, TFormTextarea, TFormRow) and refactor GroupForm, BudgetForm, TransferForm, CategoryForm, PartiesForm, WalletForm, and ReminderForm. - Extract tab primitives (TTab, TTabList, TTabPanel) and refactor ReportsTabs, CalendarTab, CategoriesTab, FlowTab, and ChartsTab. - Extract modal primitives (TModal, TModalHeader, TModalBody, TModalFooter) and refactor ConfirmModal, LearningModal, RecurringModal, PasswordModal, and ImportConfirmDialog. - Extract toolbar, pagination, and action primitives (TListHeader, TFilterToggle, TPagination, TEmptyState, TCardActionMenu, TSectionHeader) and refactor TTableComponent, TTransactionsCardList, ContentListView, PartyCardList, and ContentCardGrid. - Extract shared string template helper to utils/fill.ts and Lucide icon resolution to composables/useLucideIcon.ts. - Ensure all T* primitives contain zero domain logic, preserving full backward compatibility across all domain component props/emits interfaces.
…ble test selectors
- Add 11 presentational AI chat components from webui: ChatProgressSteps, ChatCalloutBlock, ChatChartBlock, ChatComparisonBlock, ChatKpiBlock, ChatListBlock, ChatProgressBlock, ChatQuestionBlock, ChatQuickActionsBlock, ChatTableBlock, ChatTimelineBlock - Add OnboardingWizard component - Add vue3-apexcharts as peer dependency for ChatChartBlock - Add playground demo configurations for all new components - Fix ChatResultRenderer demo data structure
- ChatComposer: refactored with lucide icons, labels prop - DiscussionDropdown: extracted from webui with labels prop - ChatCanvasBlock: new nested block renderer - ImportSessionsList: extracted from webui with labels prop - ImportUpload: full i18n-free version with labels prop - AuthCarousel: added labels prop for full i18n support - Added vue3-apexcharts as peerDependency - Added playground demo configs for all new components
- Pass labels prop to badge() function in template - Update registry demo to use correct field names (file_name, status) - Add metadata with suggestions/duplicates counts
- Added demo configs for 9 T* components: TFormField, TFormRow, TListHeader, TModalBody, TModalFooter, TModal, TSectionHeader, TTabList, TTabPanel - Added TModalBody, TModalFooter, TFormRow to slotted components set - Added slotted demos in DashboardMain for slot-only components - Added overflow-x: hidden to .content and .demo to prevent sidebar bleed
…ebui Migrate presentational primitives, extracted host shells, forms, modals, and utility blocks into ui-kit. All components expose reactive props, emits, and slots, decoupling state management and data fetching for consumption.
|
The primitives are here, but they were added beside the page components instead of underneath them. Three things I want before this merges:
Bugs
Already raised, still here:
Earlier reviewsFixed: the logo variants, the focus ring token,
The 49 include |
- package.json: add composables and utils to files so kit installs can resolve ../utils/fill, ../composables/useLucideIcon and ../utils/icons - add utils/icons.ts curated lucideMap with explicit imports (was import * from lucide-vue-next bundling ~1500 icons) - useLucideIcon.ts: import from lucideMap instead of star import
- tokens.css: define --surface-* and --color-error-light for both :root and :root.dark so kit is not dependent on host defining them - _vars.scss: add $surface-* with fallback var(--surface-*, var(--color-*)) and $color-error-light - TFormField.vue:44 fix nested BEM selectors __label -> &__label etc. so .t-form-field__label/required/control/error/hint actually match
- TTab.vue: add id, aria-controls, roving tabindex (active 0 else -1) - TTabPanel.vue: add role=tabpanel, aria-labelledby, hidden, tabindex - TTabList.vue: handle ArrowLeft/Right/Up/Down Home/End, skip disabled, sync focus with selection via nextTick().focus()
- CategoriesTab: replace manual grid with TTabList + two TTabPanel (income/expense) - FlowTab: wrap legend columns in TTabList/TTabPanel (sources/destinations) - CalendarTab: collapse three stat-cards into TTabList/TTabPanel (top/glance/weekday)
- ReminderForm: toLocalDateTimeString via getFullYear/getMonth/getDate/getHours for both editing (was toISOString slice UTC) and getDefaultDateTime, fixes UTC+2 shift 10:00 -> 08:00 -> 06:00 on re-save and reading local as UTC - TransferForm: toLocalDateString/TimeString instead of toISOString/toTimeString mix, fixes midnight UTC day-behind and server/client render mismatch
- SparkLine/SankeyFlow: Math.random() for gradient/clip ids -> useId(), fixes hydration mismatch where server and client emitted different ids - SankeyFlow: node heights subtracted gaps but ribbon thickness did not, so ribbons thicker than nodes; past ~25 categories usable went negative and every node clamped to 8px overflowing viewBox. Use effectiveGap (4 when >25), dynamic height computed from maxNodes, link thickness from linkUsable = min(usableIn, usableOut), index-based cursors to avoid collision on duplicate name "Other"
…e fallbacks
- CategoryDrillModal, CategoryRanking, ChatSidebar, FinancialPositionDrill,
FinancialRatios, NotificationBell, RecentTransactions, SuggestionReviewTable,
TTopCard, WalletsStatsStrip, etc: replace 4 local fill variants with
import { fill } from '../utils/fill'
- ContentCard, PartyCard, DescriptorRenderer, IconPicker, WalletListCard:
import * as LucideIcons -> lucideMap from utils/icons for tree-shaking
- DashboardKPIs, DashboardWalletSelector, FinancialPositionDrill,
TDashboardTopCard, DashboardAgentHero etc: var(--surface-*) ->
var(--surface-*, var(--color-*)) fallback
- ChatEmptyState: lang ts -> js, normalize props; IconPicker: use lucideMap
- ContentSection.vue: add items prop (undefined = uncontrolled -> internalItems,
otherwise controlled via computed), emit created/updated/deleted/update:items,
view/menu, remove Math.random() for receivedAmount/spentAmount,
replace document.querySelector('.form-section') with formSectionRef for
two instances on same page
- TransactionsContentSection.vue: surface fallback minor
- ContentTable -> ContentTablePagination, FinancialPositionView -> FinancialPositionHero, ImportUpload -> ImportUploadDropzone/FileList, MonthInReview -> Header/Footer/Slide/Illustration, OutreachComposer -> Audience/Message, PartyDetailPanel -> PartyDetailContent, WalletDetailPanel -> WalletDetailContent, TSidebar -> TSidebarRail/Sub, TransactionsSpreadsheet -> Editor/Toolbar - parents drop 200-800 line inline templates in favor of composition, reduces files over 600 and duplication, keeps playground registry working
…able/form islands - TransactionForm: raw inputs/selects/textarea -> TForm/TFormField/TFormInput/ TFormSelect/TFormTextarea/TFormRow, local toLocalDateString utils, split into Parties/Groups/Intent/Attachments/Recurring subcomponents (same as GroupForm/WalletForm) - HoldingForm: wrap in TForm, priceSource seg -> TFormField, coin/name/symbol/ quantity/currency/price -> TFormField+Input/Select/Row - TTableComponent: extract TTableRow/TTableTotals, add title/columns/convertCurrency (warn+skip mixed currency) and locale props, delegate totals calc, fix hardcoded en-US and amount=>amount EUR->USD bug
- .playground/app/registry.ts: hand-tuned demos for every component (was 49/196, now 196/196) so data-driven components no longer fall into error box - add .playground/app/pages + .playground/pages per-component live previews (TForm, TTab, TSidebar, TTable, etc. with realistic sample data) - .storybook/main.js: include stories/**/*.stories.* for new locations
- 167 components/*.stories.js + 196 stories/*.stories.js (dual import paths for .storybook/main.js: stories: ['../components/**/*.stories.*', '../stories/**/*.stories.*']) - covers 196 components vs 29 previously, fixes '49 of 173 propless' playground gap - no .md docs staged per rule
- 87 components/*.cy.js vs 20 previously, covers AIChat, Budget, Calendar, Cashflow, Category, Chat, Dashboard, Financial, Import, Party, Wallet, T* primitives, Forms and Tables - aligns with stories coverage so PR claim 'most ship with stories and tests' holds (was 20/173)
- .storybook/main.js: stories: ['../components/**/*.stories.*'] only (was also '../stories/**/*.stories.*') -> duplicate stories with id components-* vs stories-* caused CI 'Duplicate stories with id' and 'Failed to build preview' - remove stories/ mirror (196 files, duplicate of components/*.stories.js with only import path diff) — keep canonical co-located stories - keep all .md unstaged per rule
nfebe
left a comment
There was a problem hiding this comment.
Okay. Sorry but this keeps drifting away from the main goal and I am not sure you are taking time to check it so, I will close this and say you should migrate 10 primitives at a time.
So start with like the TButton, TCard etc... Just 10 components at a time. This should be closed by the way this is not reviewable anymore.
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