172 lines
7.5 KiB
Markdown
172 lines
7.5 KiB
Markdown
# Migrate Mercury UI to the shadcn-vue version of @nychthemeron/library
|
|
|
|
## Background
|
|
|
|
`@nychthemeron/library` was rebuilt from PrimeVue (unstyled mode + a custom
|
|
`ThemeEngine` passthrough layer) to shadcn-vue + Tailwind v4. PrimeVue moved
|
|
to a paid licensing model, so the old build was replaced in place under the
|
|
same `0.0.1` version on the private registry (`git.mcpeakdev.com`) rather
|
|
than published as a new version — confirmed intentional by the maintainer.
|
|
|
|
The consuming app (`Mercury/ui`) still targets the old PrimeVue-era API
|
|
everywhere: `main.ts` wires up the PrimeVue plugin, and 10 admin views plus
|
|
`Login.vue` use globally-registered `Nych*` components with PrimeVue-style
|
|
props (`label`, `severity`, `fluid`, `v-model:visible`, `:options`/
|
|
`optionLabel`/`optionValue`). None of that matches the new library's API, so
|
|
this is a real port, not a version bump.
|
|
|
|
## Scope
|
|
|
|
**In scope:** everything needed to get Mercury UI running correctly against
|
|
the new library — build/plugin wiring, and a native rewrite of every view
|
|
that uses a `Nych*` component.
|
|
|
|
**Out of scope:** any new features, any styling changes beyond what's needed
|
|
to preserve current look/behavior, and the library's own source (it's an
|
|
external package on a separate registry).
|
|
|
|
## Current usage inventory
|
|
|
|
| File | Button | InputText | Dialog | Select | Textarea | Message |
|
|
|---|---|---|---|---|---|---|
|
|
| Login.vue | 1 | 2 | | | | |
|
|
| admin/ApiKeys.vue | 4 | 2 | 2 | 1 | | 1 |
|
|
| admin/Blacklist.vue | 5 | 6 | 2 | 1 | | |
|
|
| admin/Cache.vue | 2 | | | | | |
|
|
| admin/Cdn.vue | 5 | 6 | 2 | | | |
|
|
| admin/Cors.vue | 3 | 1 | 1 | | | |
|
|
| admin/Permissions.vue | 5 | 5 | 2 | | | |
|
|
| admin/Queries.vue | 5 | 3 | 2 | | 2 | |
|
|
| admin/Tables.vue | 7 | 3 | 3 | 1 | | |
|
|
| admin/Users.vue | 5 | 4 | 2 | 2 | | |
|
|
| **Total** | **42** | **32** | **16** | **5** | **2** | **1** |
|
|
|
|
## Component API mapping
|
|
|
|
**Button** (`NychButton`) — low friction, variant names carry over almost
|
|
1:1:
|
|
- Content moves from `label="X"` prop to default slot: `<NychButton>X</NychButton>`
|
|
- `severity="danger"` → `variant="danger"` (also: `primary` (default),
|
|
`secondary`, `info`, `success`, `warning`, `danger`)
|
|
- `size="small"` → `size="sm"` (also: `default`, `lg`, `icon`)
|
|
- `:loading`, `:disabled` — unchanged
|
|
- `fluid` prop is gone — use `class="w-full"`
|
|
|
|
**Input** (`NychInputText` → `NychInput`) — rename only, `v-model`,
|
|
`type`, `placeholder`, `autocomplete` unchanged. `fluid` → `class="w-full"`.
|
|
|
|
**Textarea** (`NychTextarea`) — same shape as Input, `fluid` →
|
|
`class="w-full"`.
|
|
|
|
**Dialog** — flat component becomes a compound one:
|
|
```
|
|
<!-- old -->
|
|
<NychDialog v-model:visible="show" header="Title" :modal="true" :draggable="false">
|
|
...body...
|
|
</NychDialog>
|
|
|
|
<!-- new -->
|
|
<NychDialog v-model:open="show">
|
|
<NychDialogContent>
|
|
<NychDialogHeader><NychDialogTitle>Title</NychDialogTitle></NychDialogHeader>
|
|
...body...
|
|
</NychDialogContent>
|
|
</NychDialog>
|
|
```
|
|
- `:closable="false"` (used once, in ApiKeys.vue's key-reveal dialog, to
|
|
force the user to acknowledge before dismissing) → pass
|
|
`:show-close-button="false"` on `NychDialogContent` to drop the X button,
|
|
and ignore attempts to close via `@update:open="(v) => { if (v) show = v }"`
|
|
so outside-click/Escape can't dismiss it either.
|
|
- `:draggable="false"` — no-op, the new Dialog isn't draggable to begin with.
|
|
- Width customization (`style="width: min(560px, 95vw)"`) — pass through as
|
|
`class` on `NychDialogContent` since it accepts a `class` prop merged via
|
|
`cn()`.
|
|
|
|
**Select** — flat `:options` array becomes compound children:
|
|
```
|
|
<!-- old -->
|
|
<NychSelect v-model="x" :options="ROLES" optionLabel="label" optionValue="value" placeholder="Select a role" fluid />
|
|
|
|
<!-- new -->
|
|
<NychSelect v-model="x">
|
|
<NychSelectTrigger class="w-full"><NychSelectValue placeholder="Select a role" /></NychSelectTrigger>
|
|
<NychSelectContent>
|
|
<NychSelectItem v-for="r in ROLES" :key="r.value" :value="r.value">{{ r.label }}</NychSelectItem>
|
|
</NychSelectContent>
|
|
</NychSelect>
|
|
```
|
|
|
|
**Message → Alert** — no `NychMessage` in the new library; `NychAlert` is
|
|
the closest analog:
|
|
- `severity="warn"` → `variant="warning"` (also: `info` (default),
|
|
`success`, `danger`, `secondary`)
|
|
- Body goes in the default slot, same as before.
|
|
|
|
## Phase 1 — Foundation (build/plugin wiring)
|
|
|
|
1. **`src/main.ts`** — remove the `@primevue/core/config` import and
|
|
`app.use(PrimeVue, { unstyled: true })` call. Keep
|
|
`import '@nychthemeron/library/theme'` and `app.use(createNychthemeron())`
|
|
unchanged (both still resolve, just point at new content).
|
|
2. **`vite.config.ts`** — add the `@tailwindcss/vite` plugin (required
|
|
because the library now ships source CSS with `@import "tailwindcss"`
|
|
that must be processed at build time, not a precompiled stylesheet).
|
|
Replace the `primevue: ["@primevue/core"]` manual chunk with nothing
|
|
(package no longer exists).
|
|
3. **`package.json`** — add `tailwindcss` and `@tailwindcss/vite` as direct
|
|
devDependencies (currently only reachable transitively through
|
|
`@nychthemeron/library`, which is fragile) at the versions already
|
|
resolved in `bun.lock`. Bump `vue` from `^3.4.0` to `^3.5.0` to match the
|
|
library's peer requirement.
|
|
4. **`src/assets/main.css`** — leave the design-token-based custom classes
|
|
(`.table-card`, `.data-table`, `.dialog-form`, `.gold-rule`,
|
|
`.loading-overlay`, etc.) as-is; they key off CSS custom properties
|
|
(`--surface-1`, `--primary`, ...) which kept the same names and values in
|
|
the new theme. Remove the dead overrides tied to the old unstyled-PrimeVue
|
|
class names: `.nych-dialog { min-width: 500px }`,
|
|
`[class^="nych-button"] { ... }`, and re-check `.nych-loading-icon .wreath`
|
|
still applies (the new `NychLoadingIcon` keeps the same class name/markup,
|
|
so this one likely stays).
|
|
5. Verify with `bun run dev` that the app boots without console errors
|
|
before touching any view.
|
|
|
|
## Phase 2 — View-by-view native migration
|
|
|
|
Migrate one file at a time, in this order (simplest/lowest-risk first, so
|
|
mistakes in the component mapping surface early on a small file rather than
|
|
a large one):
|
|
|
|
1. `Login.vue` (3 call sites, no Dialog/Select — good smoke test)
|
|
2. `admin/Cache.vue` (2 call sites, Button only)
|
|
3. `admin/Cors.vue`
|
|
4. `admin/Queries.vue`
|
|
5. `admin/Blacklist.vue`
|
|
6. `admin/Permissions.vue`
|
|
7. `admin/Users.vue`
|
|
8. `admin/Cdn.vue`
|
|
9. `admin/Tables.vue`
|
|
10. `admin/ApiKeys.vue` (has the one `NychMessage` → `NychAlert` conversion
|
|
and the non-dismissible dialog pattern — do it last once that pattern is
|
|
proven elsewhere isn't needed, i.e. it's the only file needing it)
|
|
|
|
For each file: apply the mapping above, then run the app (`bun run dev`),
|
|
navigate to that view in the browser, and check the golden path (list
|
|
renders, create/edit dialog opens and submits, select options populate,
|
|
delete/revoke works) before moving to the next file. This is a visual/UI
|
|
change, so it needs to be checked in a real browser, not just type-checked.
|
|
|
|
## Verification
|
|
|
|
- `vue-tsc --noEmit` (part of `bun run build`) after each file, to catch
|
|
prop-shape mistakes the compiler can see.
|
|
- Manual browser check of each migrated view per the sequencing above.
|
|
- Final full pass: `bun run build` succeeds, and a click-through of every
|
|
admin view's create/edit/delete flow.
|
|
|
|
## Open questions / risks
|
|
|
|
- The non-dismissible-dialog pattern for `ApiKeys.vue`'s key-reveal dialog
|
|
(ignoring `update:open` on close attempts) isn't a built-in library
|
|
feature — it's an app-level workaround. If it feels fragile once
|
|
implemented, worth flagging back to the library rather than solving twice.
|