@brdgapp/ui 0.1.2 → 1.0.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.
Files changed (123) hide show
  1. package/CHANGELOG.md +83 -0
  2. package/README.md +37 -201
  3. package/components/action-menu.d.ts +65 -0
  4. package/components/action-menu.js +48 -0
  5. package/components/avatar.d.ts +50 -0
  6. package/components/avatar.js +91 -0
  7. package/components/badge.d.ts +12 -0
  8. package/components/badge.js +23 -0
  9. package/components/bottom-sheet.d.ts +17 -0
  10. package/components/bottom-sheet.js +12 -0
  11. package/components/brand-mark.d.ts +20 -0
  12. package/components/brand-mark.js +44 -0
  13. package/components/button-group.d.ts +25 -0
  14. package/components/button-group.js +53 -0
  15. package/components/button.d.ts +33 -0
  16. package/components/button.js +82 -0
  17. package/components/card.d.ts +78 -0
  18. package/components/card.js +75 -0
  19. package/components/checkbox.d.ts +22 -0
  20. package/components/checkbox.js +35 -0
  21. package/components/collapsible.d.ts +29 -0
  22. package/components/collapsible.js +38 -0
  23. package/components/column-filter-menu.d.ts +15 -0
  24. package/components/column-filter-menu.js +30 -0
  25. package/components/column-sort-button.d.ts +18 -0
  26. package/components/column-sort-button.js +14 -0
  27. package/components/confirm-dialog.d.ts +20 -0
  28. package/components/confirm-dialog.js +17 -0
  29. package/components/copy-field.d.ts +17 -0
  30. package/components/copy-field.js +36 -0
  31. package/components/data-state.d.ts +44 -0
  32. package/components/data-state.js +25 -0
  33. package/components/data-table.d.ts +65 -0
  34. package/components/data-table.js +84 -0
  35. package/components/dialog.d.ts +19 -0
  36. package/components/dialog.js +10 -0
  37. package/components/drawer.d.ts +21 -0
  38. package/components/drawer.js +17 -0
  39. package/components/expandable-bio.d.ts +8 -0
  40. package/components/expandable-bio.js +26 -0
  41. package/components/field.d.ts +29 -0
  42. package/components/field.js +27 -0
  43. package/components/filter-chip.d.ts +11 -0
  44. package/components/filter-chip.js +8 -0
  45. package/components/filter-pill.d.ts +34 -0
  46. package/components/filter-pill.js +35 -0
  47. package/components/hover-card.d.ts +21 -0
  48. package/components/hover-card.js +20 -0
  49. package/components/icon-button.d.ts +16 -0
  50. package/components/icon-button.js +32 -0
  51. package/components/illustration.d.ts +33 -0
  52. package/components/illustration.js +70 -0
  53. package/components/info-hint.d.ts +29 -0
  54. package/components/info-hint.js +42 -0
  55. package/components/input.d.ts +9 -0
  56. package/components/input.js +15 -0
  57. package/components/network-graph.d.ts +48 -0
  58. package/components/network-graph.js +37 -0
  59. package/components/page-banner.d.ts +20 -0
  60. package/components/page-banner.js +29 -0
  61. package/components/scene.d.ts +12 -0
  62. package/components/scene.js +7 -0
  63. package/components/search-input.d.ts +8 -0
  64. package/components/search-input.js +14 -0
  65. package/components/segmented-control.d.ts +18 -0
  66. package/components/segmented-control.js +14 -0
  67. package/components/skeleton.d.ts +12 -0
  68. package/components/skeleton.js +21 -0
  69. package/components/skip-link.d.ts +8 -0
  70. package/components/skip-link.js +7 -0
  71. package/components/stat-box.d.ts +23 -0
  72. package/components/stat-box.js +29 -0
  73. package/components/switch.d.ts +13 -0
  74. package/components/switch.js +8 -0
  75. package/components/tabs.d.ts +6 -0
  76. package/components/tabs.js +25 -0
  77. package/components/tag-input.d.ts +15 -0
  78. package/components/tag-input.js +40 -0
  79. package/components/tag.d.ts +27 -0
  80. package/components/tag.js +35 -0
  81. package/components/textarea.d.ts +12 -0
  82. package/components/textarea.js +78 -0
  83. package/components/theme-switch.d.ts +6 -0
  84. package/components/theme-switch.js +16 -0
  85. package/components/toast.d.ts +25 -0
  86. package/components/toast.js +44 -0
  87. package/hooks/use-is-mobile.d.ts +2 -0
  88. package/hooks/use-is-mobile.js +16 -0
  89. package/hooks/use-mock-query.d.ts +23 -0
  90. package/hooks/use-mock-query.js +36 -0
  91. package/hooks/use-theme.d.ts +11 -0
  92. package/hooks/use-theme.js +34 -0
  93. package/lib/demo-controls.d.ts +24 -0
  94. package/lib/demo-controls.js +24 -0
  95. package/lib/format.d.ts +17 -0
  96. package/lib/format.js +55 -0
  97. package/lib/layout.d.ts +19 -0
  98. package/lib/layout.js +24 -0
  99. package/lib/motion.d.ts +16 -0
  100. package/lib/motion.js +18 -0
  101. package/lib/theme.d.ts +21 -0
  102. package/lib/theme.js +63 -0
  103. package/lib/utils.d.ts +22 -0
  104. package/lib/utils.js +27 -0
  105. package/package.json +32 -16
  106. package/styles/globals.css +595 -0
  107. package/src/index.ts +0 -18
  108. package/src/network-people-helper/README.md +0 -130
  109. package/src/network-people-helper/api.ts +0 -174
  110. package/src/network-people-helper/contact-avatar.tsx +0 -63
  111. package/src/network-people-helper/domain.ts +0 -14
  112. package/src/network-people-helper/icons.tsx +0 -130
  113. package/src/network-people-helper/index.ts +0 -6
  114. package/src/network-people-helper/network-people-helper.tsx +0 -397
  115. package/src/network-people-helper/network-people-nudge.tsx +0 -98
  116. package/src/network-people-helper/open-helper.ts +0 -22
  117. package/src/network-people-helper/person-menu.tsx +0 -211
  118. package/src/network-people-helper/styles.css +0 -848
  119. package/src/network-people-helper/types.ts +0 -23
  120. package/src/network-people-helper/use-network-people-list.ts +0 -122
  121. package/src/network-people-helper/use-network-preview.ts +0 -43
  122. package/src/provider.tsx +0 -59
  123. package/src/styles.css +0 -1
package/CHANGELOG.md ADDED
@@ -0,0 +1,83 @@
1
+ # Changelog
2
+
3
+ Every release of `@brdgapp/ui`. Versions follow the rules in README.md → Versions.
4
+
5
+ ## 1.0.0
6
+
7
+ The first release of Bridge's design system as a package. A new major version: the 0.1.x releases
8
+ (NetworkPeopleHelper, BridgeUIProvider) are not part of it. Apps using them stay on 0.1.2 until they move
9
+ those parts into their own code.
10
+
11
+ - Tokens: every Bridge role token in light and dark, three shadow levels, glass, dot-grid and noise
12
+ utilities, and base styles (`globals.css`).
13
+ - Components: ActionMenu, Avatar, Badge, BottomSheet, BrandMark, Button, ButtonGroup, Card (with CardHeader, CardMedia and CardFooter), Checkbox (with CheckboxGroup), Collapsible, HoverCard, Illustration, NetworkGraph, Scene, ColumnFilterMenu, ColumnSortButton, ConfirmDialog,
14
+ CopyField, DataState, DataTable, Dialog, Drawer, ExpandableBio, Field (with FieldLabel, FieldDescription,
15
+ FieldError and FieldGroup), FilterChip, FilterPill, IconButton, InfoHint (with HoverTip and Tip), Input,
16
+ PageBanner, SearchInput, SegmentedControl, Skeleton, SkipLink, StatBox, Switch, Tabs, ThemeSwitch, Tag, TagInput, Textarea, Toast.
17
+ - Needs React 19 and Tailwind 4 (the components take `ref` as a prop and use `inert`, which React 18 ignores).
18
+ - Built on Base UI. Each component's handbook page notes how it relates to Bridge OS's own version.
19
+ - Avatar shows a person's photo, or their initials on light blue, in six sizes (16 to 64px), with an
20
+ optional badge: Bridge member, Super Connector or warning. A photo that fails to load falls back to
21
+ the initials. AvatarGroup stacks the first faces and counts the rest ("+4").
22
+ - Textarea starts 3 lines tall, grows with the text up to 10, then scrolls. With `maxLength` it shows a
23
+ count under the box that turns red near the limit.
24
+ - Checkbox is 16px with a 28px click area, can be part-ticked for "Select all", and takes a `label` and
25
+ `description`. CheckboxGroup holds a list of ticked options.
26
+ - Collapsible opens and closes a section in two looks (`section`, `inline`), sliding open in 200ms.
27
+ - BrandMark draws the company logos Bridge shows (LinkedIn, Google, Microsoft, Apple, WhatsApp, Telegram,
28
+ Chrome) at icon size, in their own colors; LinkedIn, WhatsApp and Telegram can be one color. Lucide 1.x has no logos.
29
+ - Illustration draws thirteen small scenes for empty states (people, intros, companies, messages, activity,
30
+ meetings, lists, jobs, connect, search, filters, done, error) in tokens, so they follow dark mode. DataState's
31
+ empty state takes `illustration` in place of `icon`.
32
+ - NetworkGraph shows you (or someone) in the middle and the people you know around you, with an optional blue path.
33
+ - Scene frames a product picture built from real components: decorative, inert, on the dot grid or a blue wash.
34
+ - Light, Dark or System in every app: `themeScript` (`@brdgapp/ui/lib/theme`) sets the theme before the page
35
+ paints; `useTheme()` (`@brdgapp/ui/hooks/use-theme`) reads and changes it; ThemeSwitch is the Appearance setting.
36
+ System is the default. The choice is a cookie for all of brdg.app, so every Bridge app follows it; Bridge OS's
37
+ old saved choice is still read.
38
+ - Focus is a solid 2px blue ring (red on danger buttons, white on blue) with a 2px gap: 7:1 in light, 5:1 in dark.
39
+ Bridge OS's 40% ring was under WCAG's 3:1. A fallback outline in `globals.css` covers anything an app builds
40
+ itself. TabPanel shows the ring when it takes focus.
41
+ - Disabled is 50% in every component (Bridge OS buttons used 30%; the package mixed 30, 40, 50 and 60).
42
+ - `glass-chrome` is the full top-bar glass (a 40px blur, a hairline under it, a near-solid fill where Safari
43
+ drops the blur). `liquid-glass-sm`, used nowhere, is removed. The stylesheet needs Tailwind 4.
44
+ - Overlays follow Motion's speeds on Bridge's curve: menus, tips, hover cards and toasts 200ms in and 150ms out;
45
+ dialogs, drawers, sheets and backdrops 300ms in and 200ms out (dialogs and menus were 150ms both ways).
46
+ - SkipLink: “Skip to content”, hidden until focused, for every app's layout.
47
+ - HoverCard previews a person or company after the pointer rests on its name for 300ms. Read-only.
48
+ - Card is frosted glass in three sizes (`lg`, `md`, `sm`), or a `bare` row inside another card. It can be a link
49
+ (`href`, rises on hover) or picked from a list (`onSelect`, `selected`, tints on hover); controls inside
50
+ keep working with no extra code. Header with icon (in blue, green, amber or purple) or avatar, an ⓘ hint, meta, action and an optional strip; CardMedia;
51
+ CardFooter; `flush` for tables.
52
+ - `--text-muted` is #6D7284 (Bridge OS #6E7385) so it passes 4.5:1 on `--surface-secondary`; dark `--purple` is
53
+ #9A81ED (Bridge OS #8B6EE8) so purple tags pass 4.5:1 on every dark surface.
54
+ - `cn` knows Bridge's page textures (`bg-dot-grid`, `bg-noise`, `bg-glow-patches`) aren't background colors, so
55
+ it keeps both a color and a texture, and lets the three stack.
56
+ - Named type sizes on one ladder: `text-11`, `-12`, `-13`, `-14`, `-16`, `-18`, `-22`, `-28`, `-36`, `-48`, each with its
57
+ line height. `cn` knows they are sizes. Card titles are 16px; Dialog, ConfirmDialog and Drawer titles 18px.
58
+ Input, Textarea, SearchInput and TagInput are 16px on phones (14px from 768px up) so iOS doesn't zoom in.
59
+ - Button `md` is 40px tall (Bridge OS 38px), the height of a text field, so the two line up in a row.
60
+ - `@brdgapp/ui/lib/layout`: `PAGE_WIDTH` (wide 1600, standard 1280, read 800, form 720, narrow 448), `PAGE_EDGES`,
61
+ `pageFrame(width)` and `RAIL` (a 340px side rail from 1024px).
62
+ - Phones (below 768px): FilterPill and ColumnFilterMenu open as a BottomSheet; ActionMenu rows, Tabs, fields and
63
+ the medium button are 44px tall; CopyField's copy hint shows without hover. `useIsMobile()` in
64
+ `@brdgapp/ui/hooks/use-is-mobile`.
65
+ - Corners: ColumnSortButton, ColumnFilterMenu and Collapsible triggers use 4px (were 6px), so every component
66
+ is on the five corners (4, 8, 12, 16, round).
67
+ - BottomSheet casts the dialog shadow (upward), not the drawer's.
68
+ - Motion: `ease-out` is Bridge's one curve, cubic-bezier(0.23, 1, 0.32, 1); `@brdgapp/ui/lib/motion` has the three
69
+ speeds (`DURATION`: 150, 200, 300ms), `EASE` and `transition()` for JavaScript animations.
70
+ - Tokens: `--super-connector-from`, `--super-connector-to` and `--badge-warning` for avatar badges.
71
+ - StatBox corners are 12px (the small-card radius), up from Bridge OS's 8px.
72
+ - Tag, TagInput, FilterPill, CopyField, ColumnSortButton, ColumnFilterMenu, InfoHint and HoverTip pass
73
+ extra attributes (`id`, `aria-*`, `data-*`, event handlers) to their main element; DataTable takes
74
+ `rowProps` for each row. The components' own props win where they overlap.
75
+ - `@brdgapp/ui/lib/format`: `formatCount`, `formatMoney`, `formatPercent`, `formatDate`, `formatTime`, Bridge's
76
+ writing rules for numbers and dates (American English). StatBox formats its value with `formatCount`
77
+ (1,284 in full; 12.5K from 10,000).
78
+ - Toast is built on Base UI Toast: mount `<Toaster />` once, then call `toast.success()` or
79
+ `toast.error()` from anywhere (a queue, a 3-second timer that pauses on hover, swipe to dismiss).
80
+ - SearchInput, TagInput, CopyField, ColumnSortButton and DataTable's page buttons are built on Base UI
81
+ Input and Button, like Button.
82
+ - Deprecated from the start: `Tag`'s `title` (a native tooltip). Use `Tip`; `title` is removed in 2.0.
83
+ - Deprecated from the start: `<Toast open message />`, the Bridge OS API. Use `toast`; it is removed in 2.0.
package/README.md CHANGED
@@ -1,231 +1,67 @@
1
1
  # @brdgapp/ui
2
2
 
3
- Shared UI components for Bridge apps. Each component lives in its own folder under `src/` with its own README. Apps install one package and import the components they need.
3
+ Bridge's shared components, tokens and styles. How and when to use each part is in the handbook:
4
+ **https://handbook.brdg.app** (start with "Using the system").
4
5
 
5
- It publishes to the `brdgapp` npm team. Installing it does not need an `.npmrc` or a registry token. Vercel installs it like any other dependency.
6
+ ## Needs
6
7
 
7
- Source lives in the private GitLab repo `intropath/packages/ui`. The published tarball is what apps install.
8
+ - React 19
9
+ - Tailwind CSS 4 (apps still on Tailwind 3 upgrade first)
10
+ - The Mulish font, loaded by the app
8
11
 
9
- ## Install
10
-
11
- Peer dependencies are `react` and `react-dom` (18 or newer). The app already has those.
12
+ ## Set up
12
13
 
13
14
  ```bash
14
- pnpm add @brdgapp/ui
15
- ```
16
-
17
- The package publishes TypeScript source, not a prebuilt `dist`. The app bundler must compile it.
18
-
19
- ### Next.js
20
-
21
- In `next.config.mjs` (or `next.config.js`):
22
-
23
- ```js
24
- const nextConfig = {
25
- transpilePackages: ["@brdgapp/ui"],
26
- }
27
- ```
28
-
29
- Import the stylesheet once from the root layout (`app/layout.tsx`). That file pulls in every component stylesheet. Next.js does not pick up the package’s CSS import on its own.
30
-
31
- ```tsx
32
- import "@brdgapp/ui/styles.css"
33
- ```
34
-
35
- ### Vite
36
-
37
- Add the package to `optimizeDeps.exclude` only if the dev server fails to compile its TypeScript. The package entry imports `styles.css`, so a normal Vite app gets the CSS from the JS import. If styles are missing, import `@brdgapp/ui/styles.css` once from the app entry.
38
-
39
- ## Provider
40
-
41
- Wrap the tree once with `BridgeUIProvider`. It supplies the Bridge API base and the signed-in user’s access token. Components that talk to the Bridge API use this provider. They do not take their own token prop.
42
-
43
- ```tsx
44
- "use client"
45
-
46
- import { BridgeUIProvider } from "@brdgapp/ui"
47
-
48
- export function AppProviders({ children }: { children: React.ReactNode }) {
49
- return (
50
- <BridgeUIProvider getAccessToken={getAuthToken}>
51
- {children}
52
- </BridgeUIProvider>
53
- )
54
- }
55
- ```
56
-
57
- `apiBaseUrl` defaults to production: `https://api.brdg.app/api/v4`. Pass it only for staging or a local backend, using the same value the app already uses for `NEXT_PUBLIC_API_BASE_URL`. Include the `/api/v4` prefix and leave off the trailing slash.
58
-
59
- `getAccessToken` returns the bearer token for the signed-in user, or `null` when nobody is signed in. It may be async. A Next.js server action that reads the `authToken` cookie is a valid implementation, because components call it from the browser:
60
-
61
- ```tsx
62
- "use server"
63
-
64
- import { cookies } from "next/headers"
65
-
66
- export async function getAuthToken(): Promise<string | null> {
67
- const cookieStore = await cookies()
68
- return cookieStore.get("authToken")?.value ?? null
69
- }
70
- ```
71
-
72
- Pass that function as `getAccessToken`. Do not put the token, or any other secret, in this package.
73
-
74
- `introInboxUrl` and `profileUrlForUsername` override the URLs used by built-in intro and profile actions. Defaults are `https://intros.brdg.app/inbox` and `https://os.brdg.app/to/<username>`.
75
-
76
- ## Components
77
-
78
- Import from `@brdgapp/ui`, not from a deep path. The app must already wrap the tree in `BridgeUIProvider`.
79
-
80
- - [Network people helper](#network-people-helper)
81
-
82
- <details id="network-people-helper">
83
- <summary><strong>Network people helper</strong> — people you know at a domain</summary>
84
-
85
- Shows how many people you know at a specific domain, and who they are, from the signed-in user’s Bridge network.
86
-
87
- Use it on any screen that is about one company or firm. Pass that domain. The component looks up the signed-in user’s contacts whose email domain matches, then renders a floating widget: “You know 6 people at Brdg”, with avatars. Opening it lists those people (name, role, company, email) so the user can make an intro, copy an intro link, or open a Bridge profile. Apps can add their own row actions, such as adding the person as a team member.
88
-
89
- Nothing appears when the user is signed out, or when they know nobody at that domain.
90
-
91
- ### Floating widget
92
-
93
- ```tsx
94
- import { NetworkPeopleHelper } from "@brdgapp/ui"
95
-
96
- <NetworkPeopleHelper
97
- domain={domain}
98
- placement="bottom-end"
99
- customActions={[
100
- {
101
- id: "add-team-member",
102
- label: "Add as team member",
103
- description: "Save them on this firm's team so they show up with the rest of the roster.",
104
- onSelect: (contact) => addTeamMember(contact),
105
- },
106
- ]}
107
- />
15
+ npm install @brdgapp/ui
108
16
  ```
109
17
 
110
- `domain` is required. The component cleans it (strips protocol, `www.`, path, and query) and sends it as `domain_override`.
111
-
112
- After `checkDelayMs` (default 3000) it requests:
113
-
114
- - `GET {apiBaseUrl}/search/owned_full_network_contacts/count?domain_override=`
115
- - `GET {apiBaseUrl}/search/owned_full_network_contacts?domain_override=&limit=&offset=`
116
-
117
- Requests send `Authorization: Bearer <token>` from the provider’s `getAccessToken`. Pass `0` to skip the wait.
118
-
119
- When the count is greater than zero, the widget appears: icon, then the “You know N people at …” note, then a searchable list with infinite scroll. “Do not show again” hides it for the rest of the browser session (`sessionStorage` key `bridge-network-helper-dismissed:<domain>`).
120
-
121
- #### Props
122
-
123
- | Prop | Required | Default | Purpose |
124
- | --- | --- | --- | --- |
125
- | `domain` | yes | — | Company or firm domain to match contacts against. |
126
- | `organizationLabel` | no | cleaned domain | Text after “at”. Pass a display name such as `"Brdg"` when the screen already has one. The contacts API does not return an organization name. |
127
- | `enabled` | no | `true` | Set `false` to skip the request and render nothing. |
128
- | `checkDelayMs` | no | `3000` | Wait after the page visit before the first request. |
129
- | `placement` | no | `"bottom-end"` | Fixed corner. See below. |
130
- | `className` | no | — | Extra class on the fixed root. |
131
- | `style` | no | — | Extra inline style on the fixed root. |
132
- | `customActions` | no | `[]` | Extra rows in each person’s menu. |
133
-
134
- `placement` is one of `"bottom-end"`, `"bottom-start"`, `"top-end"`, `"top-start"`. `"bottom-end"` is fixed, bottom-right, with a slightly larger offset from 640px up.
135
-
136
- Override that position in the app’s CSS. These variables win over the preset:
18
+ In the app's main CSS file, after Tailwind:
137
19
 
138
20
  ```css
139
- .my-helper {
140
- --bridge-helper-bottom: 5rem;
141
- --bridge-helper-right: 2rem;
142
- --bridge-helper-z: 50;
143
- }
21
+ @import "tailwindcss";
22
+ @import "@brdgapp/ui/globals.css";
144
23
  ```
145
24
 
146
- The full set is `--bridge-helper-top`, `--bridge-helper-right`, `--bridge-helper-bottom`, `--bridge-helper-left`, and `--bridge-helper-z`.
25
+ That brings every token (light and dark), the base styles, and tells Tailwind to read the package's
26
+ files, so its classes are included.
147
27
 
148
- Colors follow the host app’s `--foreground`, `--background`, `--muted`, `--muted-foreground`, and `--border` when those exist. Otherwise the widget uses its own defaults. The Bridge blue accent is `#0038ff`.
149
-
150
- #### Person menu
151
-
152
- Built-in actions, shown only when the contact has the field they need:
153
-
154
- - **Make intro** opens `https://intros.brdg.app/inbox?action=make-intro&n1=<email>` when `email` is set.
155
- - **Copy intro link** copies `shareLink`.
156
- - **View Bridge profile** opens `https://os.brdg.app/to/<username>` when `username` is set.
157
-
158
- Change those URLs with `introInboxUrl` and `profileUrlForUsername` on `BridgeUIProvider`.
159
-
160
- Custom actions are not built in. “Add as team member” and “Add as founder” belong to the app. Pass them only on the screens where they apply. `onSelect` receives the row’s contact:
28
+ Load Mulish however the app loads fonts. With `next/font`, name the variable `--font-mulish`:
161
29
 
162
30
  ```ts
163
- type NetworkContact = {
164
- id: string
165
- name: string
166
- email: string | null
167
- shareLink: string | null
168
- profilePicUrl: string | null
169
- position: string | null
170
- company: string | null
171
- username: string | null
172
- isMember: boolean
173
- }
174
-
175
- type NetworkPersonAction = {
176
- id: string
177
- label: string
178
- description?: string
179
- icon?: React.ReactNode
180
- onSelect: (contact: NetworkContact) => void
181
- }
31
+ const mulish = Mulish({ subsets: ["latin"], variable: "--font-mulish" });
182
32
  ```
183
33
 
184
- Custom actions render above the built-in ones. The component does not check edit permission. If the user must not see an action, omit it from `customActions`.
34
+ Anything else (a Google Fonts link, a self-hosted file) works too, as long as the family is "Mulish".
185
35
 
186
- ### Inline nudge
36
+ Themes follow `data-theme="light"` or `data-theme="dark"` on `<html>`, not the device setting.
187
37
 
188
- `NetworkPeopleNudge` is the same note, inline in a team or founders section. It uses the same request as the floating helper. Clicking it opens the helper’s people list on this page.
38
+ ## Use
189
39
 
190
- ```tsx
191
- import { NetworkPeopleNudge } from "@brdgapp/ui"
40
+ Import each component from its own file. There is no single index file.
192
41
 
193
- <NetworkPeopleNudge domain={domain} organizationLabel={displayName} />
42
+ ```tsx
43
+ import { Button } from "@brdgapp/ui/components/button";
44
+ import { DataState } from "@brdgapp/ui/components/data-state";
45
+ import { cn } from "@brdgapp/ui/lib/utils";
194
46
  ```
195
47
 
196
- Pass `onOpen` when the click should do something else. `organizationLabel`, `enabled`, and `className` match the helper. The nudge’s check delay is fixed at 3 seconds.
197
-
198
- `openNetworkPeopleHelper(domain)` opens the floating helper from any other button. It clears the session dismiss for that domain.
48
+ For toasts, mount `<Toaster />` once in the root layout, then call `toast.success("Link copied")` from
49
+ anywhere (`@brdgapp/ui/components/toast`).
199
50
 
200
- </details>
201
-
202
- ## Adding a component
203
-
204
- 1. Add `src/<component-name>/` with the component source and a `README.md` that says what it is for, its props, and a usage example. Add a `<details id="...">` section for that component under Components in this README, with the same usage, and link the component name to that id. npm renders this file only, and the GitLab repo is private.
205
- 2. Export the public components and types from `src/<component-name>/index.ts`.
206
- 3. Re-export those names from `src/index.ts` so apps keep importing `@brdgapp/ui`.
207
- 4. If the component has CSS, put it in `src/<component-name>/styles.css` and add `@import "./<component-name>/styles.css";` to `src/styles.css`.
208
- 5. If it calls the Bridge API, use `useBridgeUI()` from `src/provider.tsx`. Do not read tokens inside the component.
209
-
210
- ## Publish
211
-
212
- From this repo, after the version in `package.json` is the one you want to release:
213
-
214
- ```bash
215
- pnpm login
216
- pnpm publish
217
- ```
51
+ `useMockQuery` (`hooks/use-mock-query`) and `lib/demo-controls` are for handbook examples and
52
+ prototypes. Replace them with real data loading in product code.
218
53
 
219
- `pnpm login` uses the `brdgapp` team account. `publishConfig.access` is `public`, so `pnpm publish` does not need `--access public`.
54
+ ## Versions
220
55
 
221
- This package has no dependencies of its own, so there is no lockfile to install first. Commit the release before publishing. pnpm refuses to publish a dirty git tree.
56
+ - A fix bumps the last number (1.0.1).
57
+ - A new component or option bumps the middle number (1.1.0).
58
+ - Anything that could break an app bumps the first number (2.0.0).
59
+ - Before something is renamed or removed, it's marked Deprecated for at least one release, with its
60
+ replacement named.
222
61
 
223
- To release a change, bump `version`, commit, push, then run `pnpm publish` again.
62
+ Every release is listed in `CHANGELOG.md`.
224
63
 
225
- ## Agent checklist
64
+ ## Agents
226
65
 
227
- 1. Run `pnpm add @brdgapp/ui`.
228
- 2. Next.js: set `transpilePackages: ["@brdgapp/ui"]`.
229
- 3. Import `@brdgapp/ui/styles.css` once in the root layout.
230
- 4. Wrap the app in `BridgeUIProvider` with a `getAccessToken` that returns the signed-in user’s bearer token.
231
- 5. Open the component’s section under Components in this README and follow that.
66
+ AI coding agents connect to the handbook's read-only MCP server at `https://handbook.brdg.app/mcp`.
67
+ Setup: https://handbook.brdg.app/for-agents.
@@ -0,0 +1,65 @@
1
+ import type * as React from "react";
2
+ declare const ACTION_MENU_ITEM_CLASS: string;
3
+ type ActionMenuContextValue = {
4
+ close: (restoreFocus?: boolean) => void;
5
+ };
6
+ /** Close the menu from inside its body, for custom controls. */
7
+ declare function useActionMenu(): ActionMenuContextValue;
8
+ /** Everything the trigger needs: ref, click and keyboard handlers, aria-haspopup, aria-expanded. */
9
+ type TriggerRenderProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
10
+ ref?: React.Ref<HTMLButtonElement>;
11
+ "aria-haspopup"?: React.AriaAttributes["aria-haspopup"];
12
+ "aria-expanded"?: boolean;
13
+ };
14
+ type ActionMenuProps = {
15
+ /** Names the default ⋮ trigger. Name what the menu is for when it helps: "Actions for Leah Okonkwo". */
16
+ ariaLabel?: string;
17
+ /** Trigger size: `sm` for compact rows, `md` for headers. */
18
+ size?: "sm" | "md";
19
+ /** Menu width in px. 248 is the contact-menu width. */
20
+ width?: number;
21
+ /** Trigger icon. Defaults to vertical dots. */
22
+ icon?: React.ReactNode;
23
+ triggerClassName?: string;
24
+ menuClassName?: string;
25
+ /** Stop the trigger's click reaching a clickable row behind it. */
26
+ stopPropagation?: boolean;
27
+ /** Know when it opens, for example to load data then. */
28
+ onOpenChange?: (open: boolean) => void;
29
+ /**
30
+ * A custom trigger, such as a split button's caret. Spread `props` onto your control so it gets the
31
+ * keyboard handling too (Arrow Down opens the menu). `state.open` says whether the menu is open.
32
+ */
33
+ renderTrigger?: (props: TriggerRenderProps, state: {
34
+ open: boolean;
35
+ }) => React.ReactElement;
36
+ children: React.ReactNode | ((close: (restoreFocus?: boolean) => void) => React.ReactNode);
37
+ };
38
+ declare function ActionMenu({ ariaLabel, size, width, icon, triggerClassName, menuClassName, stopPropagation, onOpenChange, renderTrigger, children, }: ActionMenuProps): React.JSX.Element;
39
+ type ActionMenuItemProps = {
40
+ icon?: React.ReactNode;
41
+ children: React.ReactNode;
42
+ /** A muted second line under the label. */
43
+ description?: React.ReactNode;
44
+ onClick?: () => void;
45
+ /** Makes the row a link. */
46
+ href?: string;
47
+ /** With `href`: opens in a new tab. */
48
+ external?: boolean;
49
+ /** Red, for removing or leaving. */
50
+ danger?: boolean;
51
+ disabled?: boolean;
52
+ /** Keep the menu open after choosing, for a row that shows its own result ("Copied"). */
53
+ closeOnClick?: boolean;
54
+ /**
55
+ * Render the link with your router's link, e.g. `render={<Link href="/settings" />}`.
56
+ * Without it, `href` renders a plain `<a>`.
57
+ */
58
+ render?: React.ReactElement;
59
+ className?: string;
60
+ };
61
+ declare function ActionMenuItem({ icon, children, description, onClick, href, external, danger, disabled, closeOnClick, render, className, }: ActionMenuItemProps): React.JSX.Element;
62
+ /** A thin line between groups of rows. */
63
+ declare function ActionMenuSeparator(): React.JSX.Element;
64
+ export { ActionMenu, ActionMenuItem, ActionMenuSeparator, useActionMenu, ACTION_MENU_ITEM_CLASS };
65
+ export type { ActionMenuProps, ActionMenuItemProps };
@@ -0,0 +1,48 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { createContext, useCallback, useContext, useMemo, useState } from "react";
4
+ import { Menu } from "@base-ui/react/menu";
5
+ import { MoreVertical } from "lucide-react";
6
+ import { IconButton } from "@brdgapp/ui/components/icon-button";
7
+ import { cn } from "@brdgapp/ui/lib/utils";
8
+ // THE overflow (⋮) menu. Ported from Bridge OS src/components/ui/action-menu.tsx with the same API.
9
+ // Bridge OS hand-built the portal, positioning, outside-click and Escape handling; Base UI Menu now
10
+ // does that, plus arrow-key movement, type-to-find and focus return. It also follows the trigger on
11
+ // scroll instead of closing. The danger colour is now a token (Bridge OS hard-coded #E13535).
12
+ // The one menu-row look. Exported so custom stateful rows can match it.
13
+ const ACTION_MENU_ITEM_CLASS = "flex w-full cursor-default items-center gap-3 px-3.5 py-2.5 max-md:min-h-11 text-left text-[13px] text-[var(--text-body)] outline-none transition-colors " +
14
+ "data-[highlighted]:bg-[var(--surface-secondary)] hover:bg-[var(--surface-secondary)] data-[disabled]:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0";
15
+ const ACTION_MENU_ITEM_DANGER_CLASS = "text-[var(--danger-text)] data-[highlighted]:bg-[var(--error-bg)] hover:bg-[var(--error-bg)]";
16
+ const ActionMenuContext = createContext(null);
17
+ /** Close the menu from inside its body, for custom controls. */
18
+ function useActionMenu() {
19
+ const ctx = useContext(ActionMenuContext);
20
+ if (!ctx)
21
+ throw new Error("useActionMenu must be used inside <ActionMenu>");
22
+ return ctx;
23
+ }
24
+ function ActionMenu({ ariaLabel = "More options", size = "md", width = 248, icon, triggerClassName, menuClassName, stopPropagation = true, onOpenChange, renderTrigger, children, }) {
25
+ const [open, setOpen] = useState(false);
26
+ const setOpenAndNotify = useCallback((next) => {
27
+ setOpen(next);
28
+ onOpenChange?.(next);
29
+ }, [onOpenChange]);
30
+ // Base UI returns focus to the trigger whenever the menu closes, so `restoreFocus` needs no extra work.
31
+ const close = useCallback(() => setOpenAndNotify(false), [setOpenAndNotify]);
32
+ const ctx = useMemo(() => ({ close }), [close]);
33
+ const stop = stopPropagation ? (e) => e.stopPropagation() : undefined;
34
+ return (_jsxs(Menu.Root, { open: open, onOpenChange: setOpenAndNotify, children: [renderTrigger ? (_jsx(Menu.Trigger, { render: (props) => renderTrigger(props, { open }) })) : (_jsx(Menu.Trigger, { render: _jsx(IconButton, { "aria-label": ariaLabel, size: size, className: cn("shrink-0", triggerClassName), children: icon ?? _jsx(MoreVertical, {}) }), onClick: stop, onPointerDown: stop })), _jsx(Menu.Portal, { children: _jsx(Menu.Positioner, { side: "bottom", align: "end", sideOffset: 6, collisionPadding: 8, className: "z-[80]", children: _jsx(Menu.Popup, { "data-slot": "action-menu", style: { width }, className: cn("max-w-[calc(100vw-16px)] rounded-xl border border-[var(--border-default)] bg-[var(--surface-elevated)] py-1 shadow-[var(--shadow-menu)] outline-none", "origin-[var(--transform-origin)] transition-[opacity,scale] duration-200 ease-out data-[ending-style]:duration-150 data-[starting-style]:scale-95 data-[starting-style]:opacity-0 data-[ending-style]:opacity-0", menuClassName), children: _jsx(ActionMenuContext.Provider, { value: ctx, children: typeof children === "function" ? children(close) : children }) }) }) })] }));
35
+ }
36
+ function ActionMenuItem({ icon, children, description, onClick, href, external = false, danger = false, disabled = false, closeOnClick = true, render, className, }) {
37
+ const classes = cn(ACTION_MENU_ITEM_CLASS, danger && ACTION_MENU_ITEM_DANGER_CLASS, description && "items-start", className);
38
+ const body = description ? (_jsxs(_Fragment, { children: [_jsx("span", { "aria-hidden": "true", className: "mt-0.5 inline-flex shrink-0", children: icon }), _jsxs("span", { className: "min-w-0", children: [_jsx("span", { className: "block text-[13px] font-semibold text-[var(--text-primary)]", children: children }), _jsx("span", { className: "block text-[12px] text-[var(--text-muted)]", children: description })] })] })) : (_jsxs(_Fragment, { children: [icon ? (_jsx("span", { "aria-hidden": "true", className: "inline-flex shrink-0", children: icon })) : null, children] }));
39
+ if (href && !disabled) {
40
+ return (_jsx(Menu.LinkItem, { "data-slot": "action-menu-item", href: href, closeOnClick: closeOnClick, onClick: onClick, className: classes, render: render, ...(external ? { target: "_blank", rel: "noopener noreferrer" } : {}), children: body }));
41
+ }
42
+ return (_jsx(Menu.Item, { "data-slot": "action-menu-item", onClick: onClick, disabled: disabled, closeOnClick: closeOnClick, className: classes, children: body }));
43
+ }
44
+ /** A thin line between groups of rows. */
45
+ function ActionMenuSeparator() {
46
+ return _jsx(Menu.Separator, { "data-slot": "action-menu-separator", className: "my-1 h-px bg-[var(--border-divider)]" });
47
+ }
48
+ export { ActionMenu, ActionMenuItem, ActionMenuSeparator, useActionMenu, ACTION_MENU_ITEM_CLASS };
@@ -0,0 +1,50 @@
1
+ import type { CSSProperties } from "react";
2
+ import { type VariantProps } from "class-variance-authority";
3
+ declare const avatarVariants: (props?: ({
4
+ size?: "sm" | "md" | "lg" | "xs" | "xl" | "2xl" | null | undefined;
5
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
6
+ type AvatarSize = NonNullable<VariantProps<typeof avatarVariants>["size"]>;
7
+ type AvatarBadge = "member" | "super-connector" | "warning";
8
+ /** "Priya Natarajan" → "PN", "Priya" → "P", "" → "?". First and last word, so middle names don't crowd it. */
9
+ declare function initialsOf(name: string | null | undefined): string;
10
+ type AvatarProps = {
11
+ /** The person's name. Used for the initials, and read out when `standalone`. */
12
+ name: string | null | undefined;
13
+ /** Photo URL. Leave empty and the initials show. */
14
+ src?: string | null;
15
+ /** 16, 24, 32, 40, 48 or 64px. */
16
+ size?: AvatarSize;
17
+ /** A corner mark: Bridge member, Super Connector, or an account that needs attention. Not shown at `xs`. */
18
+ badge?: AvatarBadge | null;
19
+ /**
20
+ * Set when no name shows next to the avatar, so screen readers hear who it is. Most avatars sit
21
+ * beside the name, and only the badge (if any) is read, so the name isn't read twice.
22
+ */
23
+ standalone?: boolean;
24
+ /** Layout only: `mt-0.5`. */
25
+ className?: string;
26
+ /** For AvatarGroup's stacking order. */
27
+ style?: CSSProperties;
28
+ };
29
+ declare function Avatar({ name, src, size, badge, standalone, className, style }: AvatarProps): import("react").JSX.Element;
30
+ type AvatarGroupProps = {
31
+ /** Everyone in the group, in order. The first ones show. */
32
+ people: {
33
+ name: string | null | undefined;
34
+ src?: string | null;
35
+ }[];
36
+ /** How many faces show before "+N". */
37
+ max?: number;
38
+ /** 16 to 64px. Stacks are usually `sm` (24px). */
39
+ size?: AvatarSize;
40
+ /**
41
+ * Set when the names are written next to the stack. Otherwise screen readers hear the names:
42
+ * "Leah Okonkwo, Priya Natarajan, Mei Lin Zhou and 4 others".
43
+ */
44
+ decorative?: boolean;
45
+ /** Layout only. */
46
+ className?: string;
47
+ };
48
+ declare function AvatarGroup({ people, max, size, decorative, className }: AvatarGroupProps): import("react").JSX.Element;
49
+ export { Avatar, AvatarGroup, avatarVariants, initialsOf };
50
+ export type { AvatarBadge, AvatarGroupProps, AvatarProps, AvatarSize };
@@ -0,0 +1,91 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Avatar as AvatarPrimitive } from "@base-ui/react/avatar";
4
+ import { cva } from "class-variance-authority";
5
+ import { cn } from "@brdgapp/ui/lib/utils";
6
+ // A person's photo in a circle, or their initials when there's no photo, with an optional corner badge.
7
+ // Started from shadcn's Base UI avatar, then fitted to the Bridge apps, which draw this by hand in over
8
+ // 100 places across 12 repos. Base UI shows the initials while the photo loads and keeps them if the
9
+ // photo fails, so a broken URL never shows a broken image.
10
+ //
11
+ // Decisions (2026-11-03, after checking all 12 app repos):
12
+ // - Initials on light blue (`--info-bg`), letters in `--info` (not `--primary`: 3.3:1 in dark).
13
+ // - Sizes 16, 24, 32, 40, 48, 64: stacks and chips 16–24, lists 32, rows 40, cards 48, profile headers 64.
14
+ // - Badges, as in Bridge OS contact-card.tsx and app-cc avatar-status-badge.tsx: a blue tick for a Bridge
15
+ // member, a purple award for a Super Connector (replaces the tick), an amber "!" when the account is
16
+ // deactivated or its emails fail. The warning amber is darker than app-cc's #E19500, which gave the
17
+ // white mark 2.5:1; #B87A00 gives 3.6:1.
18
+ // - AvatarGroup: 3 faces then "+N", overlapping a little less than the apps do, a ring between faces (app-directories, together,
19
+ // app-portfolio, raise, bridge-jobs).
20
+ const avatarVariants = cva("relative inline-flex shrink-0 rounded-full bg-[var(--info-bg)] align-middle select-none", {
21
+ variants: {
22
+ size: {
23
+ xs: "size-4 text-[8px]",
24
+ sm: "size-6 text-[10px]",
25
+ md: "size-8 text-[12px]",
26
+ lg: "size-10 text-[14px]",
27
+ xl: "size-12 text-[16px]",
28
+ "2xl": "size-16 text-[20px]",
29
+ },
30
+ },
31
+ defaultVariants: { size: "md" },
32
+ });
33
+ const BADGE_LABEL = {
34
+ member: "Bridge member",
35
+ "super-connector": "Super Connector",
36
+ warning: "Account needs attention",
37
+ };
38
+ // Badge diameter for each avatar size. None at 16px: too small to read.
39
+ const BADGE_SIZE = {
40
+ xs: null,
41
+ sm: "size-2.5 ring-1",
42
+ md: "size-3 ring-[1.5px]",
43
+ lg: "size-3.5 ring-2",
44
+ xl: "size-[18px] ring-2",
45
+ "2xl": "size-5 ring-2",
46
+ };
47
+ /** "Priya Natarajan" → "PN", "Priya" → "P", "" → "?". First and last word, so middle names don't crowd it. */
48
+ function initialsOf(name) {
49
+ const words = (name ?? "").trim().split(/\s+/).filter(Boolean);
50
+ const first = words[0]?.[0] ?? "";
51
+ const last = words.length > 1 ? (words[words.length - 1]?.[0] ?? "") : "";
52
+ return (first + last).toUpperCase() || "?";
53
+ }
54
+ function BadgeMark({ badge }) {
55
+ if (badge === "member") {
56
+ return (_jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "4", strokeLinecap: "round", strokeLinejoin: "round", className: "size-[60%]", children: _jsx("polyline", { points: "20 6 9 17 4 12" }) }));
57
+ }
58
+ if (badge === "super-connector") {
59
+ // The award glyph from the Arch Design System, via Bridge OS super-connector-badge.tsx.
60
+ return (_jsx("svg", { viewBox: "0 0 8.24323 14.1285", fill: "currentColor", className: "h-[60%] w-auto", children: _jsx("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M0.136812 0.00229937H3.11564L5.79901 4.88271C5.85649 4.98618 5.94042 5.06781 6.03584 5.11954H2.03034L0.136812 0V0.00229937ZM4.12162 5.88523C6.39799 5.88523 8.24323 7.73047 8.24323 10.0068C8.24323 12.2832 6.39799 14.1285 4.12162 14.1285C1.84524 14.1273 0 12.2821 0 10.0057C0 7.72932 1.84524 5.88523 4.12162 5.88523ZM4.12162 7.627L4.81028 9.30784L6.62333 9.44465L5.23566 10.6219L5.66679 12.3855L4.12162 11.4279L2.57644 12.3855L3.00757 10.6219L1.61875 9.4435L3.43181 9.30669L4.12162 7.627ZM4.48722 0.00229937H7.97306L6.57044 3.79396L4.48722 0.00229937Z" }) }));
61
+ }
62
+ return (_jsxs("svg", { viewBox: "0 0 16 16", fill: "currentColor", className: "size-[70%]", children: [_jsx("path", { d: "M8 3.2c-.59 0-1.05.5-1.02 1.08l.25 4a.77.77 0 0 0 1.53 0l.25-4A1.02 1.02 0 0 0 8 3.2Z" }), _jsx("circle", { cx: "8", cy: "11.6", r: "0.95" })] }));
63
+ }
64
+ const BADGE_FILL = {
65
+ member: "bg-[var(--primary-solid)]",
66
+ "super-connector": "bg-linear-to-l from-[var(--super-connector-from)] to-[var(--super-connector-to)]",
67
+ warning: "bg-[var(--badge-warning)]",
68
+ };
69
+ function Avatar({ name, src, size = "md", badge, standalone = false, className, style }) {
70
+ const badgeSize = badge ? BADGE_SIZE[size] : null;
71
+ const label = badge ? BADGE_LABEL[badge] : null;
72
+ return (_jsxs(AvatarPrimitive.Root, { "data-slot": "avatar", "data-size": size, className: cn(avatarVariants({ size }), className), style: style, ...(standalone ? { role: "img", "aria-label": [name || "Unknown person", label].filter(Boolean).join(", ") } : {}), children: [_jsxs("span", { className: "block size-full overflow-hidden rounded-full", "aria-hidden": "true", children: [src ? _jsx(AvatarPrimitive.Image, { "data-slot": "avatar-image", src: src, alt: "", className: "size-full object-cover" }) : null, _jsx(AvatarPrimitive.Fallback, { "data-slot": "avatar-fallback", className: "flex size-full items-center justify-center leading-none font-bold text-[var(--info)]", children: initialsOf(name) })] }), badge && badgeSize ? (_jsx("span", { "data-slot": "avatar-badge", "data-badge": badge, "aria-hidden": "true", className: cn("absolute -right-0.5 -bottom-0.5 flex items-center justify-center rounded-full text-white ring-[var(--surface-primary)]", BADGE_FILL[badge], badgeSize), children: _jsx(BadgeMark, { badge: badge }) })) : null, badge && !standalone ? _jsx("span", { className: "sr-only", children: label }) : null] }));
73
+ }
74
+ // How far each face tucks under the one before it: a quarter of the face less the ring, so two-letter
75
+ // initials stay readable (the apps' 6px at 24px hid the first letter).
76
+ const OVERLAP = {
77
+ xs: "-ml-0.5",
78
+ sm: "-ml-1",
79
+ md: "-ml-1.5",
80
+ lg: "-ml-2",
81
+ xl: "-ml-2.5",
82
+ "2xl": "-ml-3",
83
+ };
84
+ function AvatarGroup({ people, max = 3, size = "sm", decorative = false, className }) {
85
+ const shown = people.slice(0, max);
86
+ const extra = people.length - shown.length;
87
+ const names = shown.map((p) => p.name || "Unknown person");
88
+ const label = extra > 0 ? `${names.join(", ")} and ${extra} ${extra === 1 ? "other" : "others"}` : names.join(", ");
89
+ return (_jsxs("span", { "data-slot": "avatar-group", className: cn("inline-flex items-center align-middle", className), ...(decorative ? { "aria-hidden": true } : { role: "img", "aria-label": label }), children: [shown.map((p, i) => (_jsx(Avatar, { name: p.name, src: p.src, size: size, className: cn("ring-2 ring-[var(--surface-primary)]", i > 0 && OVERLAP[size]), style: { zIndex: shown.length - i } }, `${p.name}-${i}`))), extra > 0 ? (_jsxs("span", { "data-slot": "avatar-group-count", className: cn(avatarVariants({ size }), OVERLAP[size], "items-center justify-center bg-[var(--surface-tertiary)] leading-none font-semibold text-[var(--text-secondary)] ring-2 ring-[var(--surface-primary)]"), children: ["+", extra] })) : null] }));
90
+ }
91
+ export { Avatar, AvatarGroup, avatarVariants, initialsOf };
@@ -0,0 +1,12 @@
1
+ import type * as React from "react";
2
+ import { type VariantProps } from "class-variance-authority";
3
+ declare const badgeVariants: (props?: ({
4
+ style?: "outline" | "alum" | "solid-red" | "solid-dark" | null | undefined;
5
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
6
+ type BadgeProps = VariantProps<typeof badgeVariants> & {
7
+ className?: string;
8
+ children: React.ReactNode;
9
+ };
10
+ declare function Badge({ style, className, children }: BadgeProps): React.JSX.Element;
11
+ export { Badge, badgeVariants };
12
+ export type { BadgeProps };