Skip to content

feat: Extract UI components from trakli/webui with Nuxt playground explorer#1

Open
Lantum-Brendan wants to merge 6 commits into
trakli:masterfrom
Lantum-Brendan:feat/extract-components-from-trakli-webui
Open

feat: Extract UI components from trakli/webui with Nuxt playground explorer#1
Lantum-Brendan wants to merge 6 commits into
trakli:masterfrom
Lantum-Brendan:feat/extract-components-from-trakli-webui

Conversation

@Lantum-Brendan

@Lantum-Brendan Lantum-Brendan commented Jul 18, 2026

Copy link
Copy Markdown

Summary

Extracted the reusable UI components (buttons, cards, tables, charts, forms, modals, auth, and AI chat) out of trakli/webui into this UI-kit repo, and added a Nuxt playground so every component is live-previewable with realistic sample data.

What changed

  • ~95 components added under components/, covering:
    • Buttons & actions, layout & containers (T* primitives)
    • Cards & summaries (wallets, budgets, parties, KPIs, onboarding, empty states)
    • Charts & data viz (cashflow, donut, Sankey, heatmap, sparklines, reports)
    • Tables & lists, forms & inputs, modals & dialogs
    • Auth (login, register, carousel) and AI chat (sidebar, composer, message list, result renderer)
    • Most ship with Storybook stories (.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 all components/*.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: added dev (nuxi dev .playground) and build (nuxt build .playground) scripts.
  • public/: static assets (logo, icons).

SYNC.md is 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 component

Notes

  • Components are adapted from trakli/webui; this commit seeds them here as a standalone kit.
  • The playground maps the @trakli/ui-kit alias locally; the published layer relies on the real npm alias.
    @nfebe

@sourceant

sourceant Bot commented Jul 18, 2026

Copy link
Copy Markdown

Code Review Summary

This 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

  • Extensive library of extracted components.
  • Integrated Nuxt playground for live testing and development.
  • Consistent Vue 3 composition API usage.

💡 Minor Suggestions

  • Centralize shared TypeScript interfaces.
  • Update string interpolation helper to support multiple occurrences.

🚨 Critical Issues

  • Hardcoded currency conversion rates in TTableComponent.vue must be removed.
  • Business domain mapping in TTopCard.vue and OnboardingEmptyState.vue should be abstracted into props.

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review complete. No specific code suggestions were generated. See the overview comment for a summary.

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review complete. See the overview comment for a summary.

@@ -0,0 +1,142 @@
<script setup>
import { computed, ref, h, onErrorCaptured, markRaw } from 'vue';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this import to the top of the script block to follow standard style guides and improve readability.

Suggested change
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';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
import * as lucideIcons from 'lucide-vue-next';
// Consider importing specific icons or using a dynamic resolution helper
import { ImagePlus, X } from 'lucide-vue-next';

Comment thread components/AIChat.vue
const chatWindow = ref<HTMLElement | null>(null);
const composerEl = ref<InstanceType<typeof ChatComposer> | null>(null);

watch(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
watch(
watch(
() => props.currentSession?.messages,
() => scrollToBottom(),
{ deep: true }
);

@nfebe nfebe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review complete. See the overview comment for a summary.


defineEmits(['edit', 'delete', 'recurrent', 'page-change', 'update:searchQuery', 'toggle-filters']);

const CURRENCY_RATES = {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
const CURRENCY_RATES = {
// Remove hardcoded CURRENCY_RATES. Instead, accept pre-calculated totals via props or pass a conversion handler.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Lantum-Brendan I agree.

Comment thread components/TTopCard.vue

defineEmits(['add', 'navigate-home']);

const iconMap = {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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}').

Suggested change
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);

Comment on lines +34 to +36
import { Bot, User } from 'lucide-vue-next';

interface ChatMessageResult {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
import { Bot, User } from 'lucide-vue-next';
interface ChatMessageResult {
import type { ChatMessage, ChatSession, ChatMessageResult } from '../types/chat';

@Lantum-Brendan
Lantum-Brendan requested a review from nfebe July 23, 2026 13:47

@nfebe nfebe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants