@lovett/ui 0.0.5 → 0.0.7
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/dist/index.d.ts +279 -115
- package/dist/index.js +479 -66
- package/dist/index.js.map +1 -1
- package/dist/styles.css +74 -0
- package/dist/tokens.css +181 -60
- package/package.json +16 -4
- package/src/__tests__/button.test.tsx +137 -0
- package/src/__tests__/card.test.tsx +103 -0
- package/src/__tests__/dead-render.test.tsx +117 -0
- package/src/__tests__/input.test.tsx +134 -0
- package/src/__tests__/modal.test.tsx +154 -0
- package/src/__tests__/page-shell.test.tsx +128 -0
- package/src/__tests__/setup.ts +43 -0
- package/src/__tests__/token-shape.test.ts +193 -0
- package/src/card.tsx +1 -1
- package/src/collapsible-card.tsx +85 -0
- package/src/data-grid/table-body.tsx +8 -1
- package/src/dropdown-menu.tsx +1 -1
- package/src/floating-status-bar.tsx +1 -1
- package/src/folder-tree-picker.tsx +5 -6
- package/src/frame-stack.tsx +27 -10
- package/src/hero-form-card.tsx +2 -2
- package/src/icons/brand.tsx +187 -0
- package/src/index.ts +32 -0
- package/src/lib/clipboard.ts +14 -0
- package/src/lib/color.ts +111 -0
- package/src/meta-cell.tsx +52 -0
- package/src/meta-previews/MetaFeedCarousel.tsx +1 -1
- package/src/meta-previews/MetaFeedPreview.tsx +1 -1
- package/src/modal.tsx +77 -6
- package/src/pill-button.tsx +23 -5
- package/src/profile-section.tsx +40 -9
- package/src/sortable-table.tsx +5 -1
- package/src/styles.css +74 -0
- package/src/tabs.tsx +4 -0
- package/src/tag-chip-input.tsx +1 -1
- package/src/theme-v2.css +466 -0
- package/src/tokens.css +181 -60
- package/src/v2/README.md +208 -0
- package/src/v2/__demo__/showcase.tsx +1045 -0
- package/src/v2/action.tsx +91 -0
- package/src/v2/callout.tsx +76 -0
- package/src/v2/document-section.tsx +82 -0
- package/src/v2/document-shell.tsx +0 -0
- package/src/v2/field-row.tsx +113 -0
- package/src/v2/icons.tsx +165 -0
- package/src/v2/index.ts +147 -0
- package/src/v2/layout.tsx +293 -0
- package/src/v2/progress-track.tsx +89 -0
- package/src/v2/stat-tile.tsx +129 -0
- package/src/v2/states.tsx +271 -0
- package/src/v2/status-pill.tsx +74 -0
- package/src/v2/theme.css +1861 -0
- package/src/v2/timeline.tsx +81 -0
- package/src/v2/tokens.ts +228 -0
- package/src/value-chip.tsx +76 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
// @vitest-environment node
|
|
2
|
+
//
|
|
3
|
+
// Pure source scan — no DOM needed, and jsdom rewrites import.meta.url to
|
|
4
|
+
// an http URL that fileURLToPath rejects.
|
|
5
|
+
/**
|
|
6
|
+
* Static guard for the "type-checks, lints, builds, renders nothing"
|
|
7
|
+
* defect class — CSS that is syntactically fine and semantically void.
|
|
8
|
+
*
|
|
9
|
+
* WHY THIS IS A SOURCE SCAN AND NOT A RENDER TEST
|
|
10
|
+
* ------------------------------------------------
|
|
11
|
+
* The first attempt at this asserted rendered inline styles. It could not
|
|
12
|
+
* work: any value containing `var()` is "pending substitution", so jsdom
|
|
13
|
+
* (and browsers, at parse time) store it verbatim without validating it.
|
|
14
|
+
* `rgb(var(--surface-overlay-soft) / 0.5)` and `rgb(var(--muted))` are
|
|
15
|
+
* indistinguishable through the CSSOM — a render test happily passes on
|
|
16
|
+
* the broken form. The invalidity only appears at computed-value time in
|
|
17
|
+
* a real browser, which is precisely why typecheck, lint, build and the
|
|
18
|
+
* whole test suite were all green while three primitives shipped with no
|
|
19
|
+
* error border and every SortableTable shipped unbanded.
|
|
20
|
+
*
|
|
21
|
+
* So this reads the source instead, and derives its rules from
|
|
22
|
+
* tokens.css rather than hardcoding a token list — a new alpha-carrying
|
|
23
|
+
* or shadow-valued token is covered the day it is added.
|
|
24
|
+
*
|
|
25
|
+
* The two rules:
|
|
26
|
+
*
|
|
27
|
+
* 1. A token whose VALUE is a shadow list ("0 0 0 3px rgb(...)") can
|
|
28
|
+
* never appear inside rgb(). `rgb(var(--ring-error))` expands to
|
|
29
|
+
* `rgb(0 0 0 3px rgb(...))`, which is dropped.
|
|
30
|
+
*
|
|
31
|
+
* 2. A token whose VALUE already carries an alpha ("0 0 0 / 0.03")
|
|
32
|
+
* can never take a second one. `rgb(var(--surface-overlay-soft) / 0.5)`
|
|
33
|
+
* expands to `rgb(0 0 0 / 0.03 / 0.5)`, which is dropped.
|
|
34
|
+
*/
|
|
35
|
+
import { readFileSync, readdirSync, statSync } from 'node:fs'
|
|
36
|
+
import { join, relative } from 'node:path'
|
|
37
|
+
import { fileURLToPath } from 'node:url'
|
|
38
|
+
import { describe, expect, it } from 'vitest'
|
|
39
|
+
|
|
40
|
+
const SRC = fileURLToPath(new URL('..', import.meta.url))
|
|
41
|
+
|
|
42
|
+
function sourceFiles(dir: string, acc: string[] = []): string[] {
|
|
43
|
+
for (const entry of readdirSync(dir)) {
|
|
44
|
+
if (entry === 'node_modules' || entry === '__tests__') continue
|
|
45
|
+
const full = join(dir, entry)
|
|
46
|
+
if (statSync(full).isDirectory()) sourceFiles(full, acc)
|
|
47
|
+
else if (/\.(tsx?|css)$/.test(entry) && entry !== 'tokens.css') acc.push(full)
|
|
48
|
+
}
|
|
49
|
+
return acc
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Parse tokens.css into { name -> [values across all theme blocks] }. */
|
|
53
|
+
function readTokenValues(): Map<string, string[]> {
|
|
54
|
+
const css = readFileSync(join(SRC, 'tokens.css'), 'utf8')
|
|
55
|
+
const out = new Map<string, string[]>()
|
|
56
|
+
for (const [, name, value] of css.matchAll(/^\s*(--[a-z0-9-]+)\s*:\s*([^;]+);/gm)) {
|
|
57
|
+
const list = out.get(name!) ?? []
|
|
58
|
+
list.push(value!.trim())
|
|
59
|
+
out.set(name!, list)
|
|
60
|
+
}
|
|
61
|
+
return out
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const TOKENS = readTokenValues()
|
|
65
|
+
|
|
66
|
+
/** A shadow list — has a length unit, so it is not a colour. */
|
|
67
|
+
const isShadowValued = (values: string[]) =>
|
|
68
|
+
values.some((v) => /\b\d+(px|rem|em)\b/.test(v) || v.startsWith('inset '))
|
|
69
|
+
|
|
70
|
+
/** Already carries an alpha, so it cannot take a second one. */
|
|
71
|
+
const isAlphaCarrying = (values: string[]) =>
|
|
72
|
+
values.some((v) => /^[\d\s.]+\/\s*[\d.]+$/.test(v))
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Blank out comments while preserving line numbers, so a violation still
|
|
76
|
+
* reports the line it is on. Without this the scan flags its own
|
|
77
|
+
* explanatory comments — every doc-block that spells out the broken form
|
|
78
|
+
* in order to warn against it.
|
|
79
|
+
*
|
|
80
|
+
* Line comments are only stripped when the line STARTS with `//`, so a
|
|
81
|
+
* `https://` inside a string is left intact.
|
|
82
|
+
*/
|
|
83
|
+
function stripComments(src: string): string {
|
|
84
|
+
return src
|
|
85
|
+
.replace(/\/\*[\s\S]*?\*\//g, (m) => m.replace(/[^\n]/g, ' '))
|
|
86
|
+
.split('\n')
|
|
87
|
+
.map((line) => (/^\s*\/\//.test(line) ? '' : line))
|
|
88
|
+
.join('\n')
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const FILES = sourceFiles(SRC)
|
|
92
|
+
const rel = (f: string) => relative(SRC, f)
|
|
93
|
+
const readCode = (f: string) => stripComments(readFileSync(f, 'utf8')).split('\n')
|
|
94
|
+
|
|
95
|
+
describe('token shape', () => {
|
|
96
|
+
it('parsed a plausible token file', () => {
|
|
97
|
+
// Guard the guard: if the parse silently returned nothing, every
|
|
98
|
+
// assertion below would vacuously pass.
|
|
99
|
+
expect(TOKENS.size).toBeGreaterThan(100)
|
|
100
|
+
expect(isShadowValued(TOKENS.get('--ring-error') ?? [])).toBe(true)
|
|
101
|
+
expect(isAlphaCarrying(TOKENS.get('--surface-overlay-soft') ?? [])).toBe(true)
|
|
102
|
+
expect(isShadowValued(TOKENS.get('--accent') ?? [])).toBe(false)
|
|
103
|
+
expect(isAlphaCarrying(TOKENS.get('--accent') ?? [])).toBe(false)
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('finds source files to scan', () => {
|
|
107
|
+
expect(FILES.length).toBeGreaterThan(50)
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('never wraps a shadow-valued token in rgb()', () => {
|
|
111
|
+
const violations: string[] = []
|
|
112
|
+
for (const file of FILES) {
|
|
113
|
+
readCode(file).forEach((line, i) => {
|
|
114
|
+
for (const [, token] of line.matchAll(/rgb\(\s*var\((--[a-z0-9-]+)\)/g)) {
|
|
115
|
+
if (isShadowValued(TOKENS.get(token!) ?? [])) {
|
|
116
|
+
violations.push(`${rel(file)}:${i + 1} — rgb(var(${token})) is a box-shadow list, not a colour`)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
})
|
|
120
|
+
}
|
|
121
|
+
expect(violations).toEqual([])
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it('never nests a second alpha on an alpha-carrying token', () => {
|
|
125
|
+
const violations: string[] = []
|
|
126
|
+
for (const file of FILES) {
|
|
127
|
+
readCode(file).forEach((line, i) => {
|
|
128
|
+
for (const [, token] of line.matchAll(
|
|
129
|
+
/rgb\(\s*var\((--[a-z0-9-]+)\)\s*\/\s*[\d.]/g,
|
|
130
|
+
)) {
|
|
131
|
+
if (isAlphaCarrying(TOKENS.get(token!) ?? [])) {
|
|
132
|
+
violations.push(`${rel(file)}:${i + 1} — var(${token}) already carries an alpha`)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
expect(violations).toEqual([])
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
it('never references a token that tokens.css does not define', () => {
|
|
141
|
+
const violations: string[] = []
|
|
142
|
+
for (const file of FILES) {
|
|
143
|
+
readCode(file).forEach((line, i) => {
|
|
144
|
+
for (const [, token] of line.matchAll(/var\((--[a-z0-9-]+)[,)]/g)) {
|
|
145
|
+
// Tailwind's own custom properties are not ours to define.
|
|
146
|
+
if (token!.startsWith('--tw-')) continue
|
|
147
|
+
if (!TOKENS.has(token!)) {
|
|
148
|
+
violations.push(`${rel(file)}:${i + 1} — var(${token}) is not defined in tokens.css`)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
})
|
|
152
|
+
}
|
|
153
|
+
expect(violations).toEqual([])
|
|
154
|
+
})
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
describe('interpolated Tailwind classes', () => {
|
|
158
|
+
it('never builds an arbitrary-value utility from a template literal', () => {
|
|
159
|
+
// Tailwind scans source statically, so `h-[${SIZE}px]` is never
|
|
160
|
+
// generated. It type-checks, lints and builds, and does nothing.
|
|
161
|
+
const violations: string[] = []
|
|
162
|
+
for (const file of FILES) {
|
|
163
|
+
readCode(file).forEach((line, i) => {
|
|
164
|
+
if (/[a-z-]+-\[[^\]]*\$\{/.test(line)) {
|
|
165
|
+
violations.push(`${rel(file)}:${i + 1} — ${line.trim()}`)
|
|
166
|
+
}
|
|
167
|
+
})
|
|
168
|
+
}
|
|
169
|
+
expect(violations).toEqual([])
|
|
170
|
+
})
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
describe('undefined utility classes', () => {
|
|
174
|
+
// `animate-in`, `fade-in-0`, `zoom-in-95`, `slide-in-from-*` ship in
|
|
175
|
+
// tailwindcss-animate / tw-animate-css. Neither is a dependency of this
|
|
176
|
+
// repo, and Tailwind 4 core ships only spin/ping/pulse/bounce — so both
|
|
177
|
+
// of the kit's entrance animations referenced classes that had never
|
|
178
|
+
// been generated and had never run.
|
|
179
|
+
const ANIMATE_PLUGIN_CLASSES =
|
|
180
|
+
/\b(animate-in|animate-out|fade-in(-\d+)?|fade-out(-\d+)?|zoom-in(-\d+)?|zoom-out(-\d+)?|slide-in-from-\w+(-\d+)?|slide-out-to-\w+(-\d+)?)\b/
|
|
181
|
+
|
|
182
|
+
it('does not use tailwindcss-animate classes without the dependency', () => {
|
|
183
|
+
const violations: string[] = []
|
|
184
|
+
for (const file of FILES) {
|
|
185
|
+
readCode(file).forEach((line, i) => {
|
|
186
|
+
if (ANIMATE_PLUGIN_CLASSES.test(line)) {
|
|
187
|
+
violations.push(`${rel(file)}:${i + 1} — ${line.trim()}`)
|
|
188
|
+
}
|
|
189
|
+
})
|
|
190
|
+
}
|
|
191
|
+
expect(violations).toEqual([])
|
|
192
|
+
})
|
|
193
|
+
})
|
package/src/card.tsx
CHANGED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// CollapsibleCard — a Card whose titled header toggles its body open/closed.
|
|
2
|
+
//
|
|
3
|
+
// Reviewing long specs means scrolling past sections you don't care about right
|
|
4
|
+
// now (a long Sitelinks list to reach Structured snippets, etc.). A collapsible
|
|
5
|
+
// titled card lets the reviewer fold sections away as they go. Built on the
|
|
6
|
+
// Card primitive; accessible (the title is a real button with aria-expanded)
|
|
7
|
+
// and honors prefers-reduced-motion.
|
|
8
|
+
//
|
|
9
|
+
// The collapse toggle is the chevron+title region only, so header `actions`
|
|
10
|
+
// (e.g. a "Suggest" button) stay independently clickable beside it — not nested
|
|
11
|
+
// inside the toggle button.
|
|
12
|
+
//
|
|
13
|
+
// Promoted from the SEM Spec lens per ADR-136 Decision 11 (the "2+ consumers ->
|
|
14
|
+
// promote" gate): the report tabs and the public share view both consume it.
|
|
15
|
+
|
|
16
|
+
import { useId, useState, type ReactNode } from 'react'
|
|
17
|
+
import { ChevronDown } from 'lucide-react'
|
|
18
|
+
import Card from './card'
|
|
19
|
+
|
|
20
|
+
export interface CollapsibleCardProps {
|
|
21
|
+
title: ReactNode
|
|
22
|
+
/** Optional leading icon (rendered before the title). */
|
|
23
|
+
icon?: ReactNode
|
|
24
|
+
/** Optional header content after the title (e.g. a count or subtitle). */
|
|
25
|
+
meta?: ReactNode
|
|
26
|
+
/** Right-aligned header controls, kept outside the collapse toggle. */
|
|
27
|
+
actions?: ReactNode
|
|
28
|
+
/** Start expanded (default) or collapsed. */
|
|
29
|
+
defaultOpen?: boolean
|
|
30
|
+
/** Body padding utility; pass '' for full-bleed bodies (e.g. tables). */
|
|
31
|
+
contentClassName?: string
|
|
32
|
+
children: ReactNode
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function CollapsibleCard({
|
|
36
|
+
title,
|
|
37
|
+
icon,
|
|
38
|
+
meta,
|
|
39
|
+
actions,
|
|
40
|
+
defaultOpen = true,
|
|
41
|
+
contentClassName = 'px-4 py-4',
|
|
42
|
+
children,
|
|
43
|
+
}: CollapsibleCardProps) {
|
|
44
|
+
const [open, setOpen] = useState(defaultOpen)
|
|
45
|
+
const bodyId = useId()
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<Card>
|
|
49
|
+
<div
|
|
50
|
+
className="flex items-center justify-between gap-3 px-4 py-3"
|
|
51
|
+
style={{
|
|
52
|
+
borderBottom: `1px solid ${open ? 'rgb(var(--border))' : 'transparent'}`,
|
|
53
|
+
}}
|
|
54
|
+
>
|
|
55
|
+
<button
|
|
56
|
+
type="button"
|
|
57
|
+
onClick={() => setOpen((o) => !o)}
|
|
58
|
+
aria-expanded={open}
|
|
59
|
+
aria-controls={bodyId}
|
|
60
|
+
className="flex min-w-0 flex-1 items-center gap-2 rounded-[var(--radius-sm)] py-0.5 text-left transition-colors focus-visible:outline-none focus-visible:[box-shadow:var(--ring-focus)]"
|
|
61
|
+
>
|
|
62
|
+
<ChevronDown
|
|
63
|
+
className="h-4 w-4 shrink-0 transition-transform duration-200 ease-out motion-reduce:transition-none"
|
|
64
|
+
style={{
|
|
65
|
+
color: 'rgb(var(--text-tertiary))',
|
|
66
|
+
transform: open ? 'rotate(0deg)' : 'rotate(-90deg)',
|
|
67
|
+
}}
|
|
68
|
+
aria-hidden="true"
|
|
69
|
+
/>
|
|
70
|
+
{icon}
|
|
71
|
+
<span className="truncate text-[14px] font-semibold" style={{ color: 'rgb(var(--foreground))' }}>
|
|
72
|
+
{title}
|
|
73
|
+
</span>
|
|
74
|
+
{meta}
|
|
75
|
+
</button>
|
|
76
|
+
{actions && <div className="flex shrink-0 items-center gap-2">{actions}</div>}
|
|
77
|
+
</div>
|
|
78
|
+
{open && (
|
|
79
|
+
<div id={bodyId} className={contentClassName}>
|
|
80
|
+
{children}
|
|
81
|
+
</div>
|
|
82
|
+
)}
|
|
83
|
+
</Card>
|
|
84
|
+
)
|
|
85
|
+
}
|
|
@@ -257,7 +257,14 @@ export function DataGridTableBody<
|
|
|
257
257
|
return (
|
|
258
258
|
<TableBody>
|
|
259
259
|
{visibleRows.map((row, rowIndex) => (
|
|
260
|
-
|
|
260
|
+
// TableRow styles `data-[state=selected]`; without this attribute a
|
|
261
|
+
// checked row had no visual state at all — only the checkbox changed.
|
|
262
|
+
<TableRow
|
|
263
|
+
key={row.id}
|
|
264
|
+
{...(selectedRowIds.includes(row.id)
|
|
265
|
+
? { 'data-state': 'selected' as const }
|
|
266
|
+
: {})}
|
|
267
|
+
>
|
|
261
268
|
<TableCell
|
|
262
269
|
className="h-10 px-0 text-center"
|
|
263
270
|
style={{
|
package/src/dropdown-menu.tsx
CHANGED
|
@@ -276,7 +276,7 @@ export function DropdownMenuContent({
|
|
|
276
276
|
'fixed z-[100] min-w-48 max-w-[min(28rem,calc(100vw-16px))]',
|
|
277
277
|
'p-1.5 overflow-y-auto',
|
|
278
278
|
'rounded-[var(--radius-lg)]',
|
|
279
|
-
isPositioned && '
|
|
279
|
+
isPositioned && 'ds-enter-pop',
|
|
280
280
|
className,
|
|
281
281
|
)}
|
|
282
282
|
style={{
|
|
@@ -61,7 +61,7 @@ export function FloatingStatusBar({
|
|
|
61
61
|
role="status"
|
|
62
62
|
className={cn(
|
|
63
63
|
'fixed bottom-4 left-1/2 z-30 flex -translate-x-1/2 items-center gap-3.5 px-3.5 py-2.5',
|
|
64
|
-
'
|
|
64
|
+
'ds-enter-rise',
|
|
65
65
|
className,
|
|
66
66
|
)}
|
|
67
67
|
style={{
|
|
@@ -265,13 +265,12 @@ export function FolderTreePicker({
|
|
|
265
265
|
aria-controls={open ? popoverId : undefined}
|
|
266
266
|
disabled={disabled}
|
|
267
267
|
onClick={() => setOpen((o) => !o)}
|
|
268
|
-
className={cn(
|
|
269
|
-
'btn btn-secondary justify-between w-full',
|
|
270
|
-
error && 'is-error',
|
|
271
|
-
className,
|
|
272
|
-
)}
|
|
268
|
+
className={cn('btn btn-secondary justify-between w-full', className)}
|
|
273
269
|
style={{
|
|
274
|
-
|
|
270
|
+
/* --ring-error is a box-shadow LIST, not a colour; rgb(var(--ring-error))
|
|
271
|
+
is invalid CSS and gets dropped, which left this error state inert.
|
|
272
|
+
The colour token is --destructive (what .input-shell.is-error uses). */
|
|
273
|
+
borderColor: error ? 'rgb(var(--destructive))' : undefined,
|
|
275
274
|
}}
|
|
276
275
|
>
|
|
277
276
|
<span className="inline-flex items-center gap-2 min-w-0">
|
package/src/frame-stack.tsx
CHANGED
|
@@ -1,13 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* FrameStack — slate outer-frame wrapper for the shell/tray pattern.
|
|
3
3
|
*
|
|
4
|
-
* Single purpose: wrap 2+ inner cards as ONE visual group. Renders
|
|
5
|
-
*
|
|
6
|
-
* the children with
|
|
4
|
+
* Single purpose: wrap 2+ inner cards as ONE visual group. Renders an
|
|
5
|
+
* outer frame (--surface-frame via --card-frame-bg, --border-card)
|
|
6
|
+
* holding the children with a --tray-inset outer padding and a slightly
|
|
7
|
+
* larger gap between them.
|
|
8
|
+
*
|
|
9
|
+
* The padding is the SAME token <Card> uses for its own tray inset, so a
|
|
10
|
+
* framed group and a framed card have an identical edge. It was a
|
|
11
|
+
* hardcoded 10 px, which made the group frame visibly chunkier than every
|
|
12
|
+
* card beside it — the two patterns are the same idea and should not
|
|
13
|
+
* measure differently.
|
|
14
|
+
*
|
|
15
|
+
* Padding smaller than the gap is deliberate, not an oversight: it is
|
|
16
|
+
* what the corpus kits measure (AI Agent's shell is pad 4 / gap 10), and
|
|
17
|
+
* it reads as panes seated in a tight frame rather than cards floating in
|
|
18
|
+
* a loose one.
|
|
7
19
|
*
|
|
8
20
|
* ┌─────────────────────────────────┐ ← <FrameStack>
|
|
9
|
-
* │ ┌─────────────────────────────┐ │
|
|
10
|
-
* │ │ Card / SectionCard 01 │ │
|
|
21
|
+
* │ ┌─────────────────────────────┐ │ frame bg (--card-frame-bg)
|
|
22
|
+
* │ │ Card / SectionCard 01 │ │ --tray-inset padding, 10 px gap
|
|
11
23
|
* │ └─────────────────────────────┘ │ 1 px --border-card outline
|
|
12
24
|
* │ ┌─────────────────────────────┐ │ --radius-xl (20 px) outer
|
|
13
25
|
* │ │ Card / SectionCard 02 │ │
|
|
@@ -28,9 +40,8 @@
|
|
|
28
40
|
* elements as direct page-background children with `gap-3`. That's
|
|
29
41
|
* "free-floating cards," not the shell/tray pattern. Wrap them.
|
|
30
42
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* primitive for the 3-section form.
|
|
43
|
+
* Consumers: the Social Spec builder (BuilderForm, TacticBuilder) and the
|
|
44
|
+
* Brand Profile section groups.
|
|
34
45
|
*
|
|
35
46
|
* Token discipline: internal tokens (--card-frame-bg, --border-card)
|
|
36
47
|
* are touched HERE per ADR-006 D2 — primitives live under
|
|
@@ -45,10 +56,16 @@ export interface FrameStackProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
45
56
|
* prototype's spec. Override only with a token value
|
|
46
57
|
* (e.g. `'var(--space-3)'` for 12 px) — never a literal. */
|
|
47
58
|
gap?: string
|
|
59
|
+
/** Outer padding. Defaults to `--tray-inset`, the same inset <Card>
|
|
60
|
+
* uses, so a framed group and a framed card share one edge. */
|
|
61
|
+
padding?: string
|
|
48
62
|
}
|
|
49
63
|
|
|
50
64
|
export const FrameStack = forwardRef<HTMLDivElement, FrameStackProps>(
|
|
51
|
-
function FrameStack(
|
|
65
|
+
function FrameStack(
|
|
66
|
+
{ className, style, gap = '10px', padding = 'var(--tray-inset)', children, ...rest },
|
|
67
|
+
ref,
|
|
68
|
+
) {
|
|
52
69
|
return (
|
|
53
70
|
<div
|
|
54
71
|
ref={ref}
|
|
@@ -58,7 +75,7 @@ export const FrameStack = forwardRef<HTMLDivElement, FrameStackProps>(
|
|
|
58
75
|
background: 'rgb(var(--card-frame-bg))',
|
|
59
76
|
border: '1px solid rgb(var(--border-card))',
|
|
60
77
|
borderRadius: 'var(--radius-xl)',
|
|
61
|
-
padding
|
|
78
|
+
padding,
|
|
62
79
|
gap,
|
|
63
80
|
...style,
|
|
64
81
|
}}
|
package/src/hero-form-card.tsx
CHANGED
|
@@ -343,8 +343,8 @@ const BANNER_TONE: Record<
|
|
|
343
343
|
icon: 'rgb(var(--warning))',
|
|
344
344
|
},
|
|
345
345
|
destructive: {
|
|
346
|
-
bg: 'rgb(var(--
|
|
347
|
-
border: 'rgb(var(--
|
|
346
|
+
bg: 'rgb(var(--destructive-bg))',
|
|
347
|
+
border: 'rgb(var(--destructive))',
|
|
348
348
|
icon: 'rgb(var(--destructive))',
|
|
349
349
|
},
|
|
350
350
|
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Brand platform glyphs — real mono (currentColor) marks for the ad platforms
|
|
3
|
+
* the Social Spec builds for, replacing approximate Lucide stand-ins (Ghost for
|
|
4
|
+
* Snapchat, Home for Nextdoor, Music for TikTok, Megaphone for Meta). ADR-135.
|
|
5
|
+
*
|
|
6
|
+
* `@lovett/ui` is the one allowed home for bespoke SVG definitions (CLAUDE.md
|
|
7
|
+
* §2/§6) — same rationale as `MicrosoftLogo`. Path data is extracted from the
|
|
8
|
+
* owner's MIT-licensed fork github.com/edwinlov3tt/logos-apps (upstream: the
|
|
9
|
+
* ln-dev7 "logos" collection); each glyph is normalized to a single-colour
|
|
10
|
+
* `currentColor` silhouette so it adapts to its context (active pill = white,
|
|
11
|
+
* inactive tab = grey) — full-colour marks would clash inside the red pill.
|
|
12
|
+
* Extraction choices were picked by visual review; Facebook, Messenger and
|
|
13
|
+
* TikTok silhouettes are true path-boolean derivations of the official marks
|
|
14
|
+
* (disc⊖f, bubble⊖bolt, glitch-layer union — computed offline with paper.js,
|
|
15
|
+
* baked here as static path data).
|
|
16
|
+
*
|
|
17
|
+
* Marks are trademarks of their platforms — used nominatively to refer to the
|
|
18
|
+
* platforms themselves, exactly like the platform-chrome in the ad previews.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
export interface BrandIconProps {
|
|
22
|
+
/** Square box size in px; the glyph letterboxes inside (non-square viewBoxes
|
|
23
|
+
* centre via preserveAspectRatio). Default 16 — a tab/affix glyph. */
|
|
24
|
+
size?: number
|
|
25
|
+
className?: string
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Meta brand glyph (mono, currentColor). */
|
|
29
|
+
export function BrandMeta({ size = 16, className }: BrandIconProps) {
|
|
30
|
+
return (
|
|
31
|
+
<svg
|
|
32
|
+
width={size}
|
|
33
|
+
height={size}
|
|
34
|
+
viewBox="-5.12 -5.12 266.24 180.28"
|
|
35
|
+
className={className}
|
|
36
|
+
aria-hidden="true"
|
|
37
|
+
focusable="false"
|
|
38
|
+
>
|
|
39
|
+
<path d="M27.651 112.136c0 9.775 2.146 17.28 4.95 21.82c3.677 5.947 9.16 8.466 14.751 8.466c7.211 0 13.808-1.79 26.52-19.372c10.185-14.092 22.186-33.874 30.26-46.275l13.675-21.01c9.499-14.591 20.493-30.811 33.1-41.806C161.196 4.985 172.298 0 183.47 0c18.758 0 36.625 10.87 50.3 31.257C248.735 53.584 256 81.707 256 110.729c0 17.253-3.4 29.93-9.187 39.946c-5.591 9.686-16.488 19.363-34.818 19.363v-27.616c15.695 0 19.612-14.422 19.612-30.927c0-23.52-5.484-49.623-17.564-68.273c-8.574-13.23-19.684-21.313-31.907-21.313c-13.22 0-23.859 9.97-35.815 27.75c-6.356 9.445-12.882 20.956-20.208 33.944l-8.066 14.289c-16.203 28.728-20.307 35.271-28.408 46.07c-14.2 18.91-26.324 26.076-42.287 26.076c-18.935 0-30.91-8.2-38.325-20.556C2.973 139.413 0 126.202 0 111.148z" fill="currentColor" />
|
|
40
|
+
<path d="M21.802 33.206C34.48 13.666 52.774 0 73.757 0C85.91 0 97.99 3.597 110.605 13.897c13.798 11.261 28.505 29.805 46.853 60.368l6.58 10.967c15.881 26.459 24.917 40.07 30.205 46.49c6.802 8.243 11.565 10.7 17.752 10.7c15.695 0 19.612-14.422 19.612-30.927l24.393-.766c0 17.253-3.4 29.93-9.187 39.946c-5.591 9.686-16.488 19.363-34.818 19.363c-11.395 0-21.49-2.475-32.654-13.007c-8.582-8.083-18.615-22.443-26.334-35.352l-22.96-38.352C118.528 64.08 107.96 49.73 101.845 43.23c-6.578-6.988-15.036-15.428-28.532-15.428c-10.923 0-20.2 7.666-27.963 19.39z" fill="currentColor" />
|
|
41
|
+
<path d="M73.312 27.802c-10.923 0-20.2 7.666-27.963 19.39c-10.976 16.568-17.698 41.245-17.698 64.944c0 9.775 2.146 17.28 4.95 21.82L9.027 149.482C2.973 139.413 0 126.202 0 111.148C0 83.772 7.514 55.24 21.802 33.206C34.48 13.666 52.774 0 73.757 0z" fill="currentColor" />
|
|
42
|
+
</svg>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Facebook brand glyph (mono, currentColor). */
|
|
47
|
+
export function BrandFacebook({ size = 16, className }: BrandIconProps) {
|
|
48
|
+
return (
|
|
49
|
+
<svg
|
|
50
|
+
width={size}
|
|
51
|
+
height={size}
|
|
52
|
+
viewBox="-5.12 -5.12 266.24 264.69"
|
|
53
|
+
className={className}
|
|
54
|
+
aria-hidden="true"
|
|
55
|
+
focusable="false"
|
|
56
|
+
>
|
|
57
|
+
<path d="M256 128C256 57.308 198.692 0 128 0S0 57.308 0 128c0 63.888 46.808 116.843 108 126.445V165H75.5v-37H108V99.8c0-32.08 19.11-49.8 48.348-49.8C170.352 50 185 52.5 185 52.5V84h-16.14C152.959 84 148 93.867 148 103.99V128h35.5l-5.675 37H148v89.445c61.192-9.602 108-62.556 108-126.445" fill="currentColor" />
|
|
58
|
+
</svg>
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Instagram brand glyph (mono, currentColor). */
|
|
63
|
+
export function BrandInstagram({ size = 16, className }: BrandIconProps) {
|
|
64
|
+
return (
|
|
65
|
+
<svg
|
|
66
|
+
width={size}
|
|
67
|
+
height={size}
|
|
68
|
+
viewBox="-5.12 -5.12 266.24 266.24"
|
|
69
|
+
className={className}
|
|
70
|
+
aria-hidden="true"
|
|
71
|
+
focusable="false"
|
|
72
|
+
>
|
|
73
|
+
<path d="M128 23.064c34.177 0 38.225.13 51.722.745c12.48.57 19.258 2.655 23.769 4.408c5.974 2.322 10.238 5.096 14.717 9.575s7.253 8.743 9.575 14.717c1.753 4.511 3.838 11.289 4.408 23.768c.615 13.498.745 17.546.745 51.723s-.13 38.226-.745 51.723c-.57 12.48-2.655 19.257-4.408 23.768c-2.322 5.974-5.096 10.239-9.575 14.718s-8.743 7.253-14.717 9.574c-4.511 1.753-11.289 3.839-23.769 4.408c-13.495.616-17.543.746-51.722.746s-38.228-.13-51.723-.746c-12.48-.57-19.257-2.655-23.768-4.408c-5.974-2.321-10.239-5.095-14.718-9.574c-4.479-4.48-7.253-8.744-9.574-14.718c-1.753-4.51-3.839-11.288-4.408-23.768c-.616-13.497-.746-17.545-.746-51.723s.13-38.225.746-51.722c.57-12.48 2.655-19.258 4.408-23.769c2.321-5.974 5.095-10.238 9.574-14.717c4.48-4.48 8.744-7.253 14.718-9.575c4.51-1.753 11.288-3.838 23.768-4.408c13.497-.615 17.545-.745 51.723-.745M128 0C93.237 0 88.878.147 75.226.77c-13.625.622-22.93 2.786-31.071 5.95c-8.418 3.271-15.556 7.648-22.672 14.764S9.991 35.738 6.72 44.155C3.555 52.297 1.392 61.602.77 75.226C.147 88.878 0 93.237 0 128s.147 39.122.77 52.774c.622 13.625 2.785 22.93 5.95 31.071c3.27 8.417 7.647 15.556 14.763 22.672s14.254 11.492 22.672 14.763c8.142 3.165 17.446 5.328 31.07 5.95c13.653.623 18.012.77 52.775.77s39.122-.147 52.774-.77c13.624-.622 22.929-2.785 31.07-5.95c8.418-3.27 15.556-7.647 22.672-14.763s11.493-14.254 14.764-22.672c3.164-8.142 5.328-17.446 5.95-31.07c.623-13.653.77-18.012.77-52.775s-.147-39.122-.77-52.774c-.622-13.624-2.786-22.929-5.95-31.07c-3.271-8.418-7.648-15.556-14.764-22.672S220.262 9.99 211.845 6.72c-8.142-3.164-17.447-5.328-31.071-5.95C167.122.147 162.763 0 128 0m0 62.27c-36.302 0-65.73 29.43-65.73 65.73s29.428 65.73 65.73 65.73c36.301 0 65.73-29.428 65.73-65.73c0-36.301-29.429-65.73-65.73-65.73m0 108.397c-23.564 0-42.667-19.103-42.667-42.667S104.436 85.333 128 85.333s42.667 19.103 42.667 42.667s-19.103 42.667-42.667 42.667m83.686-110.994c0 8.484-6.876 15.36-15.36 15.36s-15.36-6.876-15.36-15.36s6.877-15.36 15.36-15.36s15.36 6.877 15.36 15.36" fill="currentColor" />
|
|
74
|
+
</svg>
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Messenger brand glyph (mono, currentColor). */
|
|
79
|
+
export function BrandMessenger({ size = 16, className }: BrandIconProps) {
|
|
80
|
+
return (
|
|
81
|
+
<svg
|
|
82
|
+
width={size}
|
|
83
|
+
height={size}
|
|
84
|
+
viewBox="-5.12 -5.12 266.24 266.24"
|
|
85
|
+
className={className}
|
|
86
|
+
aria-hidden="true"
|
|
87
|
+
focusable="false"
|
|
88
|
+
>
|
|
89
|
+
<path d="M128,0c-72.106,0 -128,52.818 -128,124.16c0,37.317 15.293,69.562 40.2,91.835c2.09,1.871 3.352,4.493 3.438,7.298l0.697,22.77c0.223,7.262 7.724,11.988 14.37,9.054l25.406,-11.217c2.15335,-0.94916 4.56829,-1.12613 6.837,-0.501c11.675,3.21 24.1,4.92 37.052,4.92c72.106,0 128,-52.818 128,-124.16c0,-71.342 -55.894,-124.159 -128,-124.159 M51.137,160.47l37.6,-59.653c5.98,-9.49 18.788,-11.853 27.762,-5.123l29.905,22.43c2.744,2.05808 6.52006,2.04706 9.252,-0.027l40.388,-30.652c5.39,-4.091 12.428,2.36 8.82,8.085l-37.6,59.654c-5.981,9.489 -18.79,11.852 -27.763,5.122l-29.906,-22.43c-2.74359,-2.05707 -6.51846,-2.04605 -9.25,0.027l-40.39,30.652c-5.39,4.09 -12.427,-2.36 -8.818,-8.085" fill="currentColor" />
|
|
90
|
+
</svg>
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** LinkedIn brand glyph (mono, currentColor). */
|
|
95
|
+
export function BrandLinkedIn({ size = 16, className }: BrandIconProps) {
|
|
96
|
+
return (
|
|
97
|
+
<svg
|
|
98
|
+
width={size}
|
|
99
|
+
height={size}
|
|
100
|
+
viewBox="1.44 -0.56 29.12 29.12"
|
|
101
|
+
className={className}
|
|
102
|
+
aria-hidden="true"
|
|
103
|
+
focusable="false"
|
|
104
|
+
>
|
|
105
|
+
<path d="M8.268 28H2.463V9.306h5.805zM5.362 6.756C3.506 6.756 2 5.218 2 3.362a3.362 3.362 0 0 1 6.724 0c0 1.856-1.506 3.394-3.362 3.394M29.994 28h-5.792v-9.1c0-2.169-.044-4.95-3.018-4.95c-3.018 0-3.481 2.356-3.481 4.794V28h-5.799V9.306h5.567v2.55h.081c.775-1.469 2.668-3.019 5.492-3.019c5.875 0 6.955 3.869 6.955 8.894V28z" fill="currentColor" />
|
|
106
|
+
</svg>
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Nextdoor brand glyph (mono, currentColor). */
|
|
111
|
+
export function BrandNextdoor({ size = 16, className }: BrandIconProps) {
|
|
112
|
+
return (
|
|
113
|
+
<svg
|
|
114
|
+
width={size}
|
|
115
|
+
height={size}
|
|
116
|
+
viewBox="-1.28 5.29 66.56 53.4"
|
|
117
|
+
className={className}
|
|
118
|
+
aria-hidden="true"
|
|
119
|
+
focusable="false"
|
|
120
|
+
>
|
|
121
|
+
<path d="M0 25.993c.285-.085.56-.2.82-.343l9.538-5.894c.166-.125.325-.26.477-.4V6.708h10.168v6.524L32 6.574l32 19.6-5.265 8.622-5.475-3.338h-.172a.21.21 0 0 0 0 .114v25.58c-1.068.267-38.343.362-41.967.134V56.8q0-12.533 0-25.104l-.114-.23-5.76 3.32L3.1 31.373c-.916-1.488-1.908-2.976-2.766-4.464A1.91 1.91 0 0 0 0 26.508z" fill="currentColor" />
|
|
122
|
+
</svg>
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Pinterest brand glyph (mono, currentColor). */
|
|
127
|
+
export function BrandPinterest({ size = 16, className }: BrandIconProps) {
|
|
128
|
+
return (
|
|
129
|
+
<svg
|
|
130
|
+
width={size}
|
|
131
|
+
height={size}
|
|
132
|
+
viewBox="3.38 -0.22 25.25 32.44"
|
|
133
|
+
className={className}
|
|
134
|
+
aria-hidden="true"
|
|
135
|
+
focusable="false"
|
|
136
|
+
>
|
|
137
|
+
<path d="M16.75.406C10.337.406 4 4.681 4 11.6c0 4.4 2.475 6.9 3.975 6.9c.619 0 .975-1.725.975-2.212c0-.581-1.481-1.819-1.481-4.238c0-5.025 3.825-8.588 8.775-8.588c4.256 0 7.406 2.419 7.406 6.863c0 3.319-1.331 9.544-5.644 9.544c-1.556 0-2.888-1.125-2.888-2.737c0-2.363 1.65-4.65 1.65-7.088c0-4.137-5.869-3.387-5.869 1.613c0 1.05.131 2.212.6 3.169c-.863 3.713-2.625 9.244-2.625 13.069c0 1.181.169 2.344.281 3.525c.212.238.106.213.431.094c3.15-4.313 3.038-5.156 4.463-10.8c.769 1.463 2.756 2.25 4.331 2.25c6.637 0 9.619-6.469 9.619-12.3c0-6.206-5.363-10.256-11.25-10.256z" fill="currentColor" />
|
|
138
|
+
</svg>
|
|
139
|
+
)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Snapchat brand glyph (mono, currentColor). */
|
|
143
|
+
export function BrandSnapchat({ size = 16, className }: BrandIconProps) {
|
|
144
|
+
return (
|
|
145
|
+
<svg
|
|
146
|
+
width={size}
|
|
147
|
+
height={size}
|
|
148
|
+
viewBox="208.68 112.18 391.9 368.85"
|
|
149
|
+
className={className}
|
|
150
|
+
aria-hidden="true"
|
|
151
|
+
focusable="false"
|
|
152
|
+
>
|
|
153
|
+
<path d="M407.001,473.488c-1.068,0-2.087-0.039-2.862-0.076c-0.615,0.053-1.25,0.076-1.886,0.076 c-22.437,0-37.439-10.607-50.678-19.973c-9.489-6.703-18.438-13.031-28.922-14.775c-5.149-0.854-10.271-1.287-15.22-1.287 c-8.917,0-15.964,1.383-21.109,2.389c-3.166,0.617-5.896,1.148-8.006,1.148c-2.21,0-4.895-0.49-6.014-4.311 c-0.887-3.014-1.523-5.934-2.137-8.746c-1.536-7.027-2.65-11.316-5.281-11.723c-28.141-4.342-44.768-10.738-48.08-18.484 c-0.347-0.814-0.541-1.633-0.584-2.443c-0.129-2.309,1.501-4.334,3.777-4.711c22.348-3.68,42.219-15.492,59.064-35.119 c13.049-15.195,19.457-29.713,20.145-31.316c0.03-0.072,0.065-0.148,0.101-0.217c3.247-6.588,3.893-12.281,1.926-16.916 c-3.626-8.551-15.635-12.361-23.58-14.882c-1.976-0.625-3.845-1.217-5.334-1.808c-7.043-2.782-18.626-8.66-17.083-16.773 c1.124-5.916,8.949-10.036,15.273-10.036c1.756,0,3.312,0.308,4.622,0.923c7.146,3.348,13.575,5.045,19.104,5.045 c6.876,0,10.197-2.618,11-3.362c-0.198-3.668-0.44-7.546-0.674-11.214c0-0.004-0.005-0.048-0.005-0.048 c-1.614-25.675-3.627-57.627,4.546-75.95c24.462-54.847,76.339-59.112,91.651-59.112c0.408,0,6.674-0.062,6.674-0.062 c0.283-0.005,0.59-0.009,0.908-0.009c15.354,0,67.339,4.27,91.816,59.15c8.173,18.335,6.158,50.314,4.539,76.016l-0.076,1.23 c-0.222,3.49-0.427,6.793-0.6,9.995c0.756,0.696,3.795,3.096,9.978,3.339c5.271-0.202,11.328-1.891,17.998-5.014 c2.062-0.968,4.345-1.169,5.895-1.169c2.343,0,4.727,0.456,6.714,1.285l0.106,0.041c5.66,2.009,9.367,6.024,9.447,10.242 c0.071,3.932-2.851,9.809-17.223,15.485c-1.472,0.583-3.35,1.179-5.334,1.808c-7.952,2.524-19.951,6.332-23.577,14.878 c-1.97,4.635-1.322,10.326,1.926,16.912c0.036,0.072,0.067,0.145,0.102,0.221c1,2.344,25.205,57.535,79.209,66.432 c2.275,0.379,3.908,2.406,3.778,4.711c-0.048,0.828-0.248,1.656-0.598,2.465c-3.289,7.703-19.915,14.09-48.064,18.438 c-2.642,0.408-3.755,4.678-5.277,11.668c-0.63,2.887-1.271,5.717-2.146,8.691c-0.819,2.797-2.641,4.164-5.567,4.164h-0.441 c-1.905,0-4.604-0.346-8.008-1.012c-5.95-1.158-12.623-2.236-21.109-2.236c-4.948,0-10.069,0.434-15.224,1.287 c-10.473,1.744-19.421,8.062-28.893,14.758C444.443,462.88,429.436,473.488,407.001,473.488" fill="currentColor" />
|
|
154
|
+
</svg>
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** TikTok brand glyph (mono, currentColor). */
|
|
159
|
+
export function BrandTikTok({ size = 16, className }: BrandIconProps) {
|
|
160
|
+
return (
|
|
161
|
+
<svg
|
|
162
|
+
width={size}
|
|
163
|
+
height={size}
|
|
164
|
+
viewBox="-5.79 -5.79 267.56 300.84"
|
|
165
|
+
className={className}
|
|
166
|
+
aria-hidden="true"
|
|
167
|
+
focusable="false"
|
|
168
|
+
>
|
|
169
|
+
<path d="M189.722,104.42243l0,96.99357c0,48.525 -39.357,87.857 -87.905,87.857c-24.62405,0 -46.88248,-10.1174 -62.84421,-26.42813c-0.00093,-0.00062 -0.00186,-0.00125 -0.00279,-0.00187c-0.00254,-0.00259 -0.00508,-0.00519 -0.00761,-0.00778c-23.49917,-15.76977 -38.96239,-42.57494 -38.96239,-72.99022c0,-48.518 39.355,-87.852 87.905,-87.852c4.03423,-0.001 8.06385,0.2723 12.061,0.818l0,10.76898c4.64967,-0.09894 9.30133,0.17007 13.911,0.80102v48.593c-3.94487,-1.25017 -8.05877,-1.88528 -12.197,-1.883c-22.209,0 -40.21,17.994 -40.21,40.186c-0.00942,6.11418 1.38223,12.11209 4.025,17.54894l-17.941,-29.11894c0,-6.79366 1.68644,-13.19354 4.66379,-18.8042c-2.97797,5.61107 -4.66479,12.01139 -4.66479,18.8052c0,15.692 8.998,29.28 22.118,35.898c0.00103,0.00136 0.00207,0.00271 0.0031,0.00407c5.60932,2.82644 11.80469,4.29426 18.0859,4.28493c22.162,0 40.132,-17.92 40.208,-40.05v-189.845h47.834v6.113c0.1689,1.82808 0.41177,3.64857 0.728,5.457l-34.643,0.001v189.844c-0.078,22.132 -18.05,40.05 -40.21,40.05c-5.67604,0 -11.07698,-1.17525 -15.97293,-3.29552c4.89621,2.12093 10.2975,3.29652 15.97393,3.29652c22.161,0 40.131,-17.92 40.21,-40.052v-189.843l47.832,0v6.113c1.356,14.892 7.624,28.362 17.17,38.785h-0.001c10.55395,6.58342 22.74707,10.06504 35.186,10.047v10.116c4.57579,0.96983 9.24056,1.45782 13.918,1.456v47.53c-24.71212,0 -47.59337,-7.85144 -66.271,-21.19557zM99.964,151.40168c0.00033,0.00011 0.00067,0.00021 0.001,0.00032l0,-37.82098c-0.00033,0.00001 -0.00067,0.00001 -0.001,0.00002zM242.075,114.045v-0.001c-24.712,0 -47.589,-7.851 -66.272,-21.195v0.001c18.682,13.344 41.561,21.195 66.272,21.195zM69.673,225.607c-1.62409,-2.12792 -3.02095,-4.40072 -4.178,-6.78106zM228.74787,72.27412c3.67381,1.65002 7.52454,2.97698 11.51604,3.94463c-3.9686,-0.96405 -7.82207,-2.2874 -11.51604,-3.94463z" fill="currentColor" />
|
|
170
|
+
</svg>
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Keyed map for registry-style resolution (e.g. the Social Spec tactic icons). */
|
|
175
|
+
export const BRAND_ICONS = {
|
|
176
|
+
meta: BrandMeta,
|
|
177
|
+
facebook: BrandFacebook,
|
|
178
|
+
instagram: BrandInstagram,
|
|
179
|
+
messenger: BrandMessenger,
|
|
180
|
+
linkedin: BrandLinkedIn,
|
|
181
|
+
nextdoor: BrandNextdoor,
|
|
182
|
+
pinterest: BrandPinterest,
|
|
183
|
+
snapchat: BrandSnapchat,
|
|
184
|
+
tiktok: BrandTikTok,
|
|
185
|
+
} as const
|
|
186
|
+
|
|
187
|
+
export type BrandIconKey = keyof typeof BRAND_ICONS
|