@thewhatmatters/wmds 0.2.0 → 0.4.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/CHANGELOG.md +29 -0
- package/CONSUMING.md +5 -4
- package/README.md +3 -3
- package/dist/components/molecules/PromptBar/PromptBar.d.ts +11 -1
- package/dist/components/molecules/PromptBar/PromptBar.d.ts.map +1 -1
- package/dist/components/molecules/PromptBar/PromptBar.js +54 -43
- package/dist/components/molecules/PromptBar/promptBarStyles.d.ts +15 -0
- package/dist/components/molecules/PromptBar/promptBarStyles.d.ts.map +1 -1
- package/dist/components/molecules/PromptBar/promptBarStyles.js +5 -2
- package/dist/components/organisms/ChatDock/ChatDock.d.ts +93 -0
- package/dist/components/organisms/ChatDock/ChatDock.d.ts.map +1 -0
- package/dist/components/organisms/ChatDock/ChatDock.js +255 -0
- package/dist/components/organisms/ChatDock/chatDockStyles.d.ts +48 -0
- package/dist/components/organisms/ChatDock/chatDockStyles.d.ts.map +1 -0
- package/dist/components/organisms/ChatDock/chatDockStyles.js +15 -0
- package/dist/components/organisms/Dialog/AlertDialog.js +11 -11
- package/dist/components/organisms/Dialog/Dialog.js +44 -44
- package/dist/components/organisms/Sheet/Sheet.js +38 -38
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -28
- package/dist/styles.css +1 -1
- package/docs/component-contracts.md +1 -1
- package/docs/components.md +7 -6
- package/docs/exports.json +14 -14
- package/docs/patterns/components-avatar--dropdown-row.tsx +1 -1
- package/docs/patterns/components-badge--with-avatar.tsx +1 -1
- package/docs/patterns/components-button-button--destructive-action.tsx +1 -1
- package/docs/patterns/components-button-button--ghost-action.tsx +1 -1
- package/docs/patterns/components-button-button--inverse-action.tsx +1 -1
- package/docs/patterns/components-button-button--link-render.tsx +1 -1
- package/docs/patterns/components-button-button--multi-state-badge.tsx +1 -1
- package/docs/patterns/components-button-button--outline-mono.tsx +1 -1
- package/docs/patterns/components-button-button--primary-action.tsx +1 -1
- package/docs/patterns/components-button-button--row-hug.tsx +1 -1
- package/docs/patterns/components-button-button--row-layout.tsx +1 -1
- package/docs/patterns/components-button-button--secondary-action.tsx +1 -1
- package/docs/patterns/components-button-button--suggestion-pills.tsx +1 -1
- package/docs/patterns/components-button-button--with-count.tsx +1 -1
- package/docs/patterns/components-button-button--with-icon.tsx +1 -1
- package/docs/patterns/components-button-floatingactionbutton--vertical-actions.tsx +1 -1
- package/docs/patterns/components-calembed--pattern-skip-in-footer.tsx +1 -1
- package/docs/patterns/components-card-card--body-slot-occupancy-history.tsx +1 -1
- package/docs/patterns/components-card-card--headerless-media.tsx +1 -1
- package/docs/patterns/components-card-card--outlined-layout.tsx +1 -1
- package/docs/patterns/components-card-card--pinned-height.tsx +1 -1
- package/docs/patterns/components-card-selectablecard--selectable-cards-pattern.tsx +1 -1
- package/docs/patterns/components-chart--audience-activity-heatmap.tsx +1 -1
- package/docs/patterns/components-chart--cartesian-no-data-gaps.tsx +1 -1
- package/docs/patterns/components-chart--occupancy-history-in-card.tsx +1 -1
- package/docs/patterns/components-chart--occupancy-in-card.tsx +1 -1
- package/docs/patterns/components-chart--post-reach-distribution.tsx +1 -1
- package/docs/patterns/components-chart--ranked-audience-breakdown.tsx +1 -1
- package/docs/patterns/components-chart--unit-grid-composition.tsx +1 -1
- package/docs/patterns/components-chatdock--pattern-chat-dock.tsx +68 -0
- package/docs/patterns/components-chatqa--pattern-qa-with-reply.tsx +1 -1
- package/docs/patterns/components-checkbox-checkbox--default.tsx +1 -1
- package/docs/patterns/components-checkbox-checkbox--with-description.tsx +1 -1
- package/docs/patterns/components-checkbox-checkboxgroup--default.tsx +1 -1
- package/docs/patterns/components-dialog--alert-dialog-destructive.tsx +1 -1
- package/docs/patterns/components-dialog--default-dialog.tsx +1 -1
- package/docs/patterns/components-dialog--form-dialog.tsx +1 -1
- package/docs/patterns/components-displaycontrols--page-utilities.tsx +1 -1
- package/docs/patterns/components-field--horizontal-input.tsx +1 -1
- package/docs/patterns/components-field--horizontal-text-area.tsx +1 -1
- package/docs/patterns/components-field--multi-control.tsx +1 -1
- package/docs/patterns/components-footerreveal--marketing-hero-pattern.tsx +1 -1
- package/docs/patterns/components-footerreveal--marketing-hero-ruled-pattern.tsx +1 -1
- package/docs/patterns/components-footerreveal--marketing-hero-with-gallery-intro.tsx +1 -1
- package/docs/patterns/components-footerreveal--marketing-page-pattern.tsx +1 -1
- package/docs/patterns/components-footerreveal--ruled-grid-footer-pattern.tsx +1 -1
- package/docs/patterns/components-footerreveal--sequenced-marketing-hero.tsx +1 -1
- package/docs/patterns/components-herointro--two-line-intro.tsx +1 -1
- package/docs/patterns/components-herotilestack--marketing-hero-pattern.tsx +1 -1
- package/docs/patterns/components-herotilestack--marketing-hero-text-sequence.tsx +1 -1
- package/docs/patterns/components-herotilestack--marketing-hero-with-gallery-intro.tsx +1 -1
- package/docs/patterns/components-intakeconfirmation--booked-pattern.tsx +1 -1
- package/docs/patterns/components-intakeconfirmation--emailed-pattern.tsx +1 -1
- package/docs/patterns/components-intakeform--about-you-pattern.tsx +1 -1
- package/docs/patterns/components-intakemodal--intake-modal-pattern.tsx +1 -1
- package/docs/patterns/components-kbd--command-row.tsx +1 -1
- package/docs/patterns/components-kbd--numbered-choices.tsx +1 -1
- package/docs/patterns/components-kbd--shortcut.tsx +1 -1
- package/docs/patterns/components-moremenu--action-menu.tsx +1 -1
- package/docs/patterns/components-moremenu--in-card-header.tsx +1 -1
- package/docs/patterns/components-navlist--side-nav-settings.tsx +1 -1
- package/docs/patterns/components-pageheader--app-header-pattern.tsx +1 -1
- package/docs/patterns/components-pageheader--page-section-header.tsx +1 -1
- package/docs/patterns/components-pageheader--toolbar-header.tsx +1 -1
- package/docs/patterns/components-panel--end-detail-rail.tsx +1 -1
- package/docs/patterns/components-pillgroup--pill-group-pattern.tsx +1 -1
- package/docs/patterns/components-promptbar--pattern-prompt-bar.tsx +1 -1
- package/docs/patterns/components-radio-radiogroup--default.tsx +1 -1
- package/docs/patterns/components-scrollhorizontal--project-gallery-pattern.tsx +1 -1
- package/docs/patterns/components-scrollhorizontal--with-intro.tsx +1 -1
- package/docs/patterns/components-sheet--bottom-sheet.tsx +1 -1
- package/docs/patterns/components-sheet--end-sheet.tsx +1 -1
- package/docs/patterns/components-sitenav--brand-and-cta.tsx +1 -1
- package/docs/patterns/components-sitenav--centered-links.tsx +1 -1
- package/docs/patterns/components-sitenav--compact.tsx +1 -1
- package/docs/patterns/components-sitenav--marketing-header.tsx +1 -1
- package/docs/patterns/components-sitenav--mega-menu.tsx +1 -1
- package/docs/patterns/components-stat--insights-row.tsx +1 -1
- package/docs/patterns/components-stepprogress--step-progress-pattern.tsx +1 -1
- package/docs/patterns/components-switch--default.tsx +1 -1
- package/docs/patterns/components-switch--settings-row.tsx +1 -1
- package/docs/patterns/components-tab--settings-pages.tsx +1 -1
- package/docs/patterns/components-taskrows--action-details.tsx +1 -1
- package/docs/patterns/components-taskrows--capsules.tsx +1 -1
- package/docs/patterns/components-taskrows--card-with-status-rows.tsx +1 -1
- package/docs/patterns/components-taskrows--controlled-expand.tsx +1 -1
- package/docs/patterns/components-taskrows--custom-slots.tsx +1 -1
- package/docs/patterns/components-taskrows--status-list.tsx +1 -1
- package/docs/patterns/components-taskrows--tag-chips.tsx +1 -1
- package/docs/patterns/components-textlink--external-destination.tsx +1 -1
- package/docs/patterns/components-textlink--inline-prose.tsx +1 -1
- package/docs/patterns/components-textlink--supporting-copy.tsx +1 -1
- package/docs/patterns/components-textsequence--default.tsx +1 -1
- package/docs/patterns/components-textsequence--reduced-motion.tsx +1 -1
- package/docs/patterns/components-textsequence--shapes.tsx +1 -1
- package/docs/patterns/components-toast--stacked-notifications.tsx +1 -1
- package/docs/patterns/components-tooltip--controlled.tsx +1 -1
- package/docs/patterns/components-tooltip--disabled-control.tsx +1 -1
- package/docs/patterns/components-tooltip--icon-action.tsx +1 -1
- package/docs/patterns/components-tooltip--keyboard-shortcut.tsx +1 -1
- package/docs/patterns/components-tooltip--placement.tsx +1 -1
- package/docs/patterns/foundations-scroll-fade--horizontal-rail.tsx +1 -1
- package/docs/patterns/foundations-scroll-fade--overflowing-list.tsx +1 -1
- package/docs/patterns/guides-chart-explorations--comparison-gallery.tsx +1 -1
- package/docs/patterns/guides-overlay-flows--notification-preferences.tsx +1 -1
- package/docs/patterns/guides-profile-typography--editorial-profile.tsx +1 -1
- package/docs/patterns/index.json +9 -161
- package/package.json +1 -1
- package/docs/patterns/components-promptbar--pattern-marketing-composer.tsx +0 -37
- package/docs/patterns/sites-pitchkit-account-settings--account-settings-owner.tsx +0 -265
- package/docs/patterns/sites-pitchkit-creator-identity--creator-identity-owner-settings.tsx +0 -154
- package/docs/patterns/sites-pitchkit-creator-identity--creator-identity-public.tsx +0 -96
- package/docs/patterns/sites-pitchkit-insights-and-kit--creator-insights-loading.tsx +0 -1006
- package/docs/patterns/sites-pitchkit-insights-and-kit--creator-insights.tsx +0 -1031
- package/docs/patterns/sites-pitchkit-insights-and-kit--insufficient-audience-data.tsx +0 -1015
- package/docs/patterns/sites-pitchkit-insights-and-kit--insufficient-reach-and-audience-data.tsx +0 -1017
- package/docs/patterns/sites-pitchkit-insights-and-kit--insufficient-reach-data.tsx +0 -1033
- package/docs/patterns/sites-pitchkit-insights-and-kit--owner-pitch-kit.tsx +0 -838
- package/docs/patterns/sites-pitchkit-insights-and-kit--shareable-pitch-kit.tsx +0 -608
- package/docs/patterns/sites-pitchkit-intro--intro-owner.tsx +0 -161
- package/docs/patterns/sites-pitchkit-intro--intro-public.tsx +0 -111
- package/docs/patterns/sites-pitchkit-past-brands--past-brands-owner.tsx +0 -611
- package/docs/patterns/sites-pitchkit-past-brands--past-brands-public.tsx +0 -299
- package/docs/patterns/sites-pitchkit-theme-picker--theme-picker-owner.tsx +0 -832
- package/docs/patterns/sites-whatmatters-intake--start-a-project-pattern.tsx +0 -234
- package/docs/patterns/sites-whatmatters-marketing-landing--marketing-landing.tsx +0 -87
- package/docs/patterns/sites-whatmatters-prompt-chat--landing-to-chat.tsx +0 -899
- package/docs/patterns/sites-whatmatters-rfp-submitted--rfp-submitted.tsx +0 -302
|
@@ -1,608 +0,0 @@
|
|
|
1
|
-
// @thewhatmatters/wmds@0.2.0 · Pattern — shareable PitchKit
|
|
2
|
-
// Storybook: Sites/PitchKit/Insights and kit → Pattern — shareable PitchKit (?path=/story/sites-pitchkit-insights-and-kit--shareable-pitch-kit)
|
|
3
|
-
// Show code — copy verbatim and keep this header; upgrades find pasted patterns by it.
|
|
4
|
-
|
|
5
|
-
import { useLayoutEffect, useRef, useState, type CSSProperties } from "react";
|
|
6
|
-
import {
|
|
7
|
-
Avatar,
|
|
8
|
-
Badge,
|
|
9
|
-
Button,
|
|
10
|
-
Card,
|
|
11
|
-
Chart,
|
|
12
|
-
Chip,
|
|
13
|
-
Stat,
|
|
14
|
-
TextLink,
|
|
15
|
-
cardSubtitleClasses,
|
|
16
|
-
cardTitleClasses,
|
|
17
|
-
chartSeriesConfigFromKeys,
|
|
18
|
-
type ChartCartesianPoint,
|
|
19
|
-
type ChartRankedBarItem,
|
|
20
|
-
} from "@thewhatmatters/wmds";
|
|
21
|
-
|
|
22
|
-
const reachConfig = chartSeriesConfigFromKeys([
|
|
23
|
-
{ key: "typical", label: "Typical reach" },
|
|
24
|
-
{ key: "reach", label: "Daily reach" },
|
|
25
|
-
]);
|
|
26
|
-
|
|
27
|
-
const compactNumber = new Intl.NumberFormat("en", {
|
|
28
|
-
notation: "compact",
|
|
29
|
-
maximumFractionDigits: 1,
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
export interface PitchKitCreatorIdentity {
|
|
35
|
-
displayName?: string;
|
|
36
|
-
/** Without the @ — shown as @handle, shared as /k/[handle]. */
|
|
37
|
-
handle: string;
|
|
38
|
-
profilePictureUrl?: string;
|
|
39
|
-
followersCount?: number;
|
|
40
|
-
professionalAccount?: "Business" | "Creator";
|
|
41
|
-
connected?: boolean;
|
|
42
|
-
lastSyncedLabel?: string;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function CreatorIdentityStrip({
|
|
46
|
-
identity,
|
|
47
|
-
nameAs = "h1",
|
|
48
|
-
showProfessionalChip = false,
|
|
49
|
-
}: {
|
|
50
|
-
identity: PitchKitCreatorIdentity;
|
|
51
|
-
nameAs?: "h1" | "p";
|
|
52
|
-
showProfessionalChip?: boolean;
|
|
53
|
-
}) {
|
|
54
|
-
const NameTag = nameAs;
|
|
55
|
-
const avatarName = identity.displayName ?? identity.handle;
|
|
56
|
-
const handleLabel = `@${identity.handle}`;
|
|
57
|
-
const followerLabel =
|
|
58
|
-
identity.followersCount == null
|
|
59
|
-
? null
|
|
60
|
-
: `${compactNumber.format(identity.followersCount)} followers`;
|
|
61
|
-
const meta = [handleLabel, followerLabel].filter(Boolean).join(" · ");
|
|
62
|
-
const showTitleRow =
|
|
63
|
-
identity.displayName != null ||
|
|
64
|
-
(showProfessionalChip && identity.professionalAccount != null);
|
|
65
|
-
|
|
66
|
-
return (
|
|
67
|
-
<div className="flex flex-wrap items-center gap-4">
|
|
68
|
-
<Avatar
|
|
69
|
-
name={avatarName}
|
|
70
|
-
src={identity.profilePictureUrl}
|
|
71
|
-
size="lg"
|
|
72
|
-
/>
|
|
73
|
-
<div className="flex min-w-0 flex-col gap-1">
|
|
74
|
-
{showTitleRow ? (
|
|
75
|
-
<div className="flex flex-wrap items-center gap-2">
|
|
76
|
-
{identity.displayName != null ? (
|
|
77
|
-
<NameTag className="type-heading-2 text-fg">
|
|
78
|
-
{identity.displayName}
|
|
79
|
-
</NameTag>
|
|
80
|
-
) : null}
|
|
81
|
-
{showProfessionalChip && identity.professionalAccount != null ? (
|
|
82
|
-
<Chip readOnly size="sm">
|
|
83
|
-
{identity.professionalAccount}
|
|
84
|
-
</Chip>
|
|
85
|
-
) : null}
|
|
86
|
-
</div>
|
|
87
|
-
) : null}
|
|
88
|
-
<p className="type-body text-fg text-muted">{meta}</p>
|
|
89
|
-
</div>
|
|
90
|
-
</div>
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
export interface PitchKitPost {
|
|
96
|
-
id: string;
|
|
97
|
-
publishedAt: string;
|
|
98
|
-
imageUrl: string;
|
|
99
|
-
imageAlt: string;
|
|
100
|
-
saves: number;
|
|
101
|
-
reach: number;
|
|
102
|
-
likes: number;
|
|
103
|
-
comments: number;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export interface PitchKitContact {
|
|
107
|
-
email: string;
|
|
108
|
-
websiteHref: string;
|
|
109
|
-
websiteLabel: string;
|
|
110
|
-
location: string;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/** "insufficient" when the 30-day reach series cannot be plotted. */
|
|
114
|
-
export type PitchKitReachState = "resolved" | "insufficient";
|
|
115
|
-
|
|
116
|
-
export interface PitchKitPageData {
|
|
117
|
-
identity: PitchKitCreatorIdentity;
|
|
118
|
-
intro: string;
|
|
119
|
-
posts: PitchKitPost[];
|
|
120
|
-
contact: PitchKitContact;
|
|
121
|
-
brands: PitchKitPastBrand[];
|
|
122
|
-
countries: ChartRankedBarItem[];
|
|
123
|
-
reachData: ChartCartesianPoint[];
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
interface ShareablePitchKitProps extends PitchKitPageData {
|
|
127
|
-
reachState?: PitchKitReachState;
|
|
128
|
-
/** Unsigned visitors who are not the kit owner only. */
|
|
129
|
-
showCreateBand?: boolean;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function pitchKitIntroIsEmpty(intro: string) {
|
|
133
|
-
return intro.trim().length === 0;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function PublicIntro({ intro }: { intro: string }) {
|
|
137
|
-
if (pitchKitIntroIsEmpty(intro)) return null;
|
|
138
|
-
return <p className="type-body text-fg text-fg">{intro}</p>;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
export interface PitchKitPastBrand {
|
|
142
|
-
id: string;
|
|
143
|
-
name: string;
|
|
144
|
-
/** Curated mark key — missing or unknown keys use a letter Avatar. */
|
|
145
|
-
logo_key?: string;
|
|
146
|
-
result_label?: string;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const PITCHKIT_BRAND_RESULT_MAX = 24;
|
|
150
|
-
const PITCHKIT_BRAND_LOGO_LETTER = "letter";
|
|
151
|
-
|
|
152
|
-
const pitchKitBrandLogoKeys = [
|
|
153
|
-
"nike", "adidas", "apple", "google", "meta", "amazon", "spotify", "netflix",
|
|
154
|
-
"sephora", "glossier", "nordstrom", "target", "walmart", "starbucks",
|
|
155
|
-
"coca-cola", "pepsi", "samsung", "microsoft", "adobe", "shopify",
|
|
156
|
-
"uber", "airbnb", "disney", "lululemon", "reebok", "puma",
|
|
157
|
-
"dior", "chanel", "bmw", "ford", "chase", "visa",
|
|
158
|
-
];
|
|
159
|
-
|
|
160
|
-
const pastBrandLogoKeySet = new Set(pitchKitBrandLogoKeys);
|
|
161
|
-
|
|
162
|
-
function resolvePastBrandLogoKey(logoKey: string | null | undefined) {
|
|
163
|
-
if (logoKey == null || logoKey === "" || logoKey === PITCHKIT_BRAND_LOGO_LETTER) {
|
|
164
|
-
return undefined;
|
|
165
|
-
}
|
|
166
|
-
return pastBrandLogoKeySet.has(logoKey) ? logoKey : undefined;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function pastBrandLogoMonogram(logoKey: string) {
|
|
170
|
-
const parts = logoKey.split("-");
|
|
171
|
-
if (parts.length > 1) {
|
|
172
|
-
return parts.map((part) => part.charAt(0).toUpperCase()).join("").slice(0, 2);
|
|
173
|
-
}
|
|
174
|
-
return logoKey.slice(0, 1).toUpperCase();
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function normalizePastBrandResult(value: string | null | undefined) {
|
|
178
|
-
const trimmed = value?.trim() ?? "";
|
|
179
|
-
if (trimmed.length === 0) return undefined;
|
|
180
|
-
return trimmed.slice(0, PITCHKIT_BRAND_RESULT_MAX);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
function BrandMark({ name, logoKey }: { name: string; logoKey?: string }) {
|
|
184
|
-
const resolved = resolvePastBrandLogoKey(logoKey);
|
|
185
|
-
if (resolved == null) {
|
|
186
|
-
return <Avatar name={name} size="sm" />;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
return (
|
|
190
|
-
<span className="flex size-6 shrink-0 items-center justify-center rounded-full bg-fg type-supporting text-muted font-medium uppercase leading-none text-bg" role="img" aria-label={name}>
|
|
191
|
-
<span aria-hidden>{pastBrandLogoMonogram(resolved)}</span>
|
|
192
|
-
</span>
|
|
193
|
-
);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
function BrandResultChip({ label }: { label?: string }) {
|
|
197
|
-
const result = normalizePastBrandResult(label);
|
|
198
|
-
if (result == null) return null;
|
|
199
|
-
return <Chip readOnly size="sm">{result}</Chip>;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
function BrandLockup({ brand }: { brand: PitchKitPastBrand }) {
|
|
203
|
-
return (
|
|
204
|
-
<div className="flex min-w-0 items-center gap-3">
|
|
205
|
-
<BrandMark name={brand.name} logoKey={brand.logo_key} />
|
|
206
|
-
<h3 className="type-label text-fg">{brand.name}</h3>
|
|
207
|
-
<BrandResultChip label={brand.result_label} />
|
|
208
|
-
</div>
|
|
209
|
-
);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
function PastBrandCard({ brand, className }: { brand: PitchKitPastBrand; className: string }) {
|
|
213
|
-
return (
|
|
214
|
-
<Card variant="outlined" shape="rounded" className={className}>
|
|
215
|
-
<Card.Header start={<BrandLockup brand={brand} />} />
|
|
216
|
-
</Card>
|
|
217
|
-
);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
function PastBrandsRail({ brands }: { brands: readonly PitchKitPastBrand[] }) {
|
|
221
|
-
const hostRef = useRef<HTMLDivElement>(null);
|
|
222
|
-
const measureRef = useRef<HTMLUListElement>(null);
|
|
223
|
-
const [overflows, setOverflows] = useState(false);
|
|
224
|
-
const [reduceMotion, setReduceMotion] = useState(false);
|
|
225
|
-
|
|
226
|
-
useLayoutEffect(() => {
|
|
227
|
-
const media = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
228
|
-
const updateMotion = () => setReduceMotion(media.matches);
|
|
229
|
-
updateMotion();
|
|
230
|
-
media.addEventListener("change", updateMotion);
|
|
231
|
-
return () => media.removeEventListener("change", updateMotion);
|
|
232
|
-
}, []);
|
|
233
|
-
|
|
234
|
-
useLayoutEffect(() => {
|
|
235
|
-
const host = hostRef.current;
|
|
236
|
-
const measure = measureRef.current;
|
|
237
|
-
if (host == null || measure == null) return;
|
|
238
|
-
|
|
239
|
-
const update = () => {
|
|
240
|
-
setOverflows(measure.scrollWidth > host.clientWidth + 1);
|
|
241
|
-
};
|
|
242
|
-
update();
|
|
243
|
-
const observer = new ResizeObserver(update);
|
|
244
|
-
observer.observe(host);
|
|
245
|
-
observer.observe(measure);
|
|
246
|
-
return () => observer.disconnect();
|
|
247
|
-
}, [brands]);
|
|
248
|
-
|
|
249
|
-
const duration = Math.max(16, brands.length * 4);
|
|
250
|
-
const showMarquee = overflows && !reduceMotion;
|
|
251
|
-
|
|
252
|
-
return (
|
|
253
|
-
<div
|
|
254
|
-
ref={hostRef}
|
|
255
|
-
className="relative col-span-full min-w-0"
|
|
256
|
-
data-overflow={showMarquee ? "true" : "false"}
|
|
257
|
-
>
|
|
258
|
-
<ul ref={measureRef} className="pointer-events-none invisible absolute flex w-max items-center gap-3" aria-hidden>
|
|
259
|
-
{brands.map((brand) => (
|
|
260
|
-
<li key={`${brand.id}-measure`}>
|
|
261
|
-
<PastBrandCard brand={brand} className="w-max max-w-full shrink-0" />
|
|
262
|
-
</li>
|
|
263
|
-
))}
|
|
264
|
-
</ul>
|
|
265
|
-
{reduceMotion ? (
|
|
266
|
-
<ul className="flex min-w-0 flex-wrap items-center gap-3" aria-label="Past brands">
|
|
267
|
-
{brands.map((brand) => (
|
|
268
|
-
<li key={brand.id}>
|
|
269
|
-
<PastBrandCard brand={brand} className="w-max max-w-full shrink-0" />
|
|
270
|
-
</li>
|
|
271
|
-
))}
|
|
272
|
-
</ul>
|
|
273
|
-
) : showMarquee ? (
|
|
274
|
-
<>
|
|
275
|
-
<ul className="sr-only" aria-label="Past brands">
|
|
276
|
-
{brands.map((brand) => {
|
|
277
|
-
const result = normalizePastBrandResult(brand.result_label);
|
|
278
|
-
return (
|
|
279
|
-
<li key={brand.id}>
|
|
280
|
-
{result == null ? brand.name : `${brand.name}, ${result}`}
|
|
281
|
-
</li>
|
|
282
|
-
);
|
|
283
|
-
})}
|
|
284
|
-
</ul>
|
|
285
|
-
<div className="group overflow-hidden" aria-hidden>
|
|
286
|
-
<div
|
|
287
|
-
className="marquee-track flex w-max items-center"
|
|
288
|
-
style={{ "--marquee-duration": `${duration}s` } as CSSProperties}
|
|
289
|
-
>
|
|
290
|
-
<ul className="flex min-w-0 items-center gap-3">
|
|
291
|
-
{brands.map((brand) => (
|
|
292
|
-
<li key={`${brand.id}-loop-a`}>
|
|
293
|
-
<PastBrandCard brand={brand} className="w-max max-w-full shrink-0" />
|
|
294
|
-
</li>
|
|
295
|
-
))}
|
|
296
|
-
</ul>
|
|
297
|
-
<ul className="flex min-w-0 items-center gap-3">
|
|
298
|
-
{brands.map((brand) => (
|
|
299
|
-
<li key={`${brand.id}-loop-b`}>
|
|
300
|
-
<PastBrandCard brand={brand} className="w-max max-w-full shrink-0" />
|
|
301
|
-
</li>
|
|
302
|
-
))}
|
|
303
|
-
</ul>
|
|
304
|
-
</div>
|
|
305
|
-
</div>
|
|
306
|
-
</>
|
|
307
|
-
) : (
|
|
308
|
-
<ul className="flex min-w-0 items-center gap-3" aria-label="Past brands">
|
|
309
|
-
{brands.map((brand) => (
|
|
310
|
-
<li key={brand.id}>
|
|
311
|
-
<PastBrandCard brand={brand} className="w-max max-w-full shrink-0" />
|
|
312
|
-
</li>
|
|
313
|
-
))}
|
|
314
|
-
</ul>
|
|
315
|
-
)}
|
|
316
|
-
</div>
|
|
317
|
-
);
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
export function PublicPastBrands({ brands }: { brands: readonly PitchKitPastBrand[] }) {
|
|
321
|
-
if (brands.length === 0) return null;
|
|
322
|
-
|
|
323
|
-
return (
|
|
324
|
-
<section className="band min-w-0 gap-y-4">
|
|
325
|
-
<div className="col-span-full flex flex-wrap items-end justify-between gap-3">
|
|
326
|
-
<h2 className={cardTitleClasses}>Past brands</h2>
|
|
327
|
-
</div>
|
|
328
|
-
<PastBrandsRail brands={brands} />
|
|
329
|
-
</section>
|
|
330
|
-
);
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
function PublicCreatePitchkitBand() {
|
|
335
|
-
return (
|
|
336
|
-
<Card
|
|
337
|
-
variant="outlined"
|
|
338
|
-
shape="rounded"
|
|
339
|
-
bodyTerminal
|
|
340
|
-
className="col-span-full"
|
|
341
|
-
>
|
|
342
|
-
<Card.Header
|
|
343
|
-
start={<h2 className={cardTitleClasses}>Create your Pitchkit</h2>}
|
|
344
|
-
/>
|
|
345
|
-
<Card.Body>
|
|
346
|
-
<div className="flex min-w-0 flex-col gap-4 px-3.5 py-[16px]">
|
|
347
|
-
<p className="type-body text-fg text-muted">
|
|
348
|
-
Turn your Instagram into a shareable media kit.
|
|
349
|
-
</p>
|
|
350
|
-
<div className="flex flex-wrap items-center gap-3">
|
|
351
|
-
<Button role="primary">Continue with Instagram</Button>
|
|
352
|
-
</div>
|
|
353
|
-
</div>
|
|
354
|
-
</Card.Body>
|
|
355
|
-
</Card>
|
|
356
|
-
);
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
function PublicReachCard({
|
|
360
|
-
reachState,
|
|
361
|
-
reachData,
|
|
362
|
-
}: {
|
|
363
|
-
reachState: PitchKitReachState;
|
|
364
|
-
reachData: ChartCartesianPoint[];
|
|
365
|
-
}) {
|
|
366
|
-
return (
|
|
367
|
-
<Card
|
|
368
|
-
variant="outlined"
|
|
369
|
-
shape="rounded"
|
|
370
|
-
bodyTerminal
|
|
371
|
-
className="col-span-full min-w-0 lg:col-span-6"
|
|
372
|
-
>
|
|
373
|
-
<Card.Header
|
|
374
|
-
start={
|
|
375
|
-
<>
|
|
376
|
-
<h2 className={cardTitleClasses}>Reach over 30 days</h2>
|
|
377
|
-
<p className={cardSubtitleClasses}>
|
|
378
|
-
Typical performance with unusual spikes left visible.
|
|
379
|
-
</p>
|
|
380
|
-
</>
|
|
381
|
-
}
|
|
382
|
-
end={
|
|
383
|
-
<Badge variant="neutral" emphasis="muted" size="sm">
|
|
384
|
-
Graph data
|
|
385
|
-
</Badge>
|
|
386
|
-
}
|
|
387
|
-
/>
|
|
388
|
-
<Card.Body>
|
|
389
|
-
{reachState === "insufficient" ? (
|
|
390
|
-
<div
|
|
391
|
-
className="flex min-w-0 flex-col gap-4 bg-body px-3.5 py-4 rounded-[var(--radius-card-body)] items-center justify-center text-center"
|
|
392
|
-
style={{ minHeight: 220 }}
|
|
393
|
-
>
|
|
394
|
-
<div className="flex max-w-lg flex-col gap-2 items-center text-center">
|
|
395
|
-
<Badge variant="neutral" emphasis="muted">No data</Badge>
|
|
396
|
-
<h3 className="type-heading-2 text-fg">No reach data yet</h3>
|
|
397
|
-
<p className="type-body text-fg text-muted">
|
|
398
|
-
Connect more Instagram activity to plot the last 30 days.
|
|
399
|
-
</p>
|
|
400
|
-
</div>
|
|
401
|
-
</div>
|
|
402
|
-
) : (
|
|
403
|
-
<div className="flex min-w-0 flex-col gap-4 bg-body px-3.5 py-4 rounded-[var(--radius-card-body)]">
|
|
404
|
-
<Chart.Cartesian
|
|
405
|
-
data={reachData}
|
|
406
|
-
config={reachConfig}
|
|
407
|
-
periodKind="month"
|
|
408
|
-
minHeight={220}
|
|
409
|
-
aria-label="Daily and typical Instagram reach over the last 30 days"
|
|
410
|
-
/>
|
|
411
|
-
<Chart.Legend config={reachConfig} />
|
|
412
|
-
</div>
|
|
413
|
-
)}
|
|
414
|
-
</Card.Body>
|
|
415
|
-
</Card>
|
|
416
|
-
);
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
function PublicCountries({ countries }: { countries: ChartRankedBarItem[] }) {
|
|
420
|
-
const topCountries = countries.slice(0, 3);
|
|
421
|
-
if (topCountries.length === 0) return null;
|
|
422
|
-
|
|
423
|
-
return (
|
|
424
|
-
<Card
|
|
425
|
-
variant="outlined"
|
|
426
|
-
shape="rounded"
|
|
427
|
-
bodyTerminal
|
|
428
|
-
className="col-span-full min-w-0 lg:col-span-6"
|
|
429
|
-
>
|
|
430
|
-
<Card.Header
|
|
431
|
-
start={
|
|
432
|
-
<>
|
|
433
|
-
<h2 className={cardTitleClasses}>Top countries</h2>
|
|
434
|
-
<p className={cardSubtitleClasses}>
|
|
435
|
-
Top 3 from Instagram Insights.
|
|
436
|
-
</p>
|
|
437
|
-
</>
|
|
438
|
-
}
|
|
439
|
-
/>
|
|
440
|
-
<Card.Body>
|
|
441
|
-
<div className="flex min-w-0 flex-col gap-4 bg-body px-3.5 py-4 rounded-[var(--radius-card-body)]">
|
|
442
|
-
<Chart.RankedBars
|
|
443
|
-
aria-label="Audience by country"
|
|
444
|
-
items={topCountries}
|
|
445
|
-
animate="initial"
|
|
446
|
-
/>
|
|
447
|
-
</div>
|
|
448
|
-
</Card.Body>
|
|
449
|
-
</Card>
|
|
450
|
-
);
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
function ShareablePitchKit({
|
|
454
|
-
identity,
|
|
455
|
-
intro,
|
|
456
|
-
posts,
|
|
457
|
-
contact,
|
|
458
|
-
brands,
|
|
459
|
-
countries,
|
|
460
|
-
reachData,
|
|
461
|
-
reachState = "resolved",
|
|
462
|
-
showCreateBand = true,
|
|
463
|
-
}: ShareablePitchKitProps) {
|
|
464
|
-
const engagementRate = reachState === "resolved" ? "5.8%" : null;
|
|
465
|
-
const typicalReach = reachState === "resolved" ? "9.3K" : "—";
|
|
466
|
-
|
|
467
|
-
return (
|
|
468
|
-
<>
|
|
469
|
-
<section className="col-span-full border-b border-border pb-6">
|
|
470
|
-
<div className="flex min-w-0 flex-col gap-3">
|
|
471
|
-
<CreatorIdentityStrip
|
|
472
|
-
identity={identity}
|
|
473
|
-
nameAs="h1"
|
|
474
|
-
showProfessionalChip
|
|
475
|
-
/>
|
|
476
|
-
<PublicIntro intro={intro} />
|
|
477
|
-
</div>
|
|
478
|
-
</section>
|
|
479
|
-
|
|
480
|
-
<div
|
|
481
|
-
role="group"
|
|
482
|
-
aria-label="Instagram performance summary"
|
|
483
|
-
className="band gap-y-4"
|
|
484
|
-
>
|
|
485
|
-
<Stat className="col-span-2 md:col-span-4 lg:col-span-3" label="Followers" value="84.2K" />
|
|
486
|
-
{engagementRate != null ? (
|
|
487
|
-
<Stat className="col-span-2 md:col-span-4 lg:col-span-3" label="Engagement rate" value={engagementRate} />
|
|
488
|
-
) : null}
|
|
489
|
-
<Stat className="col-span-2 md:col-span-4 lg:col-span-3" label="Typical reach" value={typicalReach} />
|
|
490
|
-
<Stat className="col-span-2 md:col-span-4 lg:col-span-3" label="Typical saves" value="6.1K" />
|
|
491
|
-
</div>
|
|
492
|
-
|
|
493
|
-
<div className="band min-w-0 gap-y-6 [align-items:stretch]">
|
|
494
|
-
<PublicReachCard reachState={reachState} reachData={reachData} />
|
|
495
|
-
<PublicCountries countries={countries} />
|
|
496
|
-
</div>
|
|
497
|
-
|
|
498
|
-
<section className="band min-w-0 gap-y-4">
|
|
499
|
-
<div className="col-span-full flex flex-wrap items-end justify-between gap-3">
|
|
500
|
-
<div>
|
|
501
|
-
<h2 className={cardTitleClasses}>Selected posts</h2>
|
|
502
|
-
<p className="type-body text-fg text-muted">
|
|
503
|
-
Proof from the current Instagram set.
|
|
504
|
-
</p>
|
|
505
|
-
</div>
|
|
506
|
-
</div>
|
|
507
|
-
<div className="band col-span-full min-w-0 gap-y-4">
|
|
508
|
-
{posts.map((post) => (
|
|
509
|
-
<Card key={post.id} variant="outlined" shape="rounded" className="col-span-full min-w-0 md:col-span-4 lg:col-span-4">
|
|
510
|
-
<Card.Body>
|
|
511
|
-
<img
|
|
512
|
-
className="aspect-[4/3] w-full bg-body object-cover rounded-[var(--radius-card-body)]"
|
|
513
|
-
src={post.imageUrl}
|
|
514
|
-
alt={post.imageAlt}
|
|
515
|
-
/>
|
|
516
|
-
</Card.Body>
|
|
517
|
-
<Card.Footer>
|
|
518
|
-
<div className="grid w-full grid-cols-2 gap-3">
|
|
519
|
-
{[
|
|
520
|
-
["Likes", post.likes],
|
|
521
|
-
["Comments", post.comments],
|
|
522
|
-
].map(([label, value]) => (
|
|
523
|
-
<span key={label} className="flex min-w-0 flex-col gap-1">
|
|
524
|
-
<span className="type-supporting font-medium uppercase tracking-wider text-muted text-muted">{label}</span>
|
|
525
|
-
<span className="font-mono text-sm tabular-nums text-fg">
|
|
526
|
-
{compactNumber.format(value as number)}
|
|
527
|
-
</span>
|
|
528
|
-
</span>
|
|
529
|
-
))}
|
|
530
|
-
</div>
|
|
531
|
-
</Card.Footer>
|
|
532
|
-
</Card>
|
|
533
|
-
))}
|
|
534
|
-
</div>
|
|
535
|
-
</section>
|
|
536
|
-
|
|
537
|
-
<section className="band min-w-0 gap-y-4">
|
|
538
|
-
<div className="col-span-full flex flex-wrap items-end justify-between gap-3">
|
|
539
|
-
<div>
|
|
540
|
-
<h2 className={cardTitleClasses}>Contact</h2>
|
|
541
|
-
<p className="type-body text-fg text-muted">
|
|
542
|
-
Creator-entered details for brand outreach.
|
|
543
|
-
</p>
|
|
544
|
-
</div>
|
|
545
|
-
</div>
|
|
546
|
-
<Card variant="outlined" padding="md" shape="rounded" className="col-span-full">
|
|
547
|
-
<div className="flex flex-col gap-3">
|
|
548
|
-
<div className="flex flex-col gap-1 sm:flex-row sm:items-center sm:justify-between sm:gap-3">
|
|
549
|
-
<span className="type-supporting font-medium uppercase tracking-wider text-muted text-muted">Email</span>
|
|
550
|
-
<TextLink href={`mailto:${contact.email}`}>{contact.email}</TextLink>
|
|
551
|
-
</div>
|
|
552
|
-
<div className="flex flex-col gap-1 sm:flex-row sm:items-center sm:justify-between sm:gap-3">
|
|
553
|
-
<span className="type-supporting font-medium uppercase tracking-wider text-muted text-muted">Website</span>
|
|
554
|
-
<TextLink href={contact.websiteHref} external>{contact.websiteLabel}</TextLink>
|
|
555
|
-
</div>
|
|
556
|
-
<div className="flex flex-col gap-1 sm:flex-row sm:items-center sm:justify-between sm:gap-3">
|
|
557
|
-
<span className="type-supporting font-medium uppercase tracking-wider text-muted text-muted">Location</span>
|
|
558
|
-
<span className="type-body text-fg text-muted">{contact.location}</span>
|
|
559
|
-
</div>
|
|
560
|
-
</div>
|
|
561
|
-
</Card>
|
|
562
|
-
</section>
|
|
563
|
-
|
|
564
|
-
<PublicPastBrands brands={brands} />
|
|
565
|
-
|
|
566
|
-
{showCreateBand ? <PublicCreatePitchkitBand /> : null}
|
|
567
|
-
</>
|
|
568
|
-
);
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
export function ShareablePitchKitPage({
|
|
573
|
-
identity,
|
|
574
|
-
intro,
|
|
575
|
-
posts,
|
|
576
|
-
contact,
|
|
577
|
-
brands,
|
|
578
|
-
countries,
|
|
579
|
-
reachData,
|
|
580
|
-
reachState = "resolved",
|
|
581
|
-
showCreateBand = true,
|
|
582
|
-
}: ShareablePitchKitProps) {
|
|
583
|
-
return (
|
|
584
|
-
<main className="grid-page min-h-screen bg-body [--grid-column-gap:8px] [--grid-max:1140px] [padding-bottom:44px]">
|
|
585
|
-
<div className="band pb-4">
|
|
586
|
-
<header className="col-span-full grid grid-cols-[1fr_auto_1fr] items-center gap-3">
|
|
587
|
-
<span className="type-heading-4 text-fg text-fg">PitchKit</span>
|
|
588
|
-
</header>
|
|
589
|
-
</div>
|
|
590
|
-
|
|
591
|
-
<div className="band pt-6 sm:pt-8">
|
|
592
|
-
<div className="band min-w-0 gap-y-6 sm:gap-y-8">
|
|
593
|
-
<ShareablePitchKit
|
|
594
|
-
identity={identity}
|
|
595
|
-
intro={intro}
|
|
596
|
-
posts={posts}
|
|
597
|
-
contact={contact}
|
|
598
|
-
brands={brands}
|
|
599
|
-
countries={countries}
|
|
600
|
-
reachData={reachData}
|
|
601
|
-
reachState={reachState}
|
|
602
|
-
showCreateBand={showCreateBand}
|
|
603
|
-
/>
|
|
604
|
-
</div>
|
|
605
|
-
</div>
|
|
606
|
-
</main>
|
|
607
|
-
);
|
|
608
|
-
}
|