# @qaecy/cue-ui

Framework-agnostic web components for the [QAECY Cue platform](https://qaecy.com).  
Drop them into any HTML page — no build step required.

## Components

| Custom element                  | Description                                                                       |
| ------------------------------- | --------------------------------------------------------------------------------- |
| `<cue-logo>`                    | Animated QAECY logo                                                               |
| `<cue-document-viewer>`         | Document viewer with Cue SDK integration (PDF, BIM, CAD, images, spreadsheets, …) |
| `<cue-document-list>`           | Lazy document list that fetches metadata from the Cue SDK                         |
| `<cue-entity-list>`             | Lazy entity list that fetches entity data from the Cue SDK                        |
| `<cue-entity-viewer>`           | Full entity detail view — info, relationships, documents, and optional map        |
| `<cue-project-list>`            | Lists all projects the authenticated user is a member of, with an optional map    |
| `<cue-id-builder>`              | Interactive IFC/SPARQL ID builder                                                 |
| `<cue-block-project-documents>` | Project documents content block with charts and statistics                        |
| `<cue-document-charts>`         | Interactive document-statistics charts (by file type and/or content category)     |
| `<cue-sparql-table>`            | Declarative, column-configurable SPARQL table with built-in Cue SDK integration   |

---

## CDN quickstart

The library ships as a **single script** that lazy-loads only the components you request — no `npm install` needed.

```html
<!doctype html>
<html lang="en">
    <head>
        <meta charset="utf-8" />
        <title>My Cue App</title>
        <!-- Optional: component styles -->
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@qaecy/cue-ui/styles.css" />
        <style>
            body {
                margin: 0;
                height: 100vh;
                display: flex;
                flex-direction: column;
            }
            cue-logo {
                width: 160px;
                padding: 1rem;
            }
            cue-document-viewer {
                flex: 1;
            }
        </style>
    </head>
    <body>
        <!-- Static elements are detected automatically -->
        <cue-logo size="m"></cue-logo>

        <!-- Document viewer (populated via JS below) -->
        <div id="viewer-host" style="flex:1; display:contents;"></div>

        <!--
          1. Single loader script. Angular boots once; only requested components
             are downloaded as separate lazy chunks.

          data-components lists components that will be created dynamically
          (i.e. not present in the HTML at load time). Statically placed tags
          are detected automatically and do not need to be listed here.
        -->
        <script
            src="https://cdn.jsdelivr.net/npm/@qaecy/cue-ui/index.js"
            type="module"
            data-components="cue-document-viewer"
        ></script>

        <!-- 2. Cue SDK + app logic -->
        <script type="module">
            import { Cue } from 'https://cdn.jsdelivr.net/npm/@qaecy/cue-sdk/dist/index.esm.js';

            // 3. Authenticate
            const cue = new Cue();
            await cue.auth.signIn('google');

            // 4. Mount the document viewer once the custom element is registered
            await customElements.whenDefined('cue-document-viewer');

            const viewer = document.createElement('cue-document-viewer');

            // Required: authenticated SDK instance (must be set as a property, not an attribute)
            viewer.cue = cue;
            viewer.uuid = 'your-document-uuid'; // or 'uuid?page=3' to open at a page
            viewer.projectId = 'your-project-id';

            document.getElementById('viewer-host').appendChild(viewer);
        </script>
    </body>
</html>
```

### Installing via npm

```bash
npm install @qaecy/cue-sdk @qaecy/cue-ui
```

```js
// Import the loader — registers the smart loader that boots Angular once
import '@qaecy/cue-ui';

// Optionally import styles
import '@qaecy/cue-ui/styles';
```

---

## `<cue-logo>`

Renders the animated QAECY logo. Adapts automatically to light/dark mode.

| Property / attribute | Type                | Default | Description                      |
| -------------------- | ------------------- | ------- | -------------------------------- |
| `size`               | `'s' \| 'm' \| 'l'` | `'m'`   | Size preset                      |
| `active`             | `boolean`           | `true`  | Triggers the draw animation once |
| `continuous`         | `boolean`           | `false` | Loops the draw animation         |

```html
<!-- Static logo -->
<cue-logo size="l"></cue-logo>

<!-- Animated on load -->
<cue-logo size="m" active></cue-logo>

<!-- Continuously looping -->
<cue-logo continuous></cue-logo>
```

> **Loader** — add `cue-logo` to `data-components` if you create it dynamically; otherwise it is detected automatically. See [CDN quickstart](#cdn-quickstart).

---

## `<cue-entity-list>`

Renders a lazy-loaded entity table. Pass `iris`, a `sdkState` containing a `CueProjectEntities` instance, and optionally a Mapbox token for location views.

| Property             | Type                  | Required | Description                                                                          |
| -------------------- | --------------------- | -------- | ------------------------------------------------------------------------------------ |
| `iris`               | `string[]`            | ✅       | Entity IRIs to display in the list.                                                  |
| `sdkState`           | `{ cue?; entities? }` | ✅       | SDK context. Provide at least `entities` (`CueProjectEntities` instance).            |
| `mapboxToken`        | `string`              | ❌       | Mapbox access token. Required only if entity location data should be shown on a map. |
| `paginationPageSize` | `number`              | ❌       | Number of rows shown per page in the grid paginator. Default: `10`.                  |

> **Mapbox token** — the map view is only active when a valid `mapboxToken` is set. Without it, location fetching is skipped and the map tab is hidden. Obtain a token from [mapbox.com](https://www.mapbox.com/).

The component emits two events:

| Event         | Payload     | Description                                 |
| ------------- | ----------- | ------------------------------------------- |
| `rowClicked`  | `EntityRow` | Fires when the user clicks a row.           |
| `clickedOpen` | `EntityRow` | Fires when the user opens an entity detail. |

```js
await customElements.whenDefined('cue-entity-list');

const list = document.createElement('cue-entity-list');

// sdkState must be set as a JS property, not an HTML attribute
list.sdkState = { entities: cue.project('your-project-id').entities };
list.iris = ['https://example.com/entity/uuid-1', 'https://example.com/entity/uuid-2'];
list.paginationPageSize = 25;

list.addEventListener('rowClicked', (e) => console.log('Row clicked:', e.detail));
list.addEventListener('clickedOpen', (e) => console.log('Open entity:', e.detail));

document.getElementById('list-host').appendChild(list);
```

> **Loader** — add `cue-entity-list` to `data-components` if you create it dynamically; otherwise it is detected automatically. See [CDN quickstart](#cdn-quickstart).

---

## `<cue-id-builder>`

A self-contained interactive tool that converts a text string into a context-based GUID (used for IFC/SPARQL identifiers). No external inputs required — drop it in and it works standalone.

```html
<cue-id-builder></cue-id-builder>
```

> **Loader** — add `cue-id-builder` to `data-components` if you create it dynamically; otherwise it is detected automatically. See [CDN quickstart](#cdn-quickstart).

---

## `<cue-document-viewer>`

Fetches and renders a Cue document. Automatically picks the best viewer based on the file type. Requires an authenticated `Cue` SDK instance.

| Property    | Type                 | Required | Description                                                                          |
| ----------- | -------------------- | -------- | ------------------------------------------------------------------------------------ |
| `cue`       | `Cue` (SDK instance) | ✅       | Authenticated Cue SDK instance. **Set as a JS property**, not an HTML attribute.     |
| `uuid`      | `string`             | ✅       | Document UUID. Append `?page=N` to open at a specific page, e.g. `'abc-123?page=5'`. |
| `projectId` | `string`             | ✅       | Cue project (space) ID the document belongs to.                                      |

Language is managed globally by the SDK via `cue.api.language` / `cue.api.setLanguage(lang)` and applies to all language-sensitive requests.  
Document metadata caching is also managed by the SDK; the component reuses SDK-owned per-project document state automatically.

Supported file types: PDF, IFC/BIM, DXF/CAD, images (PNG, JPG, SVG, …), Markdown, plain text, CSV/XLSX spreadsheets.

> **Loader** — add `cue-document-viewer` to `data-components` if you create it dynamically; otherwise it is detected automatically. See [CDN quickstart](#cdn-quickstart).

---

## `<cue-document-list>`

Renders a lazy-loaded document table. Pass a `projectId`, `uuids`, and `sdkState`; the component requests metadata and categories internally.

| Property                         | Type                                                                  | Required | Description                                                                                                                                         |
| -------------------------------- | --------------------------------------------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `projectId`                      | `string`                                                              | ✅       | Cue project (space) ID.                                                                                                                             |
| `uuids`                          | `string[]`                                                            | ✅       | Document UUIDs to display.                                                                                                                          |
| `sdkState`                       | `{ cue?; view?; documents?; language?; availableContentCategories? }` | ✅       | SDK context. Provide at least one of `cue`, `view`, or `documents`.                                                                                 |
| `suffixes`                       | `string[]`                                                            | ❌       | File extensions to filter by (e.g. `['.pdf', '.ifc']`). Auto-fetches matching UUIDs. Takes precedence over `uuids`.                                 |
| `mimeTypes`                      | `string[]`                                                            | ❌       | MIME types to filter by (e.g. `['application/pdf']`). Auto-fetches matching UUIDs. Lower precedence than `suffixes`.                                |
| `contentCategories`              | `string[]`                                                            | ❌       | Content-category IRIs to filter by. Auto-fetches matching UUIDs via `documentsByContentCategory()`. Lower precedence than `suffixes` / `mimeTypes`. |
| `simple`                         | `boolean`                                                             | ❌       | Renders a compact list + paginator instead of ag-grid/flip/settings. Default: `false`.                                                              |
| `pageSize`                       | `number`                                                              | ❌       | Host-controlled page size used by both simple and non-simple modes. Default: `10`.                                                                  |
| `prefetchPages`                  | `number`                                                              | ❌       | Lazy-load buffer size multiplier (`pageSize * prefetchPages`). Default: `3`.                                                                        |
| `showOpenInDocumentViewerAction` | `boolean`                                                             | ❌       | Shows "Open in document viewer" menu item. Default: `false`.                                                                                        |
| `showOpenInFileManagerAction`    | `boolean`                                                             | ❌       | Shows "Open in file manager" menu item. Default: `false`.                                                                                           |
| `customMenuItems`                | `Array<{ label; icon?; action }>`                                     | ❌       | Custom context-menu entries appended after the standard menu items.                                                                                 |

By default, only the `Download` action is shown in the row context menu.

Set `simple = true` for a narrow sidebar-style document list (no ag-grid, no column settings, no flip card).

The component emits the same row click outputs as the internal document list (`clickedDocument`, `clickedDownloadDocument`, `clickedOpen`, `clickedOpenInDir`).

### Wiring charts to the document list

`<cue-document-charts>` emits a `selected` event with `{ filterBy, values, label }`. Map it directly to the matching `<cue-document-list>` filter input:

```js
charts.addEventListener('selected', (e) => {
    const { filterBy, values } = e.detail;
    if (filterBy === 'suffix') {
        list.suffixes = values; // e.g. ['.pdf', '.ifc']
        list.contentCategories = undefined;
    } else {
        list.contentCategories = values; // content-category IRIs
        list.suffixes = undefined;
    }
});

charts.addEventListener('selectionCleared', () => {
    list.suffixes = undefined;
    list.contentCategories = undefined;
});
```

Custom menu items are appended after the standard ones:

```js
const list = document.querySelector('cue-document-list');

list.customMenuItems = [
    {
        label: 'Copy UUID',
        icon: 'copy',
        action: (document) => {
            navigator.clipboard.writeText(document.id);
        },
    },
    {
        label: 'Log Row',
        icon: 'document',
        action: (document, cell) => {
            console.log('Document:', document, 'Cell data:', cell);
        },
    },
];
```

> **Loader** — add `cue-document-list` to `data-components` if you create it dynamically; otherwise it is detected automatically. See [CDN quickstart](#cdn-quickstart).

---

## `<cue-sparql-table>`

A fully interactive SPARQL table with declarative, serialisable column definitions and built-in Cue SDK integration. Each column is a SPARQL `SELECT` query whose bindings are reduced to a single cell value using a configurable **strategy** (`first`, `join`, `count`, `sum`, …). No imperative formatter functions are needed.

The component also supports an optional **row definition** — a SPARQL `SELECT` that returns all `?subject` IRIs for the table rows. When set, the table is automatically paginated and only the current page's rows are fetched per batch (VALUES-clause injection).

| Property        | Type                                          | Required | Description                                                                                                         |
| --------------- | --------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------- |
| `sdkState`      | `{ cue: Cue; projectId: string; graphType? }` | ✅       | Authenticated Cue instance + project ID. Wires up query and row-definition handlers automatically.                  |
| `columns`       | `ColumnDefSPARQL[]`                           | ✅       | Column definitions. Two-way bindable — edits made in the built-in column-settings panel propagate back to the host. |
| `rowDefinition` | `string`                                      | ❌       | SPARQL SELECT returning `?subject` IRIs. Enables pagination. Pass `''` to disable (load all rows at once).          |
| `pageSize`      | `number`                                      | ❌       | Rows per page when a row definition is set. Default: `10`.                                                          |
| `bufferFactor`  | `number`                                      | ❌       | Pages to batch-fetch in one go (`pageSize × bufferFactor` subjects per request). Default: `5`.                      |
| `graphType`     | `'fuseki' \| 'qlever'`                        | ❌       | SPARQL engine to query against (inside `sdkState`). Default: `'fuseki'`.                                            |

The component emits two events:

| Event          | Payload    | Description                                              |
| -------------- | ---------- | -------------------------------------------------------- |
| `visibleRows`  | `any[]`    | Fires whenever the set of visible ag-grid rows changes.  |
| `columnDefsAG` | `ColDef[]` | Fires when ag-grid column definitions are (re-)computed. |

```js
await customElements.whenDefined('cue-sparql-table');

const table = document.createElement('cue-sparql-table');

// sdkState must be set as a JS property
table.sdkState = { cue, projectId: 'your-project-id' };

// Define columns — each has a SPARQL SELECT query and a reduction strategy
table.columns = [
    {
        field: 'label',
        headerName: 'Label',
        query: `PREFIX qcy: <https://dev.qaecy.com/ont#>
SELECT ?subject ?val
WHERE { ?subject qcy:hasProperty [ qcy:label "LongName" ; qcy:value ?val ] }`,
        strategy: { type: 'first' },
    },
    {
        field: 'storey',
        headerName: 'Storey',
        query: `PREFIX qcy: <https://dev.qaecy.com/ont#>
PREFIX qcy-e: <https://dev.qaecy.com/enum#>
SELECT ?subject ?val
WHERE { ?lvl qcy:entityCategory qcy-e:Storey ; qcy:containsSpace ?subject ; qcy:hasProperty [ qcy:label "LongName" ; qcy:value ?val ] }`,
        strategy: { type: 'first' },
    },
];

// Optional: paginate over a specific set of subjects
table.rowDefinition = `PREFIX qcy: <https://dev.qaecy.com/ont#>
PREFIX qcy-e: <https://dev.qaecy.com/enum#>
SELECT ?subject WHERE { ?subject qcy:entityCategory qcy-e:Space }`;

table.pageSize = 20;

// Respond to row visibility changes
table.addEventListener('visibleRows', (e) => console.log('Visible rows:', e.detail));

document.getElementById('table-host').appendChild(table);
```

### Column strategies

Each column definition may include a `strategy` object that controls how multiple SPARQL bindings for the same `?subject` are collapsed into a single cell:

| Strategy | Description                                            |
| -------- | ------------------------------------------------------ |
| `first`  | Use the first binding value (default)                  |
| `join`   | Concatenate all values with a configurable `separator` |
| `count`  | Number of distinct bindings                            |
| `sum`    | Numeric sum of all bindings                            |
| `min`    | Minimum value                                          |
| `max`    | Maximum value                                          |

Columns also support an optional `postProcess` step (e.g. `{ type: 'formula', expression: 'ROUND(?val, 2) * 1000' }`) for lightweight value transformations after the strategy is applied.

The built-in **column-settings panel** (opened via the gear icon) lets users add, remove, reorder, and edit any column's query, strategy, and post-processing at runtime — no page reload needed.

> **Loader** — add `cue-sparql-table` to `data-components` if you create it dynamically; otherwise it is detected automatically. See [CDN quickstart](#cdn-quickstart).

---

## `<cue-document-charts>`

Visualises project document statistics as interactive pie / doughnut charts. Connects directly to the Cue SDK — pass a `cue` instance (or a pre-built `view` / `documents`) and the component fetches the overview automatically.

Two charts are available and can be shown together or individually:

- **suffix** — documents grouped by MIME category (PDF, CAD, BIM, …). Click a category to drill down to individual file extensions.
- **content-category** — documents grouped by their semantic content category. Builds a nested tree when category metadata is available.

| Property                 | Type                                                                  | Required | Description                                                                                                               |
| ------------------------ | --------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------- |
| `projectId`              | `string`                                                              | ✅       | Cue project (space) ID. Required when passing `cue` in `sdkState`.                                                        |
| `sdkState`               | `{ cue?; view?; documents?; language?; availableContentCategories? }` | ✅       | SDK context. Provide at least one of `cue`, `view`, or `documents`.                                                       |
| `mode`                   | `'both' \| 'suffix' \| 'content-category'`                            | ❌       | Which chart(s) to show. Default: `'both'`.                                                                                |
| `stackCharts`            | `boolean`                                                             | ❌       | Stack charts vertically instead of side-by-side (only relevant when `mode='both'`). Default: `false`.                     |
| `chartHeight`            | `number`                                                              | ❌       | Height in pixels of each chart. Default: `400`.                                                                           |
| `showDocumentListButton` | `boolean`                                                             | ❌       | When `true`, a button appears after the user makes a selection. Clicking it emits `showInDocumentList`. Default: `false`. |

The component emits three events:

| Event                | Payload                                         | Description                                                                                    |
| -------------------- | ----------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| `selected`           | `{ filterBy; values: string[]; label: string }` | Fires when the user clicks a chart segment.                                                    |
| `selectionCleared`   | `{ chart: 'suffix' \| 'content-category' }`     | Fires when the canvas background is clicked, clearing the selection.                           |
| `showInDocumentList` | `{ filterBy; values: string[]; label: string }` | Fires when the "List documents…" button is clicked (requires `showDocumentListButton = true`). |

`filterBy` is `'suffix'` (resolved dot-extensions, e.g. `['.pdf', '.xlsx']`) or `'content-category'` (category IRIs). Pass `values` directly to `<cue-document-list>`'s `suffixes` or the `requestDocumentsByFilter` handler of your own document list.

```js
await customElements.whenDefined('cue-document-charts');

const charts = document.createElement('cue-document-charts');

// Required: set as JS properties, not HTML attributes
charts.projectId = 'your-project-id';
charts.sdkState = { cue }; // component creates its own CueProjectView internally

// Optional
charts.mode = 'both'; // 'both' | 'suffix' | 'content-category'
charts.stackCharts = false; // true = column layout
charts.showDocumentListButton = true; // show button after a selection is made

// React to segment clicks
charts.addEventListener('selected', (e) => {
    const { filterBy, values, label } = e.detail;
    console.log(`Selected "${label}" (${filterBy}):`, values);
});

charts.addEventListener('selectionCleared', () => {
    console.log('Selection cleared');
});

// Fired when the user clicks the "List documents…" button
charts.addEventListener('showInDocumentList', (e) => {
    const { filterBy, values } = e.detail;
    const list = document.querySelector('cue-document-list');
    if (filterBy === 'suffix') {
        list.suffixes = values;
        list.contentCategories = undefined;
    } else {
        list.contentCategories = values;
        list.suffixes = undefined;
    }
});

document.getElementById('charts-host').appendChild(charts);
```

If you already have a `CueProjectView` or `CueProjectDocuments` instance, pass it directly to avoid creating a second SDK object:

```js
// Reuse an existing view
charts.sdkState = { view: myProjectView };

// Or a standalone documents instance
charts.sdkState = { documents: myProjectDocs, availableContentCategories: myCategories };
```

> **Loader** — add `cue-document-charts` to `data-components` if you create it dynamically; otherwise it is detected automatically. See [CDN quickstart](#cdn-quickstart).

---

## `<cue-block-project-documents>`

A content block that visualises project document statistics — documents by content category, by file type, total size, and duplicates — using interactive pie charts.

| Property | Type                   | Required | Description                                                                                      |
| -------- | ---------------------- | -------- | ------------------------------------------------------------------------------------------------ |
| `data`   | `ProjectDocumentsData` | ❌       | Document statistics payload. The component renders empty until set.                              |
| `config` | `BlockConfig`          | ✅       | Block configuration including `projectId`, `handlers`, and privileges. **Set as a JS property.** |

```js
await customElements.whenDefined('cue-block-project-documents');

const block = document.createElement('cue-block-project-documents');

block.config = {
    projectId: 'your-project-id',
    graphIsSaturated: false,
    privileges: { canEdit: false },
    portalBaseURL: 'https://app.qaecy.com',
    resourceBaseURL: 'https://cue.qaecy.com/r/',
};

block.data = {
    documentsBySuffix: { pdf: { count: 12, size: 4096000 } },
    documentsByContentCategory: { drawings: { count: 5, size: 1024000 } },
    duplicateCount: 2,
};

document.getElementById('block-host').appendChild(block);
```

> **Loader** — add `cue-block-project-documents` to `data-components` if you create it dynamically; otherwise it is detected automatically. See [CDN quickstart](#cdn-quickstart).

---

## `<cue-entity-viewer>`

Renders the full detail view for a single entity: an info panel (label + categories), a relationship graph, linked documents, and an optional location map.

Requires an authenticated `Cue` SDK instance. Internally creates `CueProjectEntities` and `CueProjectDocuments` instances and wires all data-fetching automatically.

| Property      | Type                 | Required | Description                                                                               |
| ------------- | -------------------- | -------- | ----------------------------------------------------------------------------------------- |
| `cue`         | `Cue` (SDK instance) | ✅       | Authenticated Cue SDK instance. **Set as a JS property**, not an HTML attribute.          |
| `projectId`   | `string`             | ✅       | Cue project (space) ID that the entity belongs to.                                        |
| `iri`         | `string`             | ✅       | Full RDF IRI of the entity to display, e.g. `'https://cue.qaecy.com/r/{project}/{uuid}'`. |
| `mapboxToken` | `string`             | ❌       | Mapbox access token. Required to show entity location on a map. Omit to hide the map tab. |

> **Mapbox token** — the map tab is only shown when a valid `mapboxToken` is provided. Without it, no map-related data is fetched and the tab does not appear. Obtain a token from [mapbox.com](https://www.mapbox.com/).

The component emits one event:

| Event        | Payload  | Description                                             |
| ------------ | -------- | ------------------------------------------------------- |
| `openEntity` | `string` | Entity UUID when the user navigates to a linked entity. |

```js
await customElements.whenDefined('cue-entity-viewer');

const viewer = document.createElement('cue-entity-viewer');

// Required: set as JS properties, not HTML attributes
viewer.cue = cue;
viewer.projectId = 'your-project-id';
viewer.iri = 'https://cue.qaecy.com/r/your-project-id/entity-uuid';

// Optional: enable the location map tab
viewer.mapboxToken = 'pk.eyJ1…';

// Navigate within your app when the user opens a related entity
viewer.addEventListener('openEntity', (e) => {
    viewer.iri = `https://cue.qaecy.com/r/your-project-id/${e.detail}`;
});

document.getElementById('viewer-host').appendChild(viewer);
```

> **Loader** — add `cue-entity-viewer` to `data-components` if you create it dynamically; otherwise it is detected automatically. See [CDN quickstart](#cdn-quickstart).

---

## `<cue-project-list>`

Fetches all projects the authenticated user is a member of via `cue.projects.listProjects()` and renders them in a sortable, selectable ag-grid table. Projects that carry a `location` (WKT string) automatically show a **Map** tab alongside the **List** tab. Clicking a row emits the project's UUID.

| Property      | Type                                                                    | Required | Description                                                                                                               |
| ------------- | ----------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------- |
| `sdkState`    | `{ cue: Cue }`                                                          | ✅       | Must include an authenticated `Cue` instance. Projects are fetched via `cue.projects.listProjects()`.                     |
| `mapboxToken` | `string`                                                                | ❌       | Mapbox access token. When provided, projects that have a `location` value are shown on an interactive map tab.            |
| `variant`     | `'none' \| 'default' \| 'primary' \| 'secondary' \| 'accent' \| 'fade'` | ❌       | Card styling variant. `'none'` (default) renders with no card border/background. Other values apply the named card style. |

> **Mapbox token** — the map tab only appears when at least one project has a `location` field **and** a `mapboxToken` is supplied. Without a token the component always shows the list-only view. Obtain a token from [mapbox.com](https://www.mapbox.com/).

The component emits one event:

| Event             | Payload  | Description                                              |
| ----------------- | -------- | -------------------------------------------------------- |
| `projectSelected` | `string` | UUID of the project the user clicked in the list or map. |

```js
await customElements.whenDefined('cue-project-list');

const list = document.createElement('cue-project-list');

// sdkState must be set as a JS property — not an HTML attribute
list.sdkState = { cue };

// Optional: show projects on a map when they have a location
list.mapboxToken = 'pk.eyJ1…';

// Optional: apply a card style ('none' | 'default' | 'primary' | 'secondary' | 'accent' | 'fade')
list.variant = 'primary';

list.addEventListener('projectSelected', (e) => {
    console.log('Selected project UUID:', e.detail);
});

document.getElementById('list-host').appendChild(list);
```

Static placement is also supported — `sdkState` must still be set via JavaScript:

```html
<cue-project-list id="projects" variant="primary"></cue-project-list>

<script type="module">
    import { Cue } from 'https://cdn.jsdelivr.net/npm/@qaecy/cue-sdk/dist/index.esm.js';

    const cue = new Cue();
    await cue.auth.signIn('google');

    const list = document.getElementById('projects');
    list.sdkState = { cue };
    list.addEventListener('projectSelected', (e) => console.log(e.detail));
</script>
```

> **Loader** — add `cue-project-list` to `data-components` if you create it dynamically; otherwise it is detected automatically. See [CDN quickstart](#cdn-quickstart).

---

## `<cue-entity-viewer>`

The `cue-document-viewer`, `cue-document-list`, `cue-entity-list`, `cue-entity-viewer`, and `cue-project-list` components require an authenticated [`Cue`](https://www.npmjs.com/package/@qaecy/cue-sdk) instance. The SDK handles authentication and all data fetching — the components never make direct network calls.

```js
import { Cue } from 'https://cdn.jsdelivr.net/npm/@qaecy/cue-sdk/dist/index.esm.js';

const cue = new Cue();
await cue.auth.signIn('google'); // or 'microsoft', 'email', etc.
```

---

## Theming

All visual properties are driven by CSS custom properties defined on `:root`. Because the components do **not** use Shadow DOM, you can override any variable from your own stylesheet — no `::part()` or `::slotted()` selectors needed.

### Colors

```css
:root {
    /* Brand palette */
    --cue-color-blue: #2859e1;
    --cue-color-green: #e2f552; /* accent */

    /* Semantic color keys — override these to retheme without touching raw values */
    --cue-primary: var(--cue-color-blue);
    --cue-primaryContrast: #ffffff;

    --cue-accent: var(--cue-color-green);
    --cue-accentContrast: #121c2b;

    --cue-main-background: #f5f6f9;
    --cue-main-foreground: #121c2b;

    --cue-border-color: #d9d9d9;
}
```

Apply a dark theme by adding the `dark` class to `<body>` (or any ancestor element):

```css
body.dark {
    --cue-main-background: #121c2b;
    --cue-main-foreground: #ffffff;
    --cue-default: #0e1827;
    --cue-defaultContrast: #f5f6f9;
    --cue-border-color: #404955;
}
```

### Typography

```css
:root {
    --cue-font-family: 'Inter', sans-serif; /* swap out Poppins */

    --cue-font-weight-regular: 400;
    --cue-font-weight-medium: 500;
    --cue-font-weight-semibold: 600;
}
```

> The default font is **Poppins**, loaded via the bundled stylesheet. If you override `--cue-font-family`, load your chosen font yourself (e.g. via Google Fonts).

### Spacing & element sizes

```css
:root {
    /* Gap / padding scale */
    --cue-dim-gap-xs: 0.25rem;
    --cue-dim-gap-s: 0.5rem;
    --cue-dim-gap-m: 1rem;
    --cue-dim-gap-l: 1.875rem;

    /* Interactive element heights */
    --cue-dim-elem-s: 2rem; /* small buttons, inputs */
    --cue-dim-elem-m: 2.75rem; /* default */
    --cue-dim-elem-l: 3.5rem; /* large */
}
```

### Minimal brand override example

```html
<style>
    :root {
        --cue-primary: #7c3aed; /* violet */
        --cue-primaryContrast: #ffffff;
        --cue-accent: #f59e0b; /* amber */
        --cue-accentContrast: #1c1917;
        --cue-font-family: 'Inter', sans-serif;
    }
</style>
```
