--- url: /guide/getting-started.md --- # Getting Started Set up the Cocoar Design System in your Vue 3 project in a few steps. ## 1. Install ```bash pnpm add @cocoar/vue-ui ``` ## 2. Import Fonts & Styles Import fonts and styles in your app's entry point. Fonts are self-hosted via `@fontsource` — no external CDN needed. ```ts // main.ts import '@cocoar/vue-ui/fonts'; // Poppins + Inter (self-hosted) import '@cocoar/vue-ui/styles'; // Design tokens + component styles ``` ::: info Bring your own fonts? The font import is optional. If you prefer a CDN or custom fonts, skip `@cocoar/vue-ui/fonts` and load them yourself. Components fall back to system fonts gracefully. ::: ## 3. Use Components Import components directly — no global registration required. Tree-shaking is automatic. ```vue ``` ## 4. Dark Mode Toggle dark mode by adding the `.dark-mode` class to the root element. All design tokens and components adapt automatically. ```ts document.documentElement.classList.toggle('dark-mode', isDark); ``` ## 5. Overlay System For components that render overlays (Dialog, Toast, Popover, Tooltip), register the plugin once: ```ts // main.ts import { createApp } from 'vue'; import { CoarOverlayPlugin } from '@cocoar/vue-ui'; createApp(App) .use(CoarOverlayPlugin) .mount('#app'); ``` And add the overlay host to your root layout: ```vue ``` ## Date/Time Components The date and time pickers use the [Temporal API](https://tc39.es/proposal-temporal/docs/) via `@js-temporal/polyfill`, which is included as a dependency of `@cocoar/vue-ui`. No extra install needed. When native Temporal support reaches all browsers, the polyfill can be dropped in a future major release. ## Additional Packages Optional packages for extended functionality: ```bash pnpm add @cocoar/vue-localization # i18n & timezone pnpm add @cocoar/vue-data-grid # AG Grid wrapper pnpm add @cocoar/vue-markdown # Markdown viewer ``` --- --- url: /guide/error-handling.md --- # Error Handling Cocoar UI components are designed to fail gracefully. Internal errors are either caught and recovered silently, or surfaced to the user through controlled feedback mechanisms. This guide explains the patterns used throughout the library and how to handle errors in your own application code. ## Library Philosophy: Fail Gracefully Components never throw unhandled exceptions into your application. Instead they follow one of two patterns: * **Silent fallback** — return a safe default (`null`, `'UTC'`, `false`) and continue * **User feedback** — surface the error visibly via a Toast or state change ## Overlay Promises `CoarDialog` and `CoarPopconfirm` return Promises. Always handle the rejection case: ```ts import { useDialog } from '@cocoar/vue-ui'; const dialog = useDialog(); // ✅ Always add .catch() dialog.confirm({ title: 'Delete item', message: 'This cannot be undone.', }) .then((confirmed) => { if (confirmed) deleteItem(); }) .catch(() => { // Dialog was closed unexpectedly (e.g. overlay destroyed before user responded) }); ``` With async/await: ```ts try { const confirmed = await dialog.confirm({ title: 'Delete item', message: '...' }); if (confirmed) await deleteItem(); } catch { // Handle unexpected close } ``` ::: tip Popconfirm `CoarPopconfirm` emits `@confirmed` and `@cancelled` events — no Promise handling needed there. Use it for simple inline confirmations, and reserve `useDialog()` for programmatic flows where error handling is more important. ::: ## Toast for Error Feedback Use `useToast().error()` to surface errors to the user. Error toasts are persistent by default (duration `0`) — they stay until the user dismisses them, which is appropriate for errors that require attention. ```ts import { useToast } from '@cocoar/vue-ui'; const toast = useToast(); async function saveData() { try { await api.save(payload); toast.success('Saved successfully'); } catch (err) { toast.error('Save failed', { message: err instanceof Error ? err.message : 'Please try again.', }); } } ``` ```ts // With a retry action toast.error('Connection lost', { message: 'Could not reach the server.', action: { label: 'Retry', callback: () => saveData(), }, }); ``` ## Date and Time Parsing Date parsing functions return `null` on failure instead of throwing. Always null-check the result before using it: ```ts import { coarParsePlainDate } from '@cocoar/vue-ui'; const date = coarParsePlainDate(userInput); if (date === null) { // Input was invalid — show validation error toast.error('Invalid date format'); return; } // date is a Temporal.PlainDate — safe to use processDate(date); ``` The date picker components handle this internally — invalid input simply doesn't update the model value. Your `v-model` will remain `null` until the user enters a valid date. ## Timezone Fallbacks Timezone utilities default to `'UTC'` when the browser API fails or the timezone identifier is unrecognised. This keeps date/time components functional even in restricted environments: ```ts import { useTimezone } from '@cocoar/vue-localization'; const { timezone } = useTimezone(); // Always a valid IANA identifier — 'UTC' as last resort ``` ## Async Operations in Overlays When loading data inside a Dialog or Popover, manage loading and error states yourself: ```vue ``` ## Pattern Summary | Situation | Recommended pattern | |-----------|---------------------| | Dialog/Popconfirm result | `.then().catch()` or `try/await/catch` | | API call in component | `try/catch` + `toast.error()` | | Date input validation | Null-check return value of parse functions | | Non-recoverable error | `toast.error()` with persistent duration (default) | | Recoverable error | `toast.error()` with `action: { label: 'Retry', callback }` | | Silent failures OK | Rely on library defaults (`null`, `'UTC'`, `false`) | --- --- url: /guide/theming.md --- # Theming Cocoar uses an **oklch-based** color system. You set a few base colors, and the entire palette — including all shades for light and dark mode — is auto-calculated. ## Quick Start Override the CSS custom properties on `:root` to match your brand: ```css :root { --coar-accent: #1183CD; /* Your brand color → primary buttons, links, focus rings */ } ``` That's it. All accent shades (50–900), in both light and dark mode, recalculate from this single value. ## Customizable Base Colors | Variable | Default | Purpose | |----------|---------|---------| | `--coar-accent` | `#1183CD` | Brand/accent color — primary buttons, active states, links | | `--coar-success` | `#1e8f48` | Success states — confirmations, positive feedback | | `--coar-error` | `#d63b3b` | Error states — validation errors, destructive actions | | `--coar-warning` | `#cc821f` | Warning states — caution, attention needed | | `--coar-info` | `#5e6b84` | Info states — neutral informational context | ### Example: Red Brand ```css :root { --coar-accent: #C41E3A; /* Red brand */ --coar-error: #8B0000; /* Darker red so errors are distinguishable */ } ``` ### Example: Purple Brand ```css :root { --coar-accent: #7C3AED; } ``` ## How It Works Each base color generates a 10-step shade scale using **oklch relative color syntax**: ```css /* You set this: */ --coar-accent: #1183CD; /* The library calculates these: */ --coar-color-accent-50: oklch(from var(--coar-accent) 0.97 0.012 h); /* lightest */ --coar-color-accent-100: oklch(from var(--coar-accent) 0.92 0.035 h); --coar-color-accent-200: oklch(from var(--coar-accent) 0.84 0.075 h); /* ... */ --coar-color-accent-500: var(--coar-accent); /* = your exact color */ /* ... */ --coar-color-accent-900: oklch(from var(--coar-accent) 0.31 0.095 h); /* darkest */ ``` The `h` (hue) is extracted from your color. Lightness and chroma follow a designed curve that keeps shades vibrant instead of washed out. `accent-500` is always your exact brand color. This works because **oklch is perceptually uniform** — unlike HSL, a lightness of 0.5 in oklch looks equally "medium" for blue, red, and yellow. ## Fine-Tuning Individual Shades If an auto-calculated shade doesn't look right for your specific color, override it: ```css :root { --coar-accent: #FF6600; /* Auto-calculated 50 too warm? Override just that one: */ --coar-color-accent-50: #FFF5EB; } ``` ## Dark Mode Dark mode shades are calculated from the same base variables — no need to set anything extra. The library uses a separate lightness/chroma curve designed for dark backgrounds: * Low numbers (50–200): dark with a subtle color tint * Mid range (300–500): vibrant and saturated * High numbers (600–900): lighter for text on dark backgrounds The primary button color (`accent-500`) stays identical in both modes. ## Browser Support The oklch color system requires: * Chrome 119+ * Firefox 128+ * Safari 18+ This covers all modern browsers. For older browsers, consider providing hex fallbacks for your specific brand color. --- --- url: /guide/changelog.md --- # Changelog All notable changes to the Cocoar Design System (Vue) will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versions are calculated automatically by [GitVersion](https://gitversion.net/). *** ## 2.10.0 A batch of improvements from the Tellify (CityDiary) build. For the media-library: inline node **creation** and an app-internal **drop target** for `CoarTree`, plus a programmatic **move**, optimistic **create**, and a **browse-only** mode for `@cocoar/vue-file-explorer-core` — so consumers can drop the workarounds they had built around the gaps. For the blog editor: `@cocoar/vue-markdown-editor` gains real **image** support (URL / paste / drag-drop upload / custom picker), full **table** editing (create, align, delete, and Notion-style hover handles with **drag-to-reorder**), and a **`flavor`** portability switch. Everything is additive and opt-in. ### Added * **`@cocoar/vue-ui` — `CoarTree` inline node creation.** Opt in with `creatable` (mirrors `renamable`) and call `api.startCreate(parentId, { kind?, initialName?, position? })` to insert a transient, focused **draft row** at its target position — `parentId: null` for the root, otherwise the parent auto-expands and the draft renders nested under it. Commit (Enter / blur) fires `@create` with `{ parentId, name, kind }`; Escape or an empty name fires `@create-cancel`. Same input + 200 ms blur-grace timer as inline rename, so it survives the context-menu-close focus race. The draft lives outside the visible-row model (selection / keyboard / DnD never see it) and works virtualized and at the empty-tree root. Optional `#draft` slot overrides the default folder/file icon. For async validation (e.g. a duplicate-name 409), a builder `onCreate` may return a `Promise` — the tree keeps the draft open + focused (name intact) until it settles, dropping it on success and reopening on reject; prop/event-form consumers get the same retry by re-calling `startCreate(parentId, { initialName })`. Everything works in prop-mode too (`startCreate` is on the component template ref). Removes the consumer workaround of a floating `` rendered outside the tree. * **`@cocoar/vue-ui` — `CoarTree` app-internal drop target (`acceptsData` / `@data-drop`).** Accept a non-OS, in-app drag — a card dragged out of a grid, a palette chip — by listing the MIME type(s) it carries in `acceptsData` (prop or builder `.acceptsData([...])`). A drop fires `@data-drop` / `onDataDrop` with `{ node: T | null, position, dataTransfer }` (`node: null` = the background), reusing the existing drop highlight, auto-expand-on-hover and `before`/`inside`/`after` position logic. Internal node drags (`@node-move`) are never re-delivered as data drops. Lets consumers drop hand-rolled `dragover`/`drop` listeners on the row slot for grid-card → folder moves. * **`@cocoar/vue-file-explorer-core` — programmatic `move(id, newParentId, position?)`.** `useFileExplorer` now exposes a plain move (same optimistic-update + rollback as a tree drag) for sources that aren't a `CoarTreeNodeMoveEvent` — a "move to folder" `