# Voicenter UI library for Vue
This is Vue 3.x + Typescript component library made for Voicenter systems.

> **v3.0.0 is a tree-shaking release with breaking changes.** See [`MIGRATION_v3.md`](./MIGRATION_v3.md). The separate `*-extended` package is gone — every component lives in this package now.

## Installation

```bash
npm i @voicenter-team/voicenter-ui-plus
npm i vue@^3.5 element-plus @vueuse/core dayjs lodash-es libphonenumber-js maska
```

### Sass compiler (build-time)

This library customizes element-plus theming via SCSS variable injection at build time. Your bundler needs to be able to compile SCSS. If your project doesn't already use Sass, add it as a dev dependency:

```bash
npm i -D sass-embedded
```

This is a **build-time only** requirement — it doesn't add anything to your runtime bundle. Most Vue 3 / Vite projects already have it.

Optional peer deps — install only for the heavy components you use:

| Component | Peer deps |
|---|---|
| `VcPdfDocument` | `pdfjs-dist@^2.9.359` |
| `VcSoundPlayer` | `wavesurfer.js` |
| `VcExcelFileUploader` | `xlsx` |
| `VcImageUploader` | `vue-advanced-cropper` |
| `VcHtmlEditor` / `VcJsonSchema` | `codemirror @codemirror/lang-html @codemirror/lang-json @codemirror/lang-markdown vue-codemirror6` |
| `VcMdEditor` | `@milkdown/crepe @milkdown/kit @milkdown/vue @prosemirror-adapter/vue` |

### If you're on Vite 8 / rolldown (and skipping some optional peers)

Vite 8's rolldown-based bundler validates named imports against optional-peer-dep stubs and fails the build with `MISSING_EXPORT` errors when a heavy component's peer is uninstalled — even when you don't use that component. Add the unused optional peers to `build.rollupOptions.external` in your `vite.config.ts`:

```ts
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
    plugins: [ vue() ],
    build: {
        rollupOptions: {
            external: [
                // List only the heavy peers you DON'T install
                /^@milkdown\//, /^@prosemirror/, /^@codemirror\//, /^codemirror/,
                /^vue-codemirror6/, /^vue-advanced-cropper/,
                'wavesurfer.js', 'xlsx', 'pdfjs-dist', /^pdfjs-dist\//
            ]
        }
    }
})
```

Earlier Vite versions (5, 6, 7 with rollup) and webpack 5+ don't require this — the older rollup variant warns but doesn't error on optional-peer named imports.

## Usage

### Tree-shake mode (recommended)
```ts
import { VcButton, VcInput } from '@voicenter-team/voicenter-ui-plus'
// Use as local components — no app.use() needed.
```
Bundlers drop unused components and their CSS. A consumer importing only `VcButton` ships ~50–150 KB JS + ~10–30 KB CSS.

If you use `useConfirmModal`, `useConfirmPopup`, or `NotifyService`, mount global overlays in `App.vue`:

```vue
<script setup lang="ts">
import { VcPluginOverlays } from '@voicenter-team/voicenter-ui-plus'
</script>

<template>
  <RouterView />
  <VcPluginOverlays />
</template>
```

### Plugin mode (global registration)
```ts
import VoicenterUI from '@voicenter-team/voicenter-ui-plus/plugin'
import '@voicenter-team/voicenter-ui-plus/style.css'

// Overlay composables can be imported from the same entry:
// import { useConfirmModal, useConfirmPopup, VcPluginOverlays } from '@voicenter-team/voicenter-ui-plus/plugin'

app.use(VoicenterUI, {
    themeConfig,             // theme config (see below)
    lang: 'en',              // localization
    injectIconFont: true     // inject icon-font <link> tags into <head>
})
// Confirm modals/popovers/notifications: auto-mounted via VcPluginOverlays
// (mountOverlays: true by default). Opt out with mountOverlays: false and
// add <VcPluginOverlays /> to App.vue — also registered globally by the plugin.
```

### Global overlays

Several APIs are **state-only** — they update global reactive singletons but do not render UI by themselves:

| API | Overlay component |
|---|---|
| `useConfirmModal` | `VcConfirmModal` |
| `useConfirmPopup` | `VcConfirmPopover` |
| `NotifyService` / `$notify` | `VcNotification` |

**Plugin mode:** mounted automatically on `app.use()` (default). Pass `mountOverlays: false` to mount manually.

**Notifications:** one top-right host by default. For multiple positions, use `overlayNotifications` in plugin config or mount your own:

```ts
app.use(VoicenterUI, {
    overlayNotifications: [
        { group: 'top-right', position: 'top-right' },
        { group: 'bottom-right', position: 'bottom-right' },
    ],
})
// NotifyService.add({ group: 'bottom-right', ... })
```

Or `overlayNotifications: 'none'` and put `<VcNotification />` in App.vue (confirms still auto-mount).

**Tree-shake mode:** add `<VcPluginOverlays />` to `App.vue` (bundles confirm hosts; configure via `notifications` prop).

Entity components such as `VcEntityListTable` call `useConfirmModal` internally — delete confirmations require overlays to be mounted.

2.1. If you are using tailwind update your `tailwind.config.js` with:
```js
const voiceTailwindScheme = require('@voicenter-team/voicenter-ui-plus/src/theme/tailwindScheme')

module.exports = {
  ...yourConfiguration,
  theme: {
    colors: {
      ...voiceTailwindScheme
    },
    borderColor: {
      ...voiceTailwindScheme
    }
  }
}
```

### Theming specification
The `themeConfig` which is passed as options to the VoicenterUI `vue.use` data could have the following specifications:

### Local
**Config:**
```js
{
    type: 'local'
    themeName: 'red' | 'blue' | '...'
    onSetupCallback: () => {
        console.log('Loaded!')
    }
}
```
**Description:**

Such configuration will append to the document element the CSS variables from one of the configuration passed in `themeName` parameter which it will get from theme specification delivered alongside with library 

**Params:**
- **themeName**
  - The name of the theme to be injected
- **onSetupCallback**
  - The function which will be called right after the variables will be injected into the document element. First parameter of which is the theme object which was set up

### Remote
**Config:**
```js
{
    type: 'remote'
    brandingSectionName: 'someName'
    apiUrl: 'someUrl'
    brandingSectionName: {
        property: 'data'
    }
    onSetupCallback: () => {
        console.log('Loaded!')
    }
}
```
**Description:**

Such configuration will initiate the call to the specified apiUrl in order to retrieve the JSON object with key-value properties of the CSS variables to inject to document element 

**Params:**
- **brandingSectionName**
  - [Optional] The name of the section (tagSection) in the database configuration which will be passed to the API in payload and on response retrieved replaced from resulted values
- **apiUrl**
  - The API url to which the POST request will be initiated
- **onSetupCallback**
  - The function which will be called right after the variables will be injected into the document element. First parameter of which is the theme object which was set up
- **brandingSectionName**
  - [Optional] The payload data that will be sent to the request

### Custom JSON
**Config:**
```js
{
    type: 'customJson'
    config: {
        black: '#000000'
    }
    onSetupCallback: () => {
      console.log('Loaded!')
    }
}
```
**Description:**

Such configuration will initiate the styling with custom provided theme

**Params:**
- **config**
  - The object where the key is the variable to be set and the value is the variable value. Check the [themes object](https://github.com/VoicenterTeam/voicenter-ui-plus/blob/master/src/theme/themes.json) to check existing variables
- **onSetupCallback**
  - The function which will be called right after the variables will be injected into the document element. First parameter of which is the theme object which was set up

## Date Handling

This library uses **dayjs** for all date operations and formatting. When working with date-related components, use dayjs format tokens (NOT date-fns format tokens).

### Format Tokens

**Important**: Use dayjs format tokens:
- `DD` - Day of month (2 digits) - **NOT** `dd`
- `YYYY` - 4-digit year - **NOT** `yyyy`
- `MM` - Month (2 digits)
- `HH` - 24-hour format
- `mm` - Minutes
- `ss` - Seconds

### Common Formats

```typescript
'DD/MM/YYYY'           // 31/12/2024
'DD-MM-YYYY'           // 31-12-2024
'YYYY-MM-DD'           // 2024-12-31
'DD/MM/YYYY, HH:mm'    // 31/12/2024, 14:30
'DD/MM/YYYY HH:mm:ss'  // 31/12/2024 14:30:45
```

### Usage Example

```vue
<VcDatePicker
  v-model="date"
  format="DD/MM/YYYY"
  input-date-format="DD/MM/YYYY"
  input-time-format="HH:mm"
/>
```

## Date Utility Functions

The library provides a comprehensive set of date utility functions built on dayjs. These utilities are available for use in external projects.

### Import Date Utilities

```typescript
import {
  formatDate,
  parseDate,
  addDays,
  subtractDays,
  getHours,
  getMinutes,
  getSeconds,
  setTime,
  isAfter,
  isSameAfter,
  isBefore,
  isSameBefore,
  format,
  add,
  sub,
  addFp,
  subFp
} from '@voicenter-team/voicenter-ui-plus'
```

### Basic Date Operations

#### Formatting Dates

```typescript
// Format a date with default format (YYYY-MM-DD)
const formatted = formatDate(new Date())
// Result: "2024-12-31"

// Format with custom format
const custom = formatDate(new Date(), 'DD/MM/YYYY')
// Result: "31/12/2024"

// Using format function (alias)
const formatted2 = format(new Date(), 'DD/MM/YYYY HH:mm')
// Result: "31/12/2024 14:30"
```

#### Parsing Dates

```typescript
// Parse a date string with format
const date = parseDate('31/12/2024', 'DD/MM/YYYY')
// Returns: Date object
```

### Date Arithmetic

#### Adding/Subtracting Days

```typescript
// Add days
const futureDate = addDays(new Date(), 7)
// Returns: Date 7 days from now

// Subtract days
const pastDate = subtractDays(new Date(), 5)
// Returns: Date 5 days ago
```

#### Adding/Subtracting Time Units

```typescript
// Add multiple time units
const newDate = add(new Date(), {
  years: 1,
  months: 2,
  days: 5,
  hours: 3,
  minutes: 30
})

// Subtract multiple time units
const earlierDate = sub(new Date(), {
  weeks: 2,
  days: 3,
  hours: 12
})

// Functional programming style (reversed arguments)
const fpDate = addFp({ days: 7 }, new Date())
const fpDate2 = subFp({ hours: 5 }, new Date())
```

### Time Operations

#### Getting Time Components

```typescript
const date = new Date('2024-12-31T14:30:45')

const hours = getHours(date)    // 14
const minutes = getMinutes(date) // 30
const seconds = getSeconds(date) // 45
```

#### Setting Time

```typescript
// Set time on a date (returns new Date object)
const newDate = setTime(new Date(), {
  hours: 14,
  minutes: 30,
  seconds: 0
})
```

### Date Comparisons

```typescript
const date1 = new Date('2024-12-31')
const date2 = new Date('2025-01-01')

// Check if date1 is after date2
const isLater = isAfter(date1, date2, 'day')
// Returns: false

// Check if date1 is same or after date2
const isSameOrLater = isSameAfter(date1, date2, 'day')
// Returns: false

// Check if date1 is before date2
const isEarlier = isBefore(date1, date2, 'day')
// Returns: true

// Check if date1 is same or before date2
const isSameOrEarlier = isSameBefore(date1, date2, 'day')
// Returns: true
```

**Comparison Units**: The comparison functions accept a `units` parameter (default: `'minute'`). Valid units:
- `'year'`, `'month'`, `'week'`, `'day'`
- `'hour'`, `'minute'`, `'second'`

### Complete Usage Example

```typescript
import {
  formatDate,
  addDays,
  isAfter,
  format,
  add,
  getHours,
  setTime
} from '@voicenter-team/voicenter-ui-plus'

// Format today's date
const today = formatDate(new Date(), 'DD/MM/YYYY')

// Get date 30 days from now
const futureDate = addDays(new Date(), 30)

// Check if a date is in the future
const isFuture = isAfter(futureDate, new Date(), 'day')

// Add 2 weeks and 3 days
const deadline = add(new Date(), {
  weeks: 2,
  days: 3
})

// Get current hour
const currentHour = getHours(new Date())

// Set specific time
const meetingTime = setTime(new Date(), {
  hours: 14,
  minutes: 30
})

// Format the result
const formattedDeadline = format(deadline, 'DD/MM/YYYY HH:mm')
```

### Notes

- All functions accept `Date`, `string`, or `number` as date input
- All date manipulation functions return new `Date` objects (immutable)
- Format strings use dayjs format tokens (see Format Tokens section above)
- These utilities are built on dayjs and maintain compatibility with dayjs format patterns

## Contributing: adding a new component

The public component surface is defined in **one place**: [`script/components-manifest.mjs`](./script/components-manifest.mjs) — one line per component.

```js
{ name: 'VcMyThing', path: 'components/VcMyThing/VcMyThing.vue' },
```

`npm run generate:entries` materializes the manifest into three fully
generated files (never edit them by hand — the next generator run
overwrites them, and the verifier fails the build until they match):

- `src/components.gen.ts` — named exports, re-exported by `src/index.ts`
  (tree-shake mode);
- `src/plugin-components.gen.ts` — `registerComponents(app)`, called by
  the plugin's install (global registration mode);
- `src/components/exports.ts` — the `GlobalComponents` template typing.

`src/index.ts` and `src/plugin.ts` themselves stay hand-written and small.
The `.gen` modules are safe to route through only because
`rewriteCssImports()` in `vite.config.ts` strips the CSS imports that
`vite-plugin-lib-inject-css` aggregates onto them — each component's CSS
stays in its own chunk, so per-component CSS tree-shaking survives. Don't
rename the `.gen` files without updating that strip list (the verifier
guards this wiring too).

Typical flows:

- **New component**: `node script/create-component.mjs` — scaffolds the
  component folder, adds the manifest line, and regenerates all entries.
- **Existing .vue file**: add one line to the manifest, then
  `npm run generate:entries`.

Every manifest entry is exported from the package root AND registered
globally in plugin mode — there are no partial modes.

`npm run verify:entries` is the safety net: it independently cross-checks
the manifest, the three generated files and the filesystem (a component
file that exists but is missing from the manifest fails too). It runs in
the pre-commit hook (together with `lint-staged` and `ts-check`; hooks are
installed automatically by `npm install` via husky) and in CI, so an
inconsistent component surface cannot reach `master`.

## Documentation

### Live Documentation
Visit [documentation](https://voicenter-ui.netlify.app/) for interactive component examples and demos.

## Project Documentation
For detailed technical documentation, see the [`docs/`](./docs/) directory:

- **[Quick Start Guide](./docs/AGENTS_QUICK_START.md)** - For developers working on the library
- **[Integration Guide](./docs/AGENTS_INTEGRATION_GUIDE.md)** - For using the library in other projects
- **[Architecture Overview](./docs/ARCHITECTURE.md)** - Project structure and technical details
- **[Documentation Guide](./docs/DOCUMENTATION_GUIDE.md)** - Working with the documentation system
