Northline Research
Northline Workforce
Monthly retainer — SEO advisory site and keyword mapping.
- Rate
- $45.00/h
- Updated
- 4 days ago
Every component the desk is built from, in every variant it supports, with the props beside it. This page is prerendered and uses no live data, so it is also the fastest way to check that a change did not quietly break something three screens away.
Slate neutrals, one indigo accent, semantic tones for state. Nothing else gets to introduce a colour.
Accent
Neutrals
Label is always rendered — placeholder-only fields lose their label the moment you type.
Shown on invoices.
must be a valid email
Stored as minor units.
| Prop | Type | Notes |
|---|---|---|
| modelValue | string | number | v-model. |
| label | string | Bound to the input with a generated id. |
| error | string? | Sets aria-invalid and renders below the field. |
| hint | string? | Hidden while an error is showing. |
| type | string | Native input type. Defaults to text. |
| required | boolean | Marks the label and sets the attribute. |
Native select on purpose — it beats every custom dropdown on mobile and with a screen reader.
pick one
| Prop | Type | Notes |
|---|---|---|
| modelValue | string | v-model. |
| label | string | Always rendered. |
| options | { value, label }[] | Order is respected as given. |
| error | string? | Same treatment as UiInput. |
For notes and descriptions. Same label and error contract as UiInput.
2000 characters max
| Prop | Type | Notes |
|---|---|---|
| modelValue | string | v-model. |
| label | string | Always rendered. |
| rows | number | Defaults to 4. |
| error | string? | Same treatment as UiInput. |
The only surface primitive. Header and footer are optional slots that add their own dividers.
Default slot content sits between the two.
Body only — no dividers rendered.
| Prop | Type | Notes |
|---|---|---|
| default | slot | Card body. |
| header | slot? | Adds a bottom-divided header region. |
| footer | slot? | Adds a top-divided footer region. |
The tone vocabulary every other badge is built from.
| Prop | Type | Notes |
|---|---|---|
| tone | gray | green | amber | red | blue | Defaults to gray. |
| default | slot | Label text. Keep it to one or two words. |
Maps a client or task status to a tone, so status colour is decided once for the whole app.
| Prop | Type | Notes |
|---|---|---|
| status | ClientStatus | TaskStatus | Label comes from statusLabel(), tone from the map. |
Four levels, ranked urgent → low. Colour is not the only signal; the word is always there.
| Prop | Type | Notes |
|---|---|---|
| priority | TaskPriority | urgent | high | medium | low. |
One number, one label, one optional line of context. Used across the dashboard header row.
Active clients
8
12 on the books
Overdue
3
Past their due date
Unbilled work
$4,182.50
Not yet invoiced
Paid this quarter
$19,240.00
| Prop | Type | Notes |
|---|---|---|
| label | string | Sits above the value. |
| value | string | number | Pre-format money and durations before passing them. |
| hint | string? | Small line underneath. |
| tone | gray | green | amber | red | blue | Defaults to gray. |
Inline SVG, no charting library. Zero days stay visible as a baseline tick rather than disappearing.
| Prop | Type | Notes |
|---|---|---|
| data | { date, minutes }[] | Oldest first. Scales to the tallest bar in the set. |
Also inline SVG. Every segment carries its label and value in text, so it survives being read aloud.
| Prop | Type | Notes |
|---|---|---|
| segments | { label, value, tone }[] | Zero-value segments are dropped, not drawn as slivers. |
Every list in the app has one. An empty table with headings and no rows is a bug report waiting to happen.
Nothing on the desk matches the current search and status. Clearing the filters brings the full list back.
Clear filters| Prop | Type | Notes |
|---|---|---|
| title | string | One short line. |
| description | string | Say what to do next, not just that it is empty. |
| actionLabel | string? | Omit both action props for a message-only state. |
| actionTo | string? | Route for the action link. |
Emits update:page; the page owns the state and writes it to the query string.
Current page: 3
| Prop | Type | Notes |
|---|---|---|
| page | number | 1-indexed. |
| pages | number | Total page count, never below 1. |
| total | number | Row count, used for the “x–y of z” line. |
| perPage | number | Page size. |
| @update:page | (n: number) => void | Controlled component — it never mutates its own page. |
Shown only on client-side navigation. SSR renders real data, so the first paint never needs one.
| Prop | Type | Notes |
|---|---|---|
| rows | number | Placeholder lines to draw. Defaults to 3. |
Focus is trapped while open and Escape closes it. Used for destructive confirmations.
| Prop | Type | Notes |
|---|---|---|
| open | boolean | Controlled by the parent. |
| title | string | Becomes the dialog’s accessible name. |
| @close | () => void | Fired by Escape, the backdrop and the close button. |
| footer | slot? | Action row. Put the destructive button last. |
The clients grid tile. Rate is formatted through money() in the client's own currency.
Northline Workforce
Monthly retainer — SEO advisory site and keyword mapping.
Harbour & Vine Ltd
Nuxt 2 legacy maintenance. Paused pending their rebrand.
| Prop | Type | Notes |
|---|---|---|
| client | Client | The whole record — the card links to /clients/:id itself. |
One task in a list. Overdue is computed from the due date and the status, never stored.
Northline Research
Northline Research
| Prop | Type | Notes |
|---|---|---|
| task | Task | The whole record. |
| clientName | string? | Omit inside a single-client view. |
| @toggle | () => void | Parent owns the write — the row never calls the API itself. |
The pieces the default layout mounts once, around whatever page is rendering.
DriverBanner
Silent on Postgres. On the memory driver it says so, because a visitor should not discover that by losing work.
ToastHost
CommandPalette
Press ⌘ K (or CtrlK) anywhere in the app.
| Prop | Type | Notes |
|---|---|---|
| DriverBanner | — | Fetches /api/health. No props. |
| ToastHost | — | Reads the queue from useToast(). Mounted once in app.vue. |
| CommandPalette | — | Global Cmd/Ctrl+K navigation. Mounted once in the layout. |
| AppHeader / AppFooter | — | Nav, theme toggle, locale switch, account menu. |