@lovett/ui 0.0.1
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/README.md +28 -0
- package/package.json +53 -0
- package/src/accordion.tsx +153 -0
- package/src/atoms.tsx +197 -0
- package/src/badge.tsx +91 -0
- package/src/brand-logo-tile.tsx +200 -0
- package/src/bulk-action-bar.tsx +104 -0
- package/src/button-group.tsx +46 -0
- package/src/button.tsx +93 -0
- package/src/calculator-shell-v2.tsx +380 -0
- package/src/calculator-shell.tsx +310 -0
- package/src/card.tsx +369 -0
- package/src/checkbox.tsx +114 -0
- package/src/chip-nav.tsx +347 -0
- package/src/choropleth.tsx +62 -0
- package/src/code-block.tsx +235 -0
- package/src/completion-ring.tsx +119 -0
- package/src/copy-field.tsx +169 -0
- package/src/data-grid/column-options-menu.tsx +127 -0
- package/src/data-grid/data-grid.tsx +391 -0
- package/src/data-grid/drawer-panel.tsx +146 -0
- package/src/data-grid/index.ts +29 -0
- package/src/data-grid/sortable-parts.tsx +215 -0
- package/src/data-grid/table-body.tsx +238 -0
- package/src/data-grid/table-elements.tsx +135 -0
- package/src/data-grid/table-header.tsx +95 -0
- package/src/data-grid/table-overlays.tsx +78 -0
- package/src/data-grid/table-parts.tsx +15 -0
- package/src/data-grid/table-summary-footer.tsx +101 -0
- package/src/data-grid/table-view.tsx +176 -0
- package/src/data-grid/types.ts +141 -0
- package/src/data-grid/use-grid-columns.ts +311 -0
- package/src/data-grid/use-grid-editing.ts +154 -0
- package/src/device-frame.tsx +274 -0
- package/src/dropdown-button.tsx +221 -0
- package/src/dropdown-menu.tsx +450 -0
- package/src/empty-state.tsx +85 -0
- package/src/file-upload/file-drop-zone.tsx +156 -0
- package/src/file-upload/file-preview-grid.tsx +39 -0
- package/src/file-upload/file-preview-item.tsx +175 -0
- package/src/file-upload/file-thumbnail.tsx +98 -0
- package/src/file-upload/file-upload-button.tsx +95 -0
- package/src/file-upload/index.ts +13 -0
- package/src/file-upload/use-file-upload.ts +371 -0
- package/src/filter-bar.tsx +125 -0
- package/src/filter-dropdown.tsx +289 -0
- package/src/floating-drawer.tsx +420 -0
- package/src/folder-card.tsx +188 -0
- package/src/folder-tree-picker.tsx +511 -0
- package/src/format.ts +117 -0
- package/src/frame-stack.tsx +73 -0
- package/src/hero-form-card.tsx +471 -0
- package/src/index.ts +372 -0
- package/src/input.tsx +130 -0
- package/src/kbd.tsx +41 -0
- package/src/lib/layout.ts +22 -0
- package/src/lib/utils.ts +6 -0
- package/src/markdown.tsx +214 -0
- package/src/menu-button.tsx +142 -0
- package/src/meta-previews/CreativeMedia.tsx +119 -0
- package/src/meta-previews/InstagramExplorePreview.tsx +340 -0
- package/src/meta-previews/InstagramFeedCarousel.tsx +245 -0
- package/src/meta-previews/InstagramFeedPreview.tsx +259 -0
- package/src/meta-previews/MetaFeedCarousel.tsx +228 -0
- package/src/meta-previews/MetaFeedPreview.tsx +294 -0
- package/src/meta-previews/MetaInstreamPreview.tsx +135 -0
- package/src/meta-previews/MetaMessengerPreview.tsx +795 -0
- package/src/meta-previews/MetaReelPreview.tsx +750 -0
- package/src/meta-previews/MetaRightColumnPreview.tsx +245 -0
- package/src/meta-previews/MetaSearchPreview.tsx +113 -0
- package/src/meta-previews/MetaStoryPreview.tsx +1132 -0
- package/src/meta-previews/ProfileAvatar.tsx +79 -0
- package/src/meta-previews/defaults.ts +27 -0
- package/src/meta-previews/index.ts +37 -0
- package/src/meta-previews/types.ts +219 -0
- package/src/metric-card.tsx +117 -0
- package/src/modal.tsx +131 -0
- package/src/option-tile.tsx +252 -0
- package/src/page-hero.tsx +78 -0
- package/src/page-shell.tsx +440 -0
- package/src/pagination.tsx +86 -0
- package/src/pill-button.tsx +103 -0
- package/src/profile-section.tsx +389 -0
- package/src/progress-bar.tsx +84 -0
- package/src/radio.tsx +172 -0
- package/src/range-slider.tsx +161 -0
- package/src/resizable.tsx +282 -0
- package/src/search-bar.tsx +81 -0
- package/src/segmented-pill.tsx +143 -0
- package/src/shell.tsx +464 -0
- package/src/slider.tsx +107 -0
- package/src/sortable-table.tsx +198 -0
- package/src/sortable.tsx +137 -0
- package/src/stat-row.tsx +132 -0
- package/src/stats-grid.tsx +38 -0
- package/src/step-loader.tsx +141 -0
- package/src/styles.css +562 -0
- package/src/tabs.tsx +143 -0
- package/src/tag-chip-input.tsx +394 -0
- package/src/textarea.tsx +39 -0
- package/src/toast.tsx +59 -0
- package/src/tokens.css +579 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
// @lovett/ui — public exports.
|
|
2
|
+
//
|
|
3
|
+
// The set below is the contract from ADR-008 Decision 3 plus the toast
|
|
4
|
+
// surface (Decision 5). Nothing else lands here without an ADR
|
|
5
|
+
// promotion. Apps consume via `import { Button, toast } from '@lovett/ui'`.
|
|
6
|
+
//
|
|
7
|
+
// CSS surface (apps import these alongside the JS):
|
|
8
|
+
// `@lovett/ui/tokens.css` — the token vocabulary
|
|
9
|
+
// `@lovett/ui/styles.css` — component CSS the primitives render through
|
|
10
|
+
|
|
11
|
+
export { default as Card, IconTile, TagRow } from './card'
|
|
12
|
+
export { Button, type ButtonProps, type ButtonVariant, type ButtonSize, type ButtonShape } from './button'
|
|
13
|
+
export { ButtonGroup, type ButtonGroupProps } from './button-group'
|
|
14
|
+
export { Input, type InputProps, type InputSize } from './input'
|
|
15
|
+
export { Textarea, type TextareaProps } from './textarea'
|
|
16
|
+
export { default as Modal } from './modal'
|
|
17
|
+
export {
|
|
18
|
+
FloatingDrawer,
|
|
19
|
+
DetailSection,
|
|
20
|
+
type FloatingDrawerProps,
|
|
21
|
+
} from './floating-drawer'
|
|
22
|
+
export { default as BrandLogoTile, type BrandLogoTileProps, type BrandLogoTileSize } from './brand-logo-tile'
|
|
23
|
+
export { default as ProfileSection, type ProfileSectionProps } from './profile-section'
|
|
24
|
+
export { default as ChipNav, type ChipNavItem } from './chip-nav'
|
|
25
|
+
export { default as CompletionRing, type CompletionRingProps } from './completion-ring'
|
|
26
|
+
export { default as Choropleth, type ChoroplethProps } from './choropleth'
|
|
27
|
+
export {
|
|
28
|
+
default as PageShell,
|
|
29
|
+
AppScroll,
|
|
30
|
+
PageHeaderHost,
|
|
31
|
+
PageHeaderSlotProvider,
|
|
32
|
+
type Crumb,
|
|
33
|
+
} from './page-shell'
|
|
34
|
+
export { Toaster, toast } from './toast'
|
|
35
|
+
|
|
36
|
+
// Brand-profile atoms — promoted in Phase 2b (ADR-010 Decision 3). Section
|
|
37
|
+
// files across the brand-profile lens (and later sibling lenses) import
|
|
38
|
+
// these from `@lovett/ui` rather than a local `./atoms`.
|
|
39
|
+
export {
|
|
40
|
+
SectionLabel,
|
|
41
|
+
IdentityLabel,
|
|
42
|
+
IdentityValue,
|
|
43
|
+
TextInput,
|
|
44
|
+
TextareaInput,
|
|
45
|
+
EmptyPlaceholder,
|
|
46
|
+
ListItem,
|
|
47
|
+
} from './atoms'
|
|
48
|
+
|
|
49
|
+
// Sortable primitives — promoted in Phase 2b (ADR-010 Decision 5). Wraps
|
|
50
|
+
// @dnd-kit/core + sortable + utilities (CLAUDE.md §1 amendment in same phase).
|
|
51
|
+
export { SortableList, SortableItem, DragHandle } from './sortable'
|
|
52
|
+
|
|
53
|
+
// EmptyState — promoted post-phase-15 (ADR-019). 4-consumer trigger:
|
|
54
|
+
// Products / Personas / Competitors / Assets lenses all forked their own
|
|
55
|
+
// full-area empty-state pattern. Canonical shape derived from the Phase
|
|
56
|
+
// 15 Assets implementation. Distinct from EmptyPlaceholder (atoms.tsx),
|
|
57
|
+
// which is the in-card dashed-border variant for brand-profile sections.
|
|
58
|
+
export { EmptyState, type EmptyStateProps } from './empty-state'
|
|
59
|
+
|
|
60
|
+
// Form primitives promoted post-utility-tools-batch-1 (ADR-029). The
|
|
61
|
+
// 4 lens-local tinted-card-with-input forks (Convert Case, Text
|
|
62
|
+
// Formatter, Replace/Dedupe modals) consolidated into OptionTile.
|
|
63
|
+
// Radio + RadioGroup for stand-alone radios outside the tile context.
|
|
64
|
+
export { Radio, RadioGroup, type RadioProps, type RadioGroupProps } from './radio'
|
|
65
|
+
export {
|
|
66
|
+
OptionTile,
|
|
67
|
+
OptionTileGroup,
|
|
68
|
+
type OptionTileProps,
|
|
69
|
+
type OptionTileGroupProps,
|
|
70
|
+
} from './option-tile'
|
|
71
|
+
|
|
72
|
+
// Phase 3a/3b/3c/8/12 batch promotions (ADRs 020-024) — shipped via
|
|
73
|
+
// pre-batch coordination commit per ADR-023 D20. Each primitive has
|
|
74
|
+
// 2+ committed consumers across the batch.
|
|
75
|
+
export { MetricCard, type MetricCardProps, type MetricCardTone } from './metric-card'
|
|
76
|
+
export { ProgressBar, type ProgressBarProps } from './progress-bar'
|
|
77
|
+
export { StepLoader, type StepLoaderProps, type StepLoaderStep } from './step-loader'
|
|
78
|
+
export { Badge, type BadgeProps, type BadgeTone } from './badge'
|
|
79
|
+
export { StatsGrid, type StatsGridProps } from './stats-grid'
|
|
80
|
+
export {
|
|
81
|
+
SortableTable,
|
|
82
|
+
type SortableTableProps,
|
|
83
|
+
type Column as SortableTableColumn,
|
|
84
|
+
} from './sortable-table'
|
|
85
|
+
export { Pagination, type PaginationProps } from './pagination'
|
|
86
|
+
export { Tabs, type TabsProps, type Tab } from './tabs'
|
|
87
|
+
export { PillButton, type PillButtonProps } from './pill-button'
|
|
88
|
+
|
|
89
|
+
// DataGrid + supporting primitives — intaked from gray-ui-csm under
|
|
90
|
+
// MIT per ADR-025 (first external-component intake under
|
|
91
|
+
// SPEC-DEV-FOR-WORKSPACE §8). DropdownMenu trigger inherits the
|
|
92
|
+
// secondary Button visual per ADR-026 D4 (DQ-4 LIFT).
|
|
93
|
+
export { Checkbox, type CheckboxProps } from './checkbox'
|
|
94
|
+
export {
|
|
95
|
+
DropdownMenu,
|
|
96
|
+
DropdownMenuTrigger,
|
|
97
|
+
DropdownMenuContent,
|
|
98
|
+
DropdownMenuItem,
|
|
99
|
+
DropdownMenuCheckboxItem,
|
|
100
|
+
DropdownMenuSeparator,
|
|
101
|
+
DropdownMenuLabel,
|
|
102
|
+
DropdownMenuGroup,
|
|
103
|
+
type DropdownMenuProps,
|
|
104
|
+
type DropdownMenuTriggerProps,
|
|
105
|
+
type DropdownMenuContentProps,
|
|
106
|
+
type DropdownMenuItemProps,
|
|
107
|
+
type DropdownMenuCheckboxItemProps,
|
|
108
|
+
type DropdownMenuLabelProps,
|
|
109
|
+
} from './dropdown-menu'
|
|
110
|
+
export {
|
|
111
|
+
DataGrid,
|
|
112
|
+
DataGridColumnOptionsMenu,
|
|
113
|
+
DataGridDragOverlay,
|
|
114
|
+
DataGridDropIndicator,
|
|
115
|
+
type DataGridColumn,
|
|
116
|
+
type DataGridDrawerPanelProps,
|
|
117
|
+
type DataGridProps,
|
|
118
|
+
type DataGridRowBase,
|
|
119
|
+
type DataGridSortState,
|
|
120
|
+
type DataGridToolbarRenderProps,
|
|
121
|
+
type DataGridIcon,
|
|
122
|
+
type EditingCell,
|
|
123
|
+
} from './data-grid'
|
|
124
|
+
|
|
125
|
+
// DropdownButton — Button-styled trigger + rich popover menu. Composed
|
|
126
|
+
// over <DropdownMenu> primitives. First consumer: Keywords dashboard's
|
|
127
|
+
// "New Keyword Plan" CTA (Set/Group picker). Reusable across other
|
|
128
|
+
// lenses with a multi-create CTA pattern.
|
|
129
|
+
export {
|
|
130
|
+
DropdownButton,
|
|
131
|
+
type DropdownButtonProps,
|
|
132
|
+
type DropdownButtonItem,
|
|
133
|
+
} from './dropdown-button'
|
|
134
|
+
|
|
135
|
+
// Accordion — controlled collapsible-section group. Compound:
|
|
136
|
+
// Accordion + Accordion.Section. First consumer: SEM Spec "New
|
|
137
|
+
// campaign spec" form (ADR-033).
|
|
138
|
+
export {
|
|
139
|
+
Accordion,
|
|
140
|
+
type AccordionProps,
|
|
141
|
+
type AccordionSectionProps,
|
|
142
|
+
} from './accordion'
|
|
143
|
+
|
|
144
|
+
// MenuButton — toolbar-style dropdown for menu-bar affordances (File /
|
|
145
|
+
// Edit). Transparent trigger + compact action rows. Composed over
|
|
146
|
+
// <DropdownMenu>. First consumer: utility-tool workbench header
|
|
147
|
+
// (ADR-031).
|
|
148
|
+
export {
|
|
149
|
+
MenuButton,
|
|
150
|
+
type MenuButtonProps,
|
|
151
|
+
type MenuButtonItem,
|
|
152
|
+
type MenuButtonEntry,
|
|
153
|
+
type MenuButtonSeparator,
|
|
154
|
+
} from './menu-button'
|
|
155
|
+
|
|
156
|
+
// CopyField — input/output field with a copy button pinned top-right.
|
|
157
|
+
// Owns the copy interaction (clipboard + ✓ feedback + toast). First
|
|
158
|
+
// consumer: utility-tool workbench body (ADR-031). Reusable anywhere a
|
|
159
|
+
// field's contents need a one-click copy.
|
|
160
|
+
export { CopyField, type CopyFieldProps } from './copy-field'
|
|
161
|
+
|
|
162
|
+
// TagChipInput — chips + autocomplete + type-to-create. First consumers:
|
|
163
|
+
// Keywords NewSetPage + NewGroupPage. Reusable anywhere a multi-tag
|
|
164
|
+
// input is needed.
|
|
165
|
+
export { TagChipInput, type TagChipInputProps } from './tag-chip-input'
|
|
166
|
+
|
|
167
|
+
// FolderTreePicker — dropdown picker over a nestable folder tree with
|
|
168
|
+
// optional inline-create. Generic shape (`{ id, name, parentId }`) so
|
|
169
|
+
// any folder-like data structure can use it. First consumers: Keywords
|
|
170
|
+
// NewSetPage + NewGroupPage.
|
|
171
|
+
export {
|
|
172
|
+
FolderTreePicker,
|
|
173
|
+
type FolderTreeNode,
|
|
174
|
+
type FolderTreePickerProps,
|
|
175
|
+
} from './folder-tree-picker'
|
|
176
|
+
|
|
177
|
+
// HeroFormCard — centered hero-layout form scaffold (eyebrow + title +
|
|
178
|
+
// subtitle + body card + footer). Compound sub-components: Section,
|
|
179
|
+
// Field, FieldRow, Banner, Advanced, Footer. Icon-agnostic — consumers
|
|
180
|
+
// pass any Lucide / custom node as `icon`. First consumers: Keywords
|
|
181
|
+
// NewSetPage + NewGroupPage. Reusable for SEM Spec input + other
|
|
182
|
+
// centered create / config flows.
|
|
183
|
+
export {
|
|
184
|
+
HeroFormCard,
|
|
185
|
+
type HeroFormCardProps,
|
|
186
|
+
type HeroFormCardSectionProps,
|
|
187
|
+
type HeroFormCardFieldProps,
|
|
188
|
+
type HeroFormCardBannerProps,
|
|
189
|
+
type HeroFormCardAdvancedProps,
|
|
190
|
+
} from './hero-form-card'
|
|
191
|
+
|
|
192
|
+
// RangeSlider — dual-thumb numeric range selector built on two
|
|
193
|
+
// stacked native range inputs. First consumer: Keywords filter bar
|
|
194
|
+
// (Volume + Avg CPC range filters).
|
|
195
|
+
export { RangeSlider, type RangeSliderProps } from './range-slider'
|
|
196
|
+
|
|
197
|
+
// SegmentedPill — segmented control with outlined-accent active state.
|
|
198
|
+
// Generic items shape so any small mutually-exclusive choice can use
|
|
199
|
+
// it. First consumer: Keywords lens research-mode picker.
|
|
200
|
+
export {
|
|
201
|
+
SegmentedPill,
|
|
202
|
+
type SegmentedPillProps,
|
|
203
|
+
type SegmentedPillItem,
|
|
204
|
+
} from './segmented-pill'
|
|
205
|
+
|
|
206
|
+
// Shell — multi-tray framed-card pattern. Outer slate frame holds one
|
|
207
|
+
// or more white inner trays. Each tray has a rich header (title +
|
|
208
|
+
// meta + optional accent numeral), body (form/grid/flush spacing
|
|
209
|
+
// variants), and optional recessed footer strip (secondary controls
|
|
210
|
+
// left, primary action right). First consumers: 5 utility tools
|
|
211
|
+
// (Dedupe / Combine / Replace / ConvertCase / TextFormatter) and the
|
|
212
|
+
// New Set / New Group pages.
|
|
213
|
+
export {
|
|
214
|
+
Shell,
|
|
215
|
+
ShellStat,
|
|
216
|
+
type ShellProps,
|
|
217
|
+
type ShellTrayProps,
|
|
218
|
+
type ShellTrayHeaderProps,
|
|
219
|
+
type ShellTrayBodyProps,
|
|
220
|
+
type ShellTrayFooterProps,
|
|
221
|
+
type ShellSubCardProps,
|
|
222
|
+
type ShellStatProps,
|
|
223
|
+
type ShellStatTone,
|
|
224
|
+
} from './shell'
|
|
225
|
+
|
|
226
|
+
// FolderCard — drive-style folder tile (colour-keyed swatch + name +
|
|
227
|
+
// count + hover checkbox / menu slot). Promoted in ADR-035 (Assets
|
|
228
|
+
// lens redesign); a card surface, so it owns the internal surface
|
|
229
|
+
// tokens as a primitive. Reusable by the v1.1 Swipes / Collections
|
|
230
|
+
// lenses (PRD §5.6).
|
|
231
|
+
export {
|
|
232
|
+
FolderCard,
|
|
233
|
+
FOLDER_COLOR_KEYS,
|
|
234
|
+
folderColorVar,
|
|
235
|
+
type FolderCardProps,
|
|
236
|
+
type FolderColorKey,
|
|
237
|
+
} from './folder-card'
|
|
238
|
+
|
|
239
|
+
// BulkActionBar — floating bottom-centred selection action bar.
|
|
240
|
+
// Promoted in ADR-035 — two consumers (Assets page + Manage Folders
|
|
241
|
+
// page). Owns the shell; callers pass their own action buttons.
|
|
242
|
+
export { BulkActionBar, type BulkActionBarProps } from './bulk-action-bar'
|
|
243
|
+
|
|
244
|
+
// File-upload subtree — promoted from brand-ai-extension (ADR-036 D20).
|
|
245
|
+
// D14 fix: uploadUrl + authHeaders are caller-supplied; the primitive never
|
|
246
|
+
// reads import.meta.env or hardcodes a URL.
|
|
247
|
+
export {
|
|
248
|
+
FileUploadButton,
|
|
249
|
+
FileDropZone,
|
|
250
|
+
FilePreviewGrid,
|
|
251
|
+
FilePreviewItem,
|
|
252
|
+
FileThumbnail,
|
|
253
|
+
useFileUpload,
|
|
254
|
+
type PendingFile,
|
|
255
|
+
type UploadResult,
|
|
256
|
+
type UseFileUploadOptions,
|
|
257
|
+
} from './file-upload/index'
|
|
258
|
+
|
|
259
|
+
// CodeBlock + MarkdownCode — syntax-highlighted code block and react-markdown
|
|
260
|
+
// code element adapter. Promoted from brand-ai-extension (ADR-036 D20).
|
|
261
|
+
export { CodeBlock, MarkdownCode, type CodeBlockProps } from './code-block'
|
|
262
|
+
|
|
263
|
+
// MarkdownRenderer — full markdown surface with GFM support (headings, lists,
|
|
264
|
+
// code via CodeBlock, tables, task lists, links). Promoted from
|
|
265
|
+
// brand-ai-extension chat-message.tsx (ADR-036 D20, BUILD_FRESH extraction).
|
|
266
|
+
export { MarkdownRenderer, type MarkdownRendererProps } from './markdown'
|
|
267
|
+
|
|
268
|
+
// ResizableHandle — pointer-driven splitter with WAI-ARIA splitter a11y.
|
|
269
|
+
// Promoted from the Generate lens's local mouse-event resize per
|
|
270
|
+
// ADR-049 D8/D9 (ADR-008 D3 2+ consumer gate: Generate lens + sidebar
|
|
271
|
+
// pinned-panel resize). ResizablePane is an optional thin <aside> wrapper.
|
|
272
|
+
export {
|
|
273
|
+
ResizableHandle,
|
|
274
|
+
ResizablePane,
|
|
275
|
+
type ResizableHandleProps,
|
|
276
|
+
type ResizablePaneProps,
|
|
277
|
+
} from './resizable'
|
|
278
|
+
|
|
279
|
+
// Kbd — semantic keyboard-input chip. Promoted post-ADR-050 D11 — 3+
|
|
280
|
+
// committed consumers (Chat title chip, palette body + footer, sidebar
|
|
281
|
+
// switcher-popout Esc hint). Renders <kbd> with sm/md size variants.
|
|
282
|
+
export { Kbd, type KbdProps } from './kbd'
|
|
283
|
+
|
|
284
|
+
// FrameStack — slate outer-frame wrapper for the shell/tray pattern.
|
|
285
|
+
// Wraps 2+ Card / SectionCard children as ONE visual group with 10 px
|
|
286
|
+
// padding + 10 px gap. Dedicated single-purpose primitive (avoids the
|
|
287
|
+
// "Shell" name collision with the lens-shell + the data-framed div).
|
|
288
|
+
// See `frame-stack.tsx` docstring for the visual diagram. Lens-level
|
|
289
|
+
// lock: Meta Creative `CLAUDE.md` §"The shell/tray pattern".
|
|
290
|
+
export { FrameStack, type FrameStackProps } from './frame-stack'
|
|
291
|
+
|
|
292
|
+
// CalculatorShell — centered, fixed-width layout primitive for marketing
|
|
293
|
+
// / ad-performance / customer-economics calculators. Promoted under
|
|
294
|
+
// ADR-058 D8 (sharpens ADR-030 D2/D3/D5/D6/D8). First-day consumers
|
|
295
|
+
// (ADR-008 D3 gate cleared 3×): ROAS / ROI / CTR / CPC / CPM /
|
|
296
|
+
// Conversion Rate. The shell is presentational; each calculator route
|
|
297
|
+
// owns its `useState` and computes `result` reactively.
|
|
298
|
+
export { CalculatorShell, type CalculatorShellProps } from './calculator-shell'
|
|
299
|
+
|
|
300
|
+
// Calculator Shell v2 kit (ADR-076). Additive — v1 above is retained for
|
|
301
|
+
// the existing six math calculators. v2 = a superset shell with two
|
|
302
|
+
// layouts + a flat brand-ink header, the StatRow signature primitive, and
|
|
303
|
+
// a single-value Slider. Frame = ProfileSection (reused, D3); KPI chips =
|
|
304
|
+
// MetricCard labelPosition="top" (D8). First consumer: /tools/ctv-frequency.
|
|
305
|
+
export {
|
|
306
|
+
CalculatorShellV2,
|
|
307
|
+
type CalculatorShellV2Props,
|
|
308
|
+
type CalcLayout,
|
|
309
|
+
type CalcPrefill,
|
|
310
|
+
} from './calculator-shell-v2'
|
|
311
|
+
export { StatRow, type StatRowProps, type StatRowStep } from './stat-row'
|
|
312
|
+
export { Slider, type SliderProps } from './slider'
|
|
313
|
+
|
|
314
|
+
// Number-formatting helpers — `formatCurrency` / `formatPercent` /
|
|
315
|
+
// `formatRatio` / `formatNumber`. Pure functions, zero deps (built on
|
|
316
|
+
// `Intl.NumberFormat`). Spec'd in ADR-030 D6 and re-exported here per
|
|
317
|
+
// ADR-058 D8 so calculator routes consume the shell + helpers from a
|
|
318
|
+
// single `@lovett/ui` import. All four return `'—'` on NaN / ±Infinity
|
|
319
|
+
// so partial-input UI never crashes.
|
|
320
|
+
export { formatCurrency, formatPercent, formatRatio, formatNumber } from './format'
|
|
321
|
+
|
|
322
|
+
// Shared utilities — used by primitives, also re-exported for app/lens code
|
|
323
|
+
// that needs the same class-composition or layout numerics (e.g. Phase 2's
|
|
324
|
+
// brand-profile atoms when they promote into packages/ui/).
|
|
325
|
+
export { cn } from './lib/utils'
|
|
326
|
+
export { HEADER_HEIGHT, SECTION_SCROLL_OFFSET } from './lib/layout'
|
|
327
|
+
|
|
328
|
+
// v1.1 lens-port-batch promotions (Phase A pre-pass). 3+ consumers each
|
|
329
|
+
// in the v1.1 batch (Swipes / Collections / Discover / Prompts). Source:
|
|
330
|
+
// `brand-ai-extension/webapp/src/components/ui/` — same shape, tokens
|
|
331
|
+
// scrubbed to use --foreground / --text-tertiary / public surfaces only
|
|
332
|
+
// where the original used --text-muted (still allowed inside packages/ui).
|
|
333
|
+
export { PageHero, type PageHeroProps } from './page-hero'
|
|
334
|
+
export { SearchBar, type SearchBarProps } from './search-bar'
|
|
335
|
+
export {
|
|
336
|
+
FilterBar,
|
|
337
|
+
type FilterBarProps,
|
|
338
|
+
type FilterBarSearchProps,
|
|
339
|
+
type FilterBarCountProps,
|
|
340
|
+
} from './filter-bar'
|
|
341
|
+
export {
|
|
342
|
+
FilterDropdown,
|
|
343
|
+
type FilterDropdownProps,
|
|
344
|
+
type FilterOption,
|
|
345
|
+
} from './filter-dropdown'
|
|
346
|
+
|
|
347
|
+
// Meta / Instagram platform previews — promoted from
|
|
348
|
+
// docs/prototype/meta/preview-package/ per ADR-060 D9a (amended
|
|
349
|
+
// 2026-05-28). Pixel-accurate FB+IG+Messenger renderers; zero coupling
|
|
350
|
+
// to stores/hooks/routers. Consumed by the Meta Creative lens's
|
|
351
|
+
// PreviewRail; available for future Meta-related lenses (the D9a
|
|
352
|
+
// promotion rationale). Namespace import keeps the 10 platform-named
|
|
353
|
+
// components out of the root `@lovett/ui` export.
|
|
354
|
+
// DeviceFrame — generic, content-agnostic device bezel (macOS Safari
|
|
355
|
+
// window or iPhone). Genericized/promoted from the sem-spec source app's
|
|
356
|
+
// SerpCard MacOSFrame + IPhoneFrame (all Google/SERP specifics stripped).
|
|
357
|
+
// Device-replica chrome colors are owned by the primitive (like the
|
|
358
|
+
// platform-chrome allowlist), not the theme palette. First reuse target:
|
|
359
|
+
// AdStage. Ratified by ADR-087 (accepted 2026-06-07); sem-spec retrofit is a
|
|
360
|
+
// tracked follow-up in that ADR.
|
|
361
|
+
export { DeviceFrame, type DeviceFrameProps } from './device-frame'
|
|
362
|
+
|
|
363
|
+
export * as MetaPreviews from './meta-previews'
|
|
364
|
+
export type {
|
|
365
|
+
PreviewAdData,
|
|
366
|
+
PreviewPlatform,
|
|
367
|
+
PreviewPlacement,
|
|
368
|
+
PreviewDevice,
|
|
369
|
+
PlacementPreview,
|
|
370
|
+
PreviewConfiguration,
|
|
371
|
+
} from './meta-previews/types'
|
|
372
|
+
export { PLACEMENT_CONFIGS } from './meta-previews/types'
|
package/src/input.tsx
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input — typed text input wrapped in the .input-shell from styles.css.
|
|
3
|
+
* Supports leading/trailing affixes (icon, badge, action button), three
|
|
4
|
+
* sizes (sm 32 / md 40 / lg 48), and the standard error/disabled states.
|
|
5
|
+
*
|
|
6
|
+
* <Input value={v} onChange={e => setV(e.target.value)} placeholder="Search…" />
|
|
7
|
+
*
|
|
8
|
+
* <Input
|
|
9
|
+
* leadingAffix={<Search className="h-4 w-4" />}
|
|
10
|
+
* trailingAffix={<KeyboardShortcut keys="⌘K" />}
|
|
11
|
+
* placeholder="Type to search"
|
|
12
|
+
* />
|
|
13
|
+
*
|
|
14
|
+
* <Input error placeholder="Invalid" />
|
|
15
|
+
*
|
|
16
|
+
* <Input
|
|
17
|
+
* type="password"
|
|
18
|
+
* showPasswordToggle
|
|
19
|
+
* autoComplete="current-password"
|
|
20
|
+
* />
|
|
21
|
+
*/
|
|
22
|
+
import {
|
|
23
|
+
forwardRef,
|
|
24
|
+
useState,
|
|
25
|
+
type InputHTMLAttributes,
|
|
26
|
+
type ReactNode,
|
|
27
|
+
} from 'react'
|
|
28
|
+
import { Eye, EyeOff } from 'lucide-react'
|
|
29
|
+
import { cn } from './lib/utils'
|
|
30
|
+
|
|
31
|
+
export type InputSize = 'sm' | 'md' | 'lg'
|
|
32
|
+
|
|
33
|
+
export interface InputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'size' | 'prefix'> {
|
|
34
|
+
inputSize?: InputSize
|
|
35
|
+
error?: boolean
|
|
36
|
+
/** Element rendered before the input — typically an icon. Static, no interaction. */
|
|
37
|
+
leadingAffix?: ReactNode
|
|
38
|
+
/** Element rendered after the input. Pass an `<button class="affix action">` for clickable. */
|
|
39
|
+
trailingAffix?: ReactNode
|
|
40
|
+
/** Override classes on the .input-shell wrapper. */
|
|
41
|
+
shellClassName?: string
|
|
42
|
+
/**
|
|
43
|
+
* Show/hide affordance for `type="password"`. Manages visibility state
|
|
44
|
+
* internally and renders an Eye/EyeOff toggle in the trailing affix slot.
|
|
45
|
+
* No-op when `type !== "password"`. Caller-supplied `trailingAffix`
|
|
46
|
+
* wins — the toggle is only injected when the slot is otherwise empty.
|
|
47
|
+
*/
|
|
48
|
+
showPasswordToggle?: boolean
|
|
49
|
+
/**
|
|
50
|
+
* Numeric variant (ADR-076 D8) — renders the field in the monospace
|
|
51
|
+
* face with `tabular-nums` so digits align as the value changes.
|
|
52
|
+
* Pair with the existing `leadingAffix` (e.g. a `$`) / `trailingAffix`
|
|
53
|
+
* (e.g. a unit suffix) for calculator inputs. No new prefix/suffix
|
|
54
|
+
* props — affixes are the canonical adornment slots.
|
|
55
|
+
*/
|
|
56
|
+
numeric?: boolean
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const SIZE_CLASS: Record<InputSize, string> = {
|
|
60
|
+
sm: 'size-sm',
|
|
61
|
+
md: '',
|
|
62
|
+
lg: 'size-lg',
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const Input = forwardRef<HTMLInputElement, InputProps>(function Input(
|
|
66
|
+
{
|
|
67
|
+
inputSize = 'md',
|
|
68
|
+
error,
|
|
69
|
+
leadingAffix,
|
|
70
|
+
trailingAffix,
|
|
71
|
+
shellClassName,
|
|
72
|
+
className,
|
|
73
|
+
disabled,
|
|
74
|
+
type,
|
|
75
|
+
showPasswordToggle,
|
|
76
|
+
numeric,
|
|
77
|
+
...rest
|
|
78
|
+
},
|
|
79
|
+
ref,
|
|
80
|
+
) {
|
|
81
|
+
const [revealed, setRevealed] = useState(false)
|
|
82
|
+
const isPasswordToggle =
|
|
83
|
+
showPasswordToggle === true && type === 'password' && !trailingAffix
|
|
84
|
+
const resolvedType = isPasswordToggle && revealed ? 'text' : type
|
|
85
|
+
const resolvedTrailingAffix = isPasswordToggle ? (
|
|
86
|
+
<button
|
|
87
|
+
type="button"
|
|
88
|
+
onClick={() => setRevealed((v) => !v)}
|
|
89
|
+
aria-label={revealed ? 'Hide password' : 'Show password'}
|
|
90
|
+
aria-pressed={revealed}
|
|
91
|
+
className="affix action"
|
|
92
|
+
tabIndex={-1}
|
|
93
|
+
>
|
|
94
|
+
{revealed ? (
|
|
95
|
+
<EyeOff className="h-4 w-4" aria-hidden />
|
|
96
|
+
) : (
|
|
97
|
+
<Eye className="h-4 w-4" aria-hidden />
|
|
98
|
+
)}
|
|
99
|
+
</button>
|
|
100
|
+
) : (
|
|
101
|
+
trailingAffix
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<div
|
|
106
|
+
className={cn(
|
|
107
|
+
'input-shell',
|
|
108
|
+
SIZE_CLASS[inputSize],
|
|
109
|
+
numeric && 'is-numeric',
|
|
110
|
+
leadingAffix && 'has-leading',
|
|
111
|
+
resolvedTrailingAffix && 'has-trailing',
|
|
112
|
+
error && 'is-error',
|
|
113
|
+
disabled && 'is-disabled',
|
|
114
|
+
shellClassName,
|
|
115
|
+
)}
|
|
116
|
+
>
|
|
117
|
+
{leadingAffix && <span className="affix">{leadingAffix}</span>}
|
|
118
|
+
<input
|
|
119
|
+
ref={ref}
|
|
120
|
+
type={resolvedType}
|
|
121
|
+
disabled={disabled}
|
|
122
|
+
className={className}
|
|
123
|
+
{...rest}
|
|
124
|
+
/>
|
|
125
|
+
{resolvedTrailingAffix && (
|
|
126
|
+
<span className="affix">{resolvedTrailingAffix}</span>
|
|
127
|
+
)}
|
|
128
|
+
</div>
|
|
129
|
+
)
|
|
130
|
+
})
|
package/src/kbd.tsx
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kbd — semantic keyboard-input chip.
|
|
3
|
+
*
|
|
4
|
+
* Promoted from the Chat lens's inline title-chip markup per ADR-050 D11.
|
|
5
|
+
* Committed consumers (ADR-008 D3 promotion gate):
|
|
6
|
+
* • Chat lens title-bar chip (apps/workspace/src/lenses/chat/index.tsx) —
|
|
7
|
+
* first migrated consumer, lands in this phase.
|
|
8
|
+
* • Command palette body / footer hints (ADR-050 Phases B–E).
|
|
9
|
+
* • Sidebar switcher-popout Esc hint (ADR-049 Phase C).
|
|
10
|
+
*
|
|
11
|
+
* Renders a `<kbd>` semantic element (screen readers announce as keyboard
|
|
12
|
+
* input). Token-driven styling — no hex literals, all colors flow through
|
|
13
|
+
* public tokens.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { ReactNode, CSSProperties } from 'react'
|
|
17
|
+
|
|
18
|
+
export interface KbdProps {
|
|
19
|
+
/** Child text — typically a single key glyph or short string. */
|
|
20
|
+
children: ReactNode
|
|
21
|
+
/** Optional size — defaults to 'md'. */
|
|
22
|
+
size?: 'sm' | 'md'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function Kbd({ children, size = 'md' }: KbdProps) {
|
|
26
|
+
const style: CSSProperties = {
|
|
27
|
+
display: 'inline-flex',
|
|
28
|
+
alignItems: 'center',
|
|
29
|
+
justifyContent: 'center',
|
|
30
|
+
gap: '0.125rem',
|
|
31
|
+
background: 'rgb(var(--surface-overlay-soft))',
|
|
32
|
+
border: '1px solid rgb(var(--border))',
|
|
33
|
+
color: 'rgb(var(--text-tertiary))',
|
|
34
|
+
fontFamily: 'var(--font-mono)',
|
|
35
|
+
fontSize: size === 'sm' ? '10.5px' : '11.5px',
|
|
36
|
+
padding: size === 'sm' ? '1px 6px' : '2px 8px',
|
|
37
|
+
borderRadius: 'var(--radius-xs)',
|
|
38
|
+
lineHeight: 1,
|
|
39
|
+
}
|
|
40
|
+
return <kbd style={style}>{children}</kbd>
|
|
41
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layout constants shared between CSS and JS.
|
|
3
|
+
*
|
|
4
|
+
* Some primitives (e.g. ChipNav) need numeric pixel values for
|
|
5
|
+
* `IntersectionObserver` rootMargin or `position: sticky` `top` style
|
|
6
|
+
* — those can't read from CSS custom properties. To keep the layout
|
|
7
|
+
* coherent, this module mirrors the canonical values from `tokens.css`.
|
|
8
|
+
* If you change one, change the other.
|
|
9
|
+
*
|
|
10
|
+
* The corresponding CSS tokens are:
|
|
11
|
+
* --header-height → HEADER_HEIGHT
|
|
12
|
+
* --section-scroll-offset → SECTION_SCROLL_OFFSET
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/** Height of the sticky AppHeader, in px. Mirrors `--header-height`. */
|
|
16
|
+
export const HEADER_HEIGHT = 56
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Vertical offset for in-page section anchors (header + chip nav +
|
|
20
|
+
* breathing room). Mirrors `--section-scroll-offset`.
|
|
21
|
+
*/
|
|
22
|
+
export const SECTION_SCROLL_OFFSET = 120
|