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
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.
- Where the ticks live. Without
selectedIdsthey are inWorkspacesStore, so every menu on screen shows the same ones. WithselectedIdsthey are yours: the store is left alone, and you hear each change fromselectionChangedand 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
predicatenarrows 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
import { Component, signal } from "@angular/core";
import { WorkspaceMention, type Workspace } from "@sinequa/atomic-angular/document-workspaces";
@Component({
selector: "mention-search-example",
imports: [WorkspaceMention],
template: `
<workspace-mention searchable searchFields="name+description" />
<workspace-mention searchable selectAll [predicate]="writableOnly" [selectedIds]="ids()" (selectionChanged)="ids.set($event.ids)" />
`,
})
export class MentionSearchExample {
readonly ids = signal<readonly string[]>([]);
readonly writableOnly = (workspace: Workspace) => workspace.currentUserRole !== "reader";
}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.
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).
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.
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.
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
Save to a workspace
Let the user put documents into a workspace, or move them to another — a popover under your button, a sheet on a narrow screen — and tick workspaces to mention.
Custom elements
Use the workspaces view, the save-to-workspace picker and the mention list from a page that is not Angular, as custom elements with a prebuilt stylesheet.