@l3mpire/ui 3.2.1 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/USAGE.md CHANGED
@@ -116,7 +116,7 @@ Works with all Tailwind spacing prefixes: `p-`, `px-`, `py-`, `m-`, `mx-`, `my-`
116
116
 
117
117
  **Line heights:** `leading-2xs` (14px), `leading-xs` (16px), `leading-sm` (20px), `leading-base` (24px), `leading-md` (28px), `leading-lg` (32px)
118
118
 
119
- **Font weights:** `font-regular` (400), `font-medium` (500), `font-semibold` (600), `font-bold` (700)
119
+ **Font weights:** `font-regular` (400), `font-medium` (500), `font-semibold` (600), `font-solid` (700)
120
120
 
121
121
  ### Typography component
122
122
 
@@ -129,7 +129,8 @@ import { Typography } from "@l3mpire/ui";
129
129
  ```
130
130
 
131
131
  **Variants:** `h1`, `h2`, `h3`, `lg`, `md`, `sm`, `xs`
132
- **Weights:** `regular`, `medium`, `bold`
132
+ **Weights:** `regular`, `medium`, `semibold`, `solid`
133
+ Default weight by variant (from Figma text styles): `h1` → `semibold`, `h2`/`h3` → `medium`, body (`lg`/`md`/`sm`/`xs`) → `regular`. Override with the `weight` prop.
133
134
 
134
135
  ---
135
136
 
@@ -141,6 +142,57 @@ import { Typography } from "@l3mpire/ui";
141
142
  | `shadow-md` | Cards, dropdowns |
142
143
  | `shadow-lg` | Modals, popovers |
143
144
  | `shadow-focus-ring` | Focus state ring (3px blue) |
145
+ | `shadow-elevation-sm` | Tight 1px AI cell / chip lift |
146
+ | `shadow-elevation-md` | AI popover / floating panel |
147
+ | `shadow-elevation-lg` | AI hero / standalone card |
148
+
149
+ ---
150
+
151
+ ## AI Utilities
152
+
153
+ Animation primitives shared by AI cells, AI badges, suggestion dots and any
154
+ surface that should look like it is thinking. The shimmer-text and
155
+ icon-spin classes are the load-bearing ones; the rest are decorative.
156
+
157
+ | Class | Usage |
158
+ |---|---|
159
+ | `bg-ai-gradient` | Brand AI gradient (blue → violet → indigo, 125deg) |
160
+ | `ai-shimmer-text` | Text with a tertiary → info → tertiary sweep (2.1s) |
161
+ | `ai-icon-spin` | Wrapper that rotates its child icon (see FA Kit caveat below) |
162
+ | `ai-skeleton` | Surface skeleton with a horizontal sheen sweep |
163
+ | `ai-cell-reveal` | One-shot blur(3px) → clear, 320ms fade-in for cells |
164
+ | `ai-pulse` | Breathing scale + opacity for dot indicators |
165
+ | `ai-gradient-text` | Flat info-blue accent for AI labels |
166
+ | `ai-orb` | Decorative gradient disc with a soft glow |
167
+ | `ai-halo` | Conic-gradient halo that rotates around an orb |
168
+ | `ai-ring` | Pulsing concentric ring |
169
+ | `animate-fade-up` | Generic entrance (8px translate + fade) |
170
+ | `animate-pop-in` | Generic entrance (0.85 → 1 scale with overshoot) |
171
+
172
+ All animations are disabled under `@media (prefers-reduced-motion: reduce)` —
173
+ the shimmer text falls back to a flat info color so labels stay readable.
174
+
175
+ ### Spinning icons / FA Kit rule
176
+
177
+ We load FontAwesome through the Kit script, which swaps every `<i class="fa-…">`
178
+ into an `<svg>` via a MutationObserver. Any subsequent className change on the
179
+ icon re-triggers the swap and **interrupts** any rotation animation applied
180
+ directly to the icon. The symptom: an icon that ticks ~90° and resets in a loop.
181
+
182
+ Always wrap the icon in a span that carries the rotation:
183
+
184
+ ```tsx
185
+ // ❌ fa-spin on <Icon> is interrupted by the Kit re-swap
186
+ <Icon icon={faSpinnerThirdOutline} className="fa-spin" />
187
+
188
+ // ✅ rotation lives on a stable parent; the icon is left alone
189
+ <span className="ai-icon-spin">
190
+ <Icon icon={faSpinnerThirdOutline} size="xs" />
191
+ </span>
192
+ ```
193
+
194
+ The same applies to any custom rotation animation — never put it on the icon
195
+ className itself.
144
196
 
145
197
  ---
146
198
 
@@ -211,15 +263,17 @@ import { faStarOutline } from "@l3mpire/icons";
211
263
  <Badge variant="light" type="success" size="sm">Active</Badge>
212
264
  <Badge variant="outlined" type="critical" icon={faStarOutline}>99</Badge>
213
265
 
214
- // Categorical tone — overrides variant/type
266
+ // Categorical tone — combines with variant (solid/light/outlined), overrides type
215
267
  <Badge tone="indigo">SaaS</Badge>
268
+ <Badge variant="light" tone="amber">Manufacturing</Badge>
269
+ <Badge variant="outlined" tone="emerald">Finance</Badge>
216
270
  ```
217
271
 
218
272
  | Prop | Values |
219
273
  |---|---|
220
274
  | `variant` | `"solid"`, `"light"`, `"outlined"` |
221
275
  | `type` | `"primary"`, `"success"`, `"critical"`, `"warning"`, `"neutral"` |
222
- | `tone` | `"indigo"`, `"rose"`, `"lime"`, `"violet"`, `"cyan"`, `"orange"`, `"emerald"`, `"fuchsia"`, `"amber"`, `"slate"` — categorical palette; overrides `variant`/`type` |
276
+ | `tone` | `"indigo"`, `"rose"`, `"lime"`, `"violet"`, `"cyan"`, `"orange"`, `"emerald"`, `"fuchsia"`, `"amber"`, `"slate"`, `"teal"`, `"sky"`, `"purple"`, `"pink"` — categorical palette; combines with `variant`, overrides `type` |
223
277
  | `size` | `"sm"`, `"md"`, `"lg"` |
224
278
  | `icon` | `IconDefinition` (optional) |
225
279
 
@@ -243,7 +297,7 @@ import { faPaperPlaneOutline } from "@l3mpire/icons";
243
297
  | Prop | Values |
244
298
  |---|---|
245
299
  | `variant` | `"brand"`, `"neutral"` |
246
- | `tone` | `"indigo"`, `"rose"`, `"lime"`, `"violet"`, `"cyan"`, `"orange"`, `"emerald"`, `"fuchsia"`, `"amber"`, `"slate"` — categorical palette; overrides `variant` |
300
+ | `tone` | `"indigo"`, `"rose"`, `"lime"`, `"violet"`, `"cyan"`, `"orange"`, `"emerald"`, `"fuchsia"`, `"amber"`, `"slate"`, `"teal"`, `"sky"`, `"purple"`, `"pink"` — categorical palette; overrides `variant` |
247
301
  | `size` | `"sm"`, `"md"` |
248
302
  | `icon` | `IconDefinition` |
249
303
  | `onClose` | `() => void` (shows remove button when provided) |
@@ -744,7 +798,7 @@ import { InfoMessage } from "@l3mpire/ui";
744
798
 
745
799
  | Prop | Values |
746
800
  |---|---|
747
- | `type` | `"info"`, `"success"`, `"alert"`, `"warning"`, `"empty"` |
801
+ | `type` | `"info"`, `"success"`, `"alert"`, `"warning"`, `"empty"`, `"ai"` |
748
802
  | `title` | `React.ReactNode` (required) |
749
803
  | `description` | `React.ReactNode` |
750
804
  | `onClose` | `() => void` (shows close button when provided) |
@@ -902,6 +956,8 @@ import { faRocketOutline } from "@l3mpire/icons";
902
956
 
903
957
  Active tabs render with concave bottom corners that fuse into the bar divider (real browser-tab look). Inactive tabs get a hover fill + soft shadow on the inner pill (active tabs keep their bg on hover).
904
958
 
959
+ The close `×` only appears **on hover** of a non-pinned tab, as an absolutely-positioned overlay with a gradient fade — it never takes layout space (the label slides under the gradient instead of being pushed). Set `pinned` to hide the close button entirely and show a **"Pinned tab"** tooltip on hover. Set `isEditable` (+ `onEdit`) to add a pencil button in the same overlay, to the left of the close `×`.
960
+
905
961
  | Prop (BrowserTab) | Values |
906
962
  |---|---|
907
963
  | `onAddTab` | `() => void` (shows + button) |
@@ -915,7 +971,10 @@ Active tabs render with concave bottom corners that fuse into the bar divider (r
915
971
  | `icon` | `IconDefinition` |
916
972
  | `badge` | `string` |
917
973
  | `isActive` | `boolean` |
918
- | `onClose` | `(e) => void` |
974
+ | `pinned` | `boolean` (no close button, "Pinned tab" tooltip on hover) |
975
+ | `isEditable` | `boolean` (pencil button in hover overlay, left of close) |
976
+ | `onEdit` | `(e) => void` (edit-button click, used with `isEditable`) |
977
+ | `onClose` | `(e) => void` (close `×` appears on hover only; ignored when `pinned`) |
919
978
  | `onRename` | `(newLabel: string) => void` (enables dbl-click rename) |
920
979
 
921
980
  ---
@@ -1357,7 +1416,7 @@ import { ProductLogo } from "@l3mpire/ui";
1357
1416
 
1358
1417
  ### BulkAction
1359
1418
 
1360
- Bottom-anchored toolbar that appears when one or more list items are selected. The selection count sits on the **left**, a fully composable **responsive** actions slot in the middle, and a ghost-brand icon-only **close button on the right** (uses `clearLabel` as its `aria-label`). Actions that don't fit collapse automatically into a "more" menu.
1419
+ Bottom-anchored toolbar that appears when one or more list items are selected. The selection count sits on the **left**, a fully composable **responsive** actions slot in the middle, and a ghost-brand icon-only **close button on the right** (`clearLabel` shows as a hover tooltip + `aria-label`). Actions that don't fit collapse automatically into a "more" menu.
1361
1420
 
1362
1421
  ```tsx
1363
1422
  import { BulkAction, type BulkActionItem } from "@l3mpire/ui";
@@ -1383,9 +1442,10 @@ const actions: BulkActionItem[] = [
1383
1442
  | `count` | Number of selected items (rendered as `{count} {countLabel}`) |
1384
1443
  | `onClear` | Called when the user clicks the right-side close button |
1385
1444
  | `countLabel` | Defaults to `"selected"` — override for i18n |
1386
- | `clearLabel` | Defaults to `"Clear"` — used as the close button's `aria-label` |
1445
+ | `clearLabel` | Defaults to `"Clear"` — shown as the close button's hover tooltip + `aria-label` |
1387
1446
  | `actions` | `BulkActionItem[]` — items collapse into a "more" menu when they overflow |
1388
- | `sticky` | `true` (default) `position: sticky; bottom: 0`. Set to `false` when placing it manually inside a flex column above a footer (e.g. inside a SidePanel) |
1447
+ | `position` | `"sticky"` (default), `"static"`, `"floating-top"` — `sticky` pins to the bottom of the scroll container; `static` flows inline; `floating-top` floats a centred pill above a `position: relative` parent (e.g. above a pagination footer) |
1448
+ | `sticky` | **Deprecated** — use `position`. `true` maps to `"sticky"`, `false` to `"static"`; ignored when `position` is set |
1389
1449
 
1390
1450
  **`BulkActionItem` shape:**
1391
1451
 
@@ -1472,6 +1532,8 @@ const columns: ColumnDef<Person>[] = [
1472
1532
  | `lockedColumnIds` | inferred | Column IDs the manage-table modal hides (defaults to `enableHiding: false` columns plus `select`) |
1473
1533
  | `tableSettingsTitle` | `"Manage table"` | Override the manage-table modal title |
1474
1534
  | `bordered` | `false` | Border-top on header (full-width layouts) |
1535
+ | `scrollContainer` | `"self"` | Set to `"parent"` to drop the internal `overflow-auto` wrapper. Required when sticky column pinning has to compose with a page-level scroll. |
1536
+ | `columnManagerSlot` | — | Slot mounted as a `<th>` at the right end of the header row. Use it to host a custom column manager (see `ColumnManagerPopover`). |
1475
1537
  | `emptyState` | — | `ReactNode` shown when `data` is empty |
1476
1538
  | `sorting` | — | Controlled sorting state |
1477
1539
  | `onSortingChange` | — | Controlled sorting callback |
@@ -1548,8 +1610,10 @@ import {
1548
1610
  EmailCell, LinkCell, ButtonCell, EditableCell, RowActions,
1549
1611
  } from "@l3mpire/ui";
1550
1612
 
1613
+ // Row cells are a fixed 40px tall; headers 32px; both with 12px horizontal padding.
1551
1614
  // In column definitions:
1552
- { cell: ({ row }) => <AvatarCell name={row.original.name} src={row.original.avatar} /> }
1615
+ // AvatarCell: shape="rounded" (default, people) | "squared" (companies)
1616
+ { cell: ({ row }) => <AvatarCell name={row.original.name} src={row.original.avatar} shape="squared" /> }
1553
1617
  { cell: ({ row }) => <StatusCell status="active" /> }
1554
1618
  { cell: ({ row }) => <NumberCell value={row.original.amount} /> }
1555
1619
  { cell: ({ row }) => <DateCell value={row.original.date} /> }
@@ -1560,6 +1624,406 @@ import {
1560
1624
 
1561
1625
  ---
1562
1626
 
1627
+ ### EntityCell
1628
+
1629
+ ```tsx
1630
+ import { EntityCell, Avatar } from "@l3mpire/ui";
1631
+ import { faArrowUpRightFromSquareOutline } from "@l3mpire/icons";
1632
+
1633
+ <EntityCell avatar={<Avatar size="sm" initials="AC" />} name="Acme Corp" subtitle="acme.com" />
1634
+ <EntityCell
1635
+ avatar={<Avatar size="sm" src={logo} />}
1636
+ name="Acme Corp"
1637
+ onNameClick={() => openRecord(id)}
1638
+ hoverCard={<CompanyPreview id={id} />}
1639
+ quickActions={[{ icon: faArrowUpRightFromSquareOutline, label: "Open", onClick: () => {} }]}
1640
+ />
1641
+ ```
1642
+
1643
+ | Prop | Values |
1644
+ |---|---|
1645
+ | `avatar` | `React.ReactNode` (required) — pass an `<Avatar />` or any logo/icon |
1646
+ | `name` | `string` (required) |
1647
+ | `subtitle` | `string` — optional second line |
1648
+ | `badge` | `React.ReactNode` — marker after the name (sync/status indicator) |
1649
+ | `hoverCard` | `React.ReactNode` — opens in a `<HoverCard />` when the name is hovered |
1650
+ | `quickActions` | `RowQuickAction[]` — hover-revealed actions on the right edge |
1651
+ | `onNameClick` | `(e) => void` — makes the name a clickable link; stops propagation |
1652
+
1653
+ ---
1654
+
1655
+ ### HoverCard
1656
+
1657
+ ```tsx
1658
+ import { HoverCard, HoverCardTrigger, HoverCardContent } from "@l3mpire/ui";
1659
+
1660
+ <HoverCard openDelay={350} closeDelay={120}>
1661
+ <HoverCardTrigger asChild><span>Acme Corp</span></HoverCardTrigger>
1662
+ <HoverCardContent side="bottom" align="start">
1663
+ <CompanyPreview id={id} />
1664
+ </HoverCardContent>
1665
+ </HoverCard>
1666
+ ```
1667
+
1668
+ Compound component (`HoverCard` / `HoverCardTrigger` / `HoverCardContent`). Hover-intent popover with open/close delays; stays open while the pointer is over the content.
1669
+
1670
+ | Prop (HoverCard) | Values |
1671
+ |---|---|
1672
+ | `openDelay` | `number` ms before opening (default `350`) |
1673
+ | `closeDelay` | `number` ms before closing (default `120`) |
1674
+ | `open` / `onOpenChange` | controlled open state |
1675
+
1676
+ | Prop (HoverCardTrigger) | Values |
1677
+ |---|---|
1678
+ | `asChild` | `boolean` — merge props onto the child |
1679
+
1680
+ `HoverCardContent` accepts Radix Popover Content props (`side`, `align`, `sideOffset` — defaults `"bottom"` / `"start"` / `8`).
1681
+
1682
+ ---
1683
+
1684
+ ### RowQuickActions
1685
+
1686
+ ```tsx
1687
+ import { RowQuickActions } from "@l3mpire/ui";
1688
+ import { faPenOutline, faTrashOutline } from "@l3mpire/icons";
1689
+
1690
+ <RowQuickActions
1691
+ group="co"
1692
+ actions={[
1693
+ { icon: faPenOutline, label: "Edit", onClick: () => {} },
1694
+ { icon: faTrashOutline, label: "Delete", onClick: () => {} },
1695
+ ]}
1696
+ />
1697
+ ```
1698
+
1699
+ A row of icon-only buttons that fade in on parent-row hover. Each button stops propagation and carries a tooltip from its `label`.
1700
+
1701
+ | Prop | Values |
1702
+ |---|---|
1703
+ | `actions` | `RowQuickAction[]` (required) — `{ icon, label, onClick, href? }` |
1704
+ | `group` | `string` — named hover group to listen to (e.g. `"co"`); defaults to the unnamed `group` |
1705
+ | `size` | `"sm"` (default), `"md"` |
1706
+
1707
+ ---
1708
+
1709
+ ### StatusPickerCell
1710
+
1711
+ ```tsx
1712
+ import { StatusPickerCell, type StatusOption } from "@l3mpire/ui";
1713
+
1714
+ const options: StatusOption[] = [
1715
+ { id: "new", label: "New", tone: "sky" },
1716
+ { id: "won", label: "Won", type: "success" },
1717
+ ];
1718
+
1719
+ <StatusPickerCell
1720
+ current={options[0]}
1721
+ options={options}
1722
+ onChange={(id) => setStatus(id)}
1723
+ onAddOption={(label) => addStatus(label)}
1724
+ onSync={() => syncCrm()}
1725
+ />
1726
+ ```
1727
+
1728
+ Badge-pill cell that opens a popover to pick, rename, add, or CRM-sync a status. Each `StatusOption` carries optional `variant` / `type` / `tone` to style its `<Badge />`.
1729
+
1730
+ | Prop | Values |
1731
+ |---|---|
1732
+ | `current` | `StatusOption \| null` (required) |
1733
+ | `options` | `StatusOption[]` (required) |
1734
+ | `onChange` | `(id: string) => void` (required) |
1735
+ | `onAddOption` | `(label: string) => void` — adds an "Add status" row |
1736
+ | `onRenameOption` | `(id, label) => void` — adds per-row rename on hover |
1737
+ | `onSync` | `() => void` — adds a "Sync with CRM" row |
1738
+ | `triggerStyle` | `"cell"` (default), `"compact"` |
1739
+ | `placeholder` | `string` (default `"Set status"`) |
1740
+
1741
+ ---
1742
+
1743
+ ### ScoreBadge
1744
+
1745
+ ```tsx
1746
+ import { ScoreBadge } from "@l3mpire/ui";
1747
+
1748
+ <ScoreBadge score={3} matched={2} total={5} />
1749
+ <ScoreBadge score={-1} matched={1} total={4} size="md" />
1750
+ ```
1751
+
1752
+ Renders a score-rule result: a colored Badge (positive → success, negative → critical, zero → neutral) plus a `matched/total` counter.
1753
+
1754
+ | Prop | Values |
1755
+ |---|---|
1756
+ | `score` | `number` (required) — signed total |
1757
+ | `matched` | `number` (required) |
1758
+ | `total` | `number` (required) |
1759
+ | `size` | Badge size — `"sm"` (default), `"md"`, `"lg"` |
1760
+
1761
+ ---
1762
+
1763
+ ### ColumnTypeBadge
1764
+
1765
+ ```tsx
1766
+ import { ColumnTypeBadge } from "@l3mpire/ui";
1767
+
1768
+ <ColumnTypeBadge kind="db" />
1769
+ <ColumnTypeBadge kind="field" />
1770
+ <ColumnTypeBadge kind="score" />
1771
+ <ColumnTypeBadge kind="ai" />
1772
+ ```
1773
+
1774
+ Small Badge labelling a column's kind: `DB` (neutral), `FIELD` (warning), `SCORE` (success), `AI` (primary).
1775
+
1776
+ | Prop | Values |
1777
+ |---|---|
1778
+ | `kind` | `"db"`, `"field"`, `"score"`, `"ai"` (required) |
1779
+ | `size` | Badge size — `"sm"` (default), `"md"`, `"lg"` |
1780
+
1781
+ ---
1782
+
1783
+ ### ColumnHeaderMenu
1784
+
1785
+ ```tsx
1786
+ import { ColumnHeaderMenu } from "@l3mpire/ui";
1787
+
1788
+ <ColumnHeaderMenu
1789
+ column={column}
1790
+ title="Company"
1791
+ editable={!isBuiltIn}
1792
+ onRename={(name) => renameColumn(name)}
1793
+ onEdit={() => openEditor()}
1794
+ onDuplicate={() => duplicate()}
1795
+ onDelete={() => remove()}
1796
+ >
1797
+ <span>Company</span>
1798
+ </ColumnHeaderMenu>
1799
+ ```
1800
+
1801
+ Dropdown attached to a column header: inline rename, sort, pin, hide, and (for editable columns) duplicate / edit / delete. Built-in columns grey the mutation items and surface `builtinReason` as a tooltip.
1802
+
1803
+ | Prop | Values |
1804
+ |---|---|
1805
+ | `column` | `HeaderColumnApi` (required) — TanStack-compatible subset (sort/pin/hide methods) |
1806
+ | `title` | `string` (required) |
1807
+ | `editable` | `boolean` (required) — false greys rename/duplicate/edit/delete |
1808
+ | `onRename` | `(name: string) => void` |
1809
+ | `onEdit` / `onDuplicate` / `onDelete` | `() => void` |
1810
+ | `builtinReason` | `string` — tooltip on greyed items (default `"Built-in columns can't be edited"`) |
1811
+ | `children` | `React.ReactNode` (required) — header content inside the trigger |
1812
+
1813
+ ---
1814
+
1815
+ ### ColumnManagerPopover
1816
+
1817
+ ```tsx
1818
+ import {
1819
+ ColumnManagerPopover,
1820
+ ColumnManagerTrigger,
1821
+ type ColumnManagerItem,
1822
+ } from "@l3mpire/ui";
1823
+
1824
+ const items: ColumnManagerItem[] = [
1825
+ { id: "name", name: "Company", kind: "db", visible: true },
1826
+ { id: "score", name: "Fit score", kind: "score", visible: true },
1827
+ ];
1828
+
1829
+ <ColumnManagerPopover
1830
+ items={items}
1831
+ trigger={<ColumnManagerTrigger />}
1832
+ onToggleVisible={(id, visible) => setVisible(id, visible)}
1833
+ onDelete={(id) => remove(id)}
1834
+ onAdd={(kind) => createColumn(kind)}
1835
+ />
1836
+ ```
1837
+
1838
+ Popover to search, show/hide, delete, and add columns. When `onAdd` is set it renders three "Add column" cards: Field, Score, AI.
1839
+
1840
+ | Prop | Values |
1841
+ |---|---|
1842
+ | `items` | `ColumnManagerItem[]` (required) — `{ id, name, kind, visible, icon? }` |
1843
+ | `onToggleVisible` | `(id, visible) => void` (required) |
1844
+ | `onDelete` | `(id) => void` — only offered for `kind !== "db"` |
1845
+ | `onAdd` | `(kind: AddColumnKind) => void` — `AddColumnKind` is `"field" \| "score" \| "ai"` |
1846
+ | `trigger` | `React.ReactNode` — e.g. `<ColumnManagerTrigger />` |
1847
+ | `open` / `onOpenChange` | controlled open state |
1848
+
1849
+ ---
1850
+
1851
+ ### CreateFieldColumnPanel
1852
+
1853
+ ```tsx
1854
+ import { CreateFieldColumnPanel } from "@l3mpire/ui";
1855
+
1856
+ <CreateFieldColumnPanel
1857
+ open={open}
1858
+ onOpenChange={setOpen}
1859
+ onSave={(column) => addFieldColumn(column)}
1860
+ />
1861
+ ```
1862
+
1863
+ Side panel to create or edit a user-defined Field column (text / number / date / single select). For `select`, options are entered one per line.
1864
+
1865
+ | Prop | Values |
1866
+ |---|---|
1867
+ | `onSave` | `(column: Omit<FieldColumn, "id" \| "createdAt" \| "kind">) => void` (required) |
1868
+ | `open` / `onOpenChange` | controlled open state |
1869
+ | `initial` | `Partial<Pick<FieldColumn, "name" \| "fieldType" \| "options">>` |
1870
+ | `trigger` | `React.ReactNode` — opens the panel in uncontrolled mode |
1871
+ | `title` | `string` (default `"Create field column"`) |
1872
+ | `saveLabel` | `string` (default `"Create column"`) |
1873
+
1874
+ ---
1875
+
1876
+ ### CreateScoreColumnPanel
1877
+
1878
+ ```tsx
1879
+ import {
1880
+ CreateScoreColumnPanel,
1881
+ type ScorableField,
1882
+ type ScorePreviewRow,
1883
+ } from "@l3mpire/ui";
1884
+
1885
+ <CreateScoreColumnPanel
1886
+ open={open}
1887
+ onOpenChange={setOpen}
1888
+ fields={scorableFields}
1889
+ preview={firstFiveRows}
1890
+ onSave={(column) => addScoreColumn(column)}
1891
+ />
1892
+ ```
1893
+
1894
+ Side panel to build a weighted Score column: add rules (field + operator + value + weight) and see a live preview of the first rows scored via `computeScore`.
1895
+
1896
+ | Prop | Values |
1897
+ |---|---|
1898
+ | `fields` | `ScorableField[]` (required) — `{ id, name, fieldType, options? }` |
1899
+ | `onSave` | `(column: Pick<ScoreColumn, "name" \| "rules">) => void` (required) |
1900
+ | `preview` | `ScorePreviewRow[]` — rows to score in the preview |
1901
+ | `open` / `onOpenChange` | controlled open state |
1902
+ | `initial` | `Partial<Pick<ScoreColumn, "name" \| "rules">>` |
1903
+ | `trigger` | `React.ReactNode` |
1904
+ | `title` | `string` (default `"Score column"`) |
1905
+ | `saveLabel` | `string` (default `"Save"`) |
1906
+
1907
+ ---
1908
+
1909
+ ### AIBadge
1910
+
1911
+ ```tsx
1912
+ import { AIBadge } from "@l3mpire/ui";
1913
+
1914
+ <AIBadge />
1915
+ <AIBadge size="md">Enriched</AIBadge>
1916
+ <AIBadge iconHidden>AI</AIBadge>
1917
+ ```
1918
+
1919
+ Marker flagging a value or column as AI-driven. Renders a `<Badge variant="light" type="primary" />` with a leading stars glyph. Children default to `"AI"`.
1920
+
1921
+ | Prop | Values |
1922
+ |---|---|
1923
+ | `iconHidden` | `boolean` — hide the leading stars icon |
1924
+ | `size` | Badge size — `"lg"` (default), `"sm"`, `"md"` |
1925
+ | `children` | `React.ReactNode` (default `"AI"`) |
1926
+
1927
+ ---
1928
+
1929
+ ### AICell
1930
+
1931
+ ```tsx
1932
+ import { AICell, type AICellState } from "@l3mpire/ui";
1933
+
1934
+ const state: AICellState = { status: "done", value: "B2B SaaS", citations: ["crunchbase"] };
1935
+
1936
+ <AICell state={state} seed={row.id} />
1937
+ <AICell state={{ status: "running" }} seed={row.id} />
1938
+ <AICell state={{ status: "error", message: "Rate limited" }} />
1939
+ ```
1940
+
1941
+ Renders the four states of an AI-generated cell in one stable shell: `pending` (Queued), `running` (shimmer + spinning verb), `done` (value + optional citation count, tooltip), `error` (red message).
1942
+
1943
+ | Prop | Values |
1944
+ |---|---|
1945
+ | `state` | `AICellState \| undefined` — `{ status: "pending" }` \| `{ status: "running" }` \| `{ status: "done"; value; citations? }` \| `{ status: "error"; message }` |
1946
+ | `seed` | `string` — stable per-cell seed so the running verb is consistent across re-renders |
1947
+
1948
+ Helpers: `AI_RUNNING_VERBS` (verb pool) and `pickRunningVerb(seed)` (stable verb pick).
1949
+
1950
+ ---
1951
+
1952
+ ### AISuggestionDot
1953
+
1954
+ ```tsx
1955
+ import { AISuggestionDot } from "@l3mpire/ui";
1956
+
1957
+ <AISuggestionDot
1958
+ reason="LinkedIn lists a newer title."
1959
+ from="VP Sales"
1960
+ to="Chief Revenue Officer"
1961
+ onApply={() => apply()}
1962
+ onDismiss={() => dismiss()}
1963
+ />
1964
+ ```
1965
+
1966
+ A pulsing dot anchored next to a cell value. Clicking it opens a popover showing the AI's `from → to` suggestion with Apply / Dismiss actions.
1967
+
1968
+ | Prop | Values |
1969
+ |---|---|
1970
+ | `to` | `React.ReactNode` (required) — suggested value |
1971
+ | `onApply` | `() => void` (required) |
1972
+ | `onDismiss` | `() => void` (required) |
1973
+ | `from` | `React.ReactNode` — current value (left of the arrow) |
1974
+ | `reason` | `string` — short explanation in the popover |
1975
+ | `side` | `"top"`, `"right"`, `"bottom"` (default), `"left"` |
1976
+ | `title` / `applyLabel` / `dismissLabel` | localized labels (defaults `"Suggested change"` / `"Apply"` / `"Dismiss"`) |
1977
+
1978
+ ---
1979
+
1980
+ ### AIColumnHeaderBar
1981
+
1982
+ ```tsx
1983
+ import { AIColumnHeaderBar } from "@l3mpire/ui";
1984
+
1985
+ <AIColumnHeaderBar label="Tech stack" onRun={() => runOnAllRows()} />
1986
+ ```
1987
+
1988
+ Header bar for an AI column. The Play ("run on all rows") button appears only on header hover.
1989
+
1990
+ | Prop | Values |
1991
+ |---|---|
1992
+ | `label` | `string` (required) |
1993
+ | `onRun` | `() => void` — shows the hover-revealed Play button |
1994
+ | `runLabel` | `string` — tooltip/aria-label (default `"Run on all rows"`) |
1995
+
1996
+ ---
1997
+
1998
+ ### AIColumnSidePanel
1999
+
2000
+ ```tsx
2001
+ import { AIColumnSidePanel, type AIColumnConfig } from "@l3mpire/ui";
2002
+
2003
+ <AIColumnSidePanel
2004
+ open={open}
2005
+ onOpenChange={setOpen}
2006
+ onSave={(config) => createAIColumn(config)}
2007
+ extrasSlot={(extras, setExtras) => <ModelPicker value={extras} onChange={setExtras} />}
2008
+ />
2009
+ ```
2010
+
2011
+ Side panel scaffold to create or edit an AI column: name, prompt, and output format. Product-specific controls (model, sources, timeframe) plug in via `extrasSlot`.
2012
+
2013
+ | Prop | Values |
2014
+ |---|---|
2015
+ | `onSave` | `(config: AIColumnConfig) => void` (required) — `{ name, prompt, outputFormat, extras? }` |
2016
+ | `open` / `onOpenChange` | controlled open state |
2017
+ | `initialConfig` | `Partial<AIColumnConfig>` |
2018
+ | `extrasSlot` | `(extras, setExtras) => React.ReactNode` — render slot for extra controls |
2019
+ | `trigger` | `React.ReactNode` — uncontrolled open |
2020
+ | `title` | `string` (default `"AI column"`) |
2021
+ | `saveLabel` | `string` (default `"Save"`) |
2022
+
2023
+ `AIColumnOutputFormat`: `"text" \| "number" \| "boolean" \| "select" \| "date"`.
2024
+
2025
+ ---
2026
+
1563
2027
  ### EmptyState
1564
2028
 
1565
2029
  ```tsx