Atomic Angular — Doc interne

Mention workspaces

Let the user tick the workspaces to mention — in a chat's "+" menu or an agent's configuration — with search, select all, categories and a new-workspace button, as a ready-made menu or as pieces you lay out yourself.

<workspace-mention> is the workspaces as rows to tick. Empty, it is a ready-made menu; with your own content inside, it only provides the state — search, category, ticks, "select all", "new workspace" — and you lay out the pieces and the rows as you like.

Minimal example

The ready-made menu
mention-menu.component.ts
import { Component } from "@angular/core";
import { type MentionSelection, WorkspaceMention } from "@sinequa/atomic-angular/document-workspaces";

@Component({
  selector: "mention-menu-example",
  imports: [WorkspaceMention],
  template: `
    <workspace-mention
      searchable
      selectAll
      allowCreate
      (selectionChanged)="changed($event)"
      (workspaceCreated)="created($event.id)" />
  `,
})
export class MentionMenuExample {
  changed(selection: MentionSelection) {
    console.log(selection.ids, selection.count);
  }

  created(id: string) {
    console.log("created", id);
  }
}

The search box, "select all" and "new workspace" are each switched on by an input, all off by default: with none of them, <workspace-mention> is just the rows (and <workspace-mention-list>, which predates it, is the same menu under its old name, with the same inputs).

How it works

<workspace-mention> owns the state, and everything inside it reads it. The state comes from injectWorkspaceMention(), so a component that wants no markup of ours can call that directly.

no selectedIds selectedIds workspace-mention injectWorkspaceMention WorkspacesStore ticks Your selectedIds search, select-all, new, tabs Your rows from items selectionChanged and workspaceCreated
  • Where the ticks live. Without selectedIds they are in WorkspacesStore, so every menu on screen shows the same ones. With selectedIds they are yours: the store is left alone, and you hear each change from selectionChanged and feed it back.
  • Select all works on what is displayed — the current category and search — and unticks them all when they already are. What the search hides keeps its tick. It shows a mixed state when only some of the displayed rows are ticked.
  • New workspace opens the dialog of the workspaces page. The workspace it makes is ticked and shown: the category goes back to "all" and the search is cleared, so it is not hidden.
  • Every workspace is offered, the ones shared read-only too — mentioning one only reads it. A predicate narrows that.
  • With content inside, the ready-made menu is not drawn: the element is only the provider of the state.

Recipes

Search the description too, or narrow what is offered

The search ignores case and accents. By default it matches the name only; name+description matches either.

Lay the pieces out yourself, and keep the ticks in a configuration

Put content inside <workspace-mention> and take the state with a template reference. The ticks here belong to an agent's configuration, not to the store.

Pieces and your own rows
mention-pieces.component.ts
import { Component, signal } from "@angular/core";
import {
  WorkspaceMention,
  WorkspaceMentionNew,
  WorkspaceMentionSearch,
  WorkspaceMentionSelectAll,
  WorkspaceMentionTabs,
} from "@sinequa/atomic-angular/document-workspaces";
import { CheckboxComponent } from "@sinequa/galactik";

@Component({
  selector: "mention-pieces-example",
  imports: [WorkspaceMention, WorkspaceMentionNew, WorkspaceMentionSearch, WorkspaceMentionSelectAll, WorkspaceMentionTabs, CheckboxComponent],
  template: `
    <workspace-mention
      #ws="workspaceMention"
      searchFields="name+description"
      [selectedIds]="config().workspaces"
      (selectionChanged)="save($event.ids)">
      <workspace-mention-tabs />
      <div class="flex items-center gap-2">
        <workspace-mention-search class="min-w-0 flex-1" />
        <workspace-mention-new />
      </div>
      <workspace-mention-select-all />
      @for (w of ws.items(); track w.id) {
        <checkbox [checked]="w.selected" (checkedChange)="ws.toggle(w.id, $event)">
          <strong>{{ w.name }}</strong> — {{ w.description }} ({{ w.documentCount }} docs)
        </checkbox>
      }
    </workspace-mention>
  `,
})
export class MentionPiecesExample {
  readonly config = signal({ name: "My agent", workspaces: [] as string[] });

  save(ids: string[]) {
    this.config.update(config => ({ ...config, workspaces: ids }));
  }
}

ws.items() are the rows the search, the category and the predicate leave, each with its id, name, description, color, documentCount and whether it is selected.

Put the categories in a sidebar

<workspace-mention-tabs> draws the categories as tabs. To draw them another way, read tabs, tab and counts() from the state: all, owned (creator or owner) and shared (reader).

Categories in a sidebar
mention-sidebar.component.ts
import { Component, signal } from "@angular/core";
import { WorkspaceMention, WorkspaceMentionSearch, WorkspaceMentionSelectAll } from "@sinequa/atomic-angular/document-workspaces";
import { CheckboxComponent } from "@sinequa/galactik";

@Component({
  selector: "mention-sidebar-example",
  imports: [WorkspaceMention, WorkspaceMentionSearch, WorkspaceMentionSelectAll, CheckboxComponent],
  template: `
    <workspace-mention #side="workspaceMention" [selectedIds]="ids()" (selectionChanged)="ids.set($event.ids)">
      <nav aria-label="Categories">
        @for (t of side.tabs; track t.id) {
          <button type="button" [attr.aria-pressed]="side.tab() === t.id" (click)="side.tab.set(t.id)">
            {{ labels[t.id] }} ({{ side.counts()[t.id] }})
          </button>
        }
      </nav>
      <workspace-mention-search />
      <workspace-mention-select-all />
      @for (w of side.items(); track w.id) {
        <checkbox [checked]="w.selected" (checkedChange)="side.toggle(w.id, $event)">{{ w.name }}</checkbox>
      } @empty {
        <p>Nothing here.</p>
      }
    </workspace-mention>
  `,
})
export class MentionSidebarExample {
  readonly ids = signal<readonly string[]>([]);
  readonly labels = { all: "All", editor: "Owned", reader: "Shared" };
}

A workspace has no "favorites" notion yet, so there is no such category. predicate is where one would plug in.

Change the look of the search box and the button

<workspace-mention-search> takes size, textSize, placeholder and aria-label. <workspace-mention-new> takes the button's own variant, scheme and size, and content placed inside replaces its icon and label.

Size, variant and label
mention-look.component.ts
import { Component } from "@angular/core";
import { WorkspaceMention, WorkspaceMentionNew, WorkspaceMentionSearch } from "@sinequa/atomic-angular/document-workspaces";

@Component({
  selector: "mention-look-example",
  imports: [WorkspaceMention, WorkspaceMentionNew, WorkspaceMentionSearch],
  template: `
    <workspace-mention>
      <workspace-mention-search size="lg" placeholder="Find a workspace" aria-label="Workspaces" />
      <workspace-mention-new variant="primary" scheme="default" size="lg">Add a workspace</workspace-mention-new>
    </workspace-mention>
  `,
})
export class MentionLookExample {}

Use the state with no component of ours

injectWorkspaceMention() gives the same state with no markup. Call it from a component's field initializer.

injectWorkspaceMention
mention-headless.component.ts
import { Component } from "@angular/core";
import { injectWorkspaceMention } from "@sinequa/atomic-angular/document-workspaces";

@Component({
  selector: "mention-headless-example",
  template: `
    <input #search placeholder="Filter…" [value]="mention.query()" (input)="mention.query.set(search.value)" />
    @for (w of mention.items(); track w.id) {
      <button type="button" [attr.aria-pressed]="w.selected" (click)="mention.toggle(w.id, !w.selected)">{{ w.name }}</button>
    }
    <button type="button" (click)="mention.toggleAll()">{{ mention.allSelected() ? "Unselect all" : "Select all" }}</button>
    <button type="button" (click)="mention.create()">New workspace…</button>
    <p>{{ mention.count() }} selected</p>
  `,
})
export class MentionHeadlessExample {
  protected readonly mention = injectWorkspaceMention({ searchFields: () => "name+description" });
}

Its options are read reactively, so they are functions: searchFields, selectedIds, predicate, and the callbacks onSelectionChange and onCreated.

Options

Prop

Type

<workspace-mention> has two outputs: selectionChanged (MentionSelection: ids and count) and workspaceCreated (the Workspace).

What a template reference (#ws="workspaceMention") or injectWorkspaceMention() gives:

Prop

Type

Pitfalls

What's next

On this page