@cosxai/ui 0.25.0 → 1.0.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. package/CHANGELOG.md +597 -0
  2. package/NOTICE +11 -0
  3. package/README.md +52 -0
  4. package/package.json +57 -7
  5. package/src/brand.css +13 -0
  6. package/src/components/Badge.tsx +68 -0
  7. package/src/components/Button.tsx +178 -0
  8. package/src/components/Card.tsx +42 -0
  9. package/src/components/Checkbox.tsx +67 -0
  10. package/src/components/Dialog.tsx +111 -0
  11. package/src/components/Field.tsx +66 -0
  12. package/src/components/Figure.tsx +30 -0
  13. package/src/components/Icon.tsx +32 -0
  14. package/src/components/IconButton.tsx +55 -0
  15. package/src/components/Input.tsx +74 -0
  16. package/src/components/Logo.tsx +62 -0
  17. package/src/components/Marker.tsx +26 -0
  18. package/src/components/MetaLabel.tsx +38 -0
  19. package/src/components/Radio.tsx +72 -0
  20. package/src/components/Select.tsx +351 -0
  21. package/src/components/Spinner.tsx +20 -0
  22. package/src/components/Switch.tsx +67 -0
  23. package/src/components/Table.tsx +196 -0
  24. package/src/components/Tabs.tsx +124 -0
  25. package/src/components/Tag.tsx +35 -0
  26. package/src/components/Textarea.tsx +46 -0
  27. package/src/components/ThemeProvider.tsx +80 -0
  28. package/src/components/Toast.tsx +159 -0
  29. package/src/components/Tooltip.tsx +57 -0
  30. package/src/components/core.test.tsx +242 -0
  31. package/src/components/forms-control.ts +36 -0
  32. package/src/components/forms.test.tsx +280 -0
  33. package/src/components/logo-art.ts +4 -0
  34. package/src/components/overlays.test.tsx +245 -0
  35. package/src/components/useButtonAction.ts +57 -0
  36. package/src/fonts.css +20 -0
  37. package/src/index.ts +34 -24
  38. package/src/lib/cn.ts +23 -6
  39. package/src/modes.css +10 -0
  40. package/src/styles.css +19 -0
  41. package/src/theme.css +143 -0
  42. package/src/tokens/colors.css +112 -0
  43. package/src/tokens/compat.css +36 -0
  44. package/src/tokens/elevation.css +14 -0
  45. package/src/tokens/motion.css +42 -0
  46. package/src/tokens/spacing.css +55 -0
  47. package/src/tokens/typography-cjk.css +42 -0
  48. package/src/tokens/typography.css +81 -0
  49. package/src/actionbar/ActionBar.tsx +0 -837
  50. package/src/actionbar/ActionBarButton.tsx +0 -111
  51. package/src/actionbar/ActionBarMenuGroup.tsx +0 -116
  52. package/src/actionbar/ActionBarModeHandle.tsx +0 -171
  53. package/src/actionbar/ActionBarProvider.tsx +0 -91
  54. package/src/actionbar/actionbar-context.ts +0 -45
  55. package/src/actionbar/index.ts +0 -28
  56. package/src/actionbar/types.ts +0 -147
  57. package/src/actionbar/useActionBarItems.ts +0 -60
  58. package/src/actionbar/useActionBarMode.ts +0 -144
  59. package/src/actionbar/useActionBarPanel.ts +0 -56
  60. package/src/actionbar/useActionBarStatusDot.ts +0 -49
  61. package/src/actionbar/useActionBarToast.ts +0 -33
  62. package/src/ambient/AmbientBackdrop.tsx +0 -74
  63. package/src/ambient/CommandInput.tsx +0 -107
  64. package/src/ambient/SuperbarStrip.tsx +0 -36
  65. package/src/ambient/index.ts +0 -6
  66. package/src/bento/BentoCell.tsx +0 -66
  67. package/src/bento/BentoGrid.tsx +0 -42
  68. package/src/bento/index.ts +0 -2
  69. package/src/command/CommandPalette.tsx +0 -329
  70. package/src/command/CommandProvider.tsx +0 -57
  71. package/src/command/command-context.ts +0 -12
  72. package/src/command/index.ts +0 -6
  73. package/src/command/rank.ts +0 -50
  74. package/src/command/types.ts +0 -37
  75. package/src/command/useCommandSource.ts +0 -37
  76. package/src/dialogs/DialogsProvider.tsx +0 -216
  77. package/src/dialogs/Modal.tsx +0 -260
  78. package/src/dialogs/Toast.tsx +0 -85
  79. package/src/dialogs/dialogs-context.ts +0 -6
  80. package/src/dialogs/index.ts +0 -10
  81. package/src/dialogs/types.ts +0 -37
  82. package/src/dialogs/useDialogs.ts +0 -8
  83. package/src/editorial/EditorialSpotlight.tsx +0 -63
  84. package/src/editorial/Folio.tsx +0 -52
  85. package/src/editorial/PlateMarker.tsx +0 -33
  86. package/src/editorial/RomanSection.tsx +0 -65
  87. package/src/editorial/RunningMarginalia.tsx +0 -65
  88. package/src/editorial/index.ts +0 -10
  89. package/src/hooks/index.ts +0 -5
  90. package/src/hooks/useKeyboardHotkey.ts +0 -93
  91. package/src/hooks/useReducedMotion.ts +0 -20
  92. package/src/hooks/useViewport.ts +0 -61
  93. package/src/layout/Breadcrumb.tsx +0 -74
  94. package/src/layout/LeftNavRail.tsx +0 -139
  95. package/src/layout/MobileTabBar.tsx +0 -101
  96. package/src/layout/NavItem.tsx +0 -128
  97. package/src/layout/NavSearchTrigger.tsx +0 -88
  98. package/src/layout/NavSection.tsx +0 -40
  99. package/src/layout/RightSidebarPanel.tsx +0 -111
  100. package/src/layout/Shell.tsx +0 -91
  101. package/src/layout/SidePanel.tsx +0 -316
  102. package/src/layout/StickyBanner.tsx +0 -83
  103. package/src/layout/Topbar.tsx +0 -68
  104. package/src/layout/index.ts +0 -24
  105. package/src/layout/useNavRailState.ts +0 -69
  106. package/src/lib/time-utils.ts +0 -44
  107. package/src/neobrutalism/Marquee.tsx +0 -81
  108. package/src/neobrutalism/Sticker.tsx +0 -71
  109. package/src/neobrutalism/index.ts +0 -4
  110. package/src/primitives/Avatar.tsx +0 -53
  111. package/src/primitives/Button.tsx +0 -67
  112. package/src/primitives/Card.tsx +0 -41
  113. package/src/primitives/Checkbox.tsx +0 -87
  114. package/src/primitives/Chip.tsx +0 -191
  115. package/src/primitives/Combobox.tsx +0 -507
  116. package/src/primitives/CopyField.tsx +0 -139
  117. package/src/primitives/CountBadge.tsx +0 -50
  118. package/src/primitives/Input.tsx +0 -204
  119. package/src/primitives/Kbd.tsx +0 -45
  120. package/src/primitives/MentionCombobox.tsx +0 -612
  121. package/src/primitives/MultiCombobox.tsx +0 -327
  122. package/src/primitives/PageHeader.tsx +0 -77
  123. package/src/primitives/Radio.tsx +0 -131
  124. package/src/primitives/SegmentedControl.tsx +0 -135
  125. package/src/primitives/Select.tsx +0 -779
  126. package/src/primitives/SignaturePad.tsx +0 -461
  127. package/src/primitives/Tag.tsx +0 -56
  128. package/src/primitives/Textarea.tsx +0 -62
  129. package/src/primitives/ToggleSwitch.tsx +0 -79
  130. package/src/primitives/Tooltip.tsx +0 -213
  131. package/src/primitives/index.ts +0 -53
  132. package/src/pwa/InstallPromptBanner.tsx +0 -132
  133. package/src/pwa/index.ts +0 -4
  134. package/src/pwa/manifest.template.json +0 -20
  135. package/src/pwa/registerSW.ts +0 -55
  136. package/src/riso/Halftone.tsx +0 -85
  137. package/src/riso/Misregister.tsx +0 -63
  138. package/src/riso/RisoStamp.tsx +0 -76
  139. package/src/riso/index.ts +0 -3
  140. package/src/sketch/HandUnderline.tsx +0 -53
  141. package/src/sketch/RoughArrow.tsx +0 -91
  142. package/src/sketch/RoughBox.tsx +0 -73
  143. package/src/sketch/StickyNote.tsx +0 -56
  144. package/src/sketch/index.ts +0 -4
  145. package/src/styles/base.css +0 -186
  146. package/src/styles/chrome-ambient.css +0 -235
  147. package/src/styles/chrome-bento.css +0 -192
  148. package/src/styles/chrome-editorial.css +0 -168
  149. package/src/styles/chrome-neobrutalism.css +0 -326
  150. package/src/styles/chrome-riso.css +0 -339
  151. package/src/styles/chrome-sketch.css +0 -362
  152. package/src/styles/chrome-swiss.css +0 -265
  153. package/src/styles/chrome-terminal.css +0 -272
  154. package/src/styles/fonts.css +0 -1034
  155. package/src/styles/index.css +0 -343
  156. package/src/styles/tokens.css +0 -941
  157. package/src/terminal/AsciiBox.tsx +0 -65
  158. package/src/terminal/BrailleSpinner.tsx +0 -46
  159. package/src/terminal/index.ts +0 -4
  160. package/src/theme/ThemeProvider.tsx +0 -99
  161. package/src/theme/index.ts +0 -6
  162. package/src/theme/inline-script.ts +0 -40
  163. package/src/theme/theme-context.ts +0 -7
  164. package/src/theme/types.ts +0 -39
  165. package/src/theme/useTheme.ts +0 -8
@@ -1,71 +0,0 @@
1
- import type { ReactNode, CSSProperties } from "react";
2
-
3
- // Rotated badge — "NEW!", "FREE!", "v2.0!" style. Sits in the
4
- // corner of featured cards or banners. Filled with an accent
5
- // colour, thick border, hard shadow, slight tilt to read as a
6
- // physical sticker pinned on the page.
7
-
8
- export interface StickerProps {
9
- children: ReactNode;
10
- // Tilt angle in degrees. Default -8 (slight left lean —
11
- // pleasing eye-tilt without going crazy).
12
- rotate?: number;
13
- // Fill tone. "accent" / "warning" / "success" / "critical"
14
- // pick from token palette; pass a hex string to override.
15
- tone?: "accent" | "warning" | "success" | "critical" | string;
16
- // px diameter — sticker is square so width == height.
17
- size?: number;
18
- className?: string;
19
- style?: CSSProperties;
20
- }
21
-
22
- function toneToBg(tone: NonNullable<StickerProps["tone"]>) {
23
- switch (tone) {
24
- case "accent":
25
- return "var(--ck-accent, #88AAEE)";
26
- case "warning":
27
- return "var(--ck-warning, #FFD23F)";
28
- case "success":
29
- return "var(--ck-success, #A3E636)";
30
- case "critical":
31
- return "var(--ck-critical, #FF6B6B)";
32
- default:
33
- return tone;
34
- }
35
- }
36
-
37
- export function Sticker({
38
- children,
39
- rotate = -8,
40
- tone = "warning",
41
- size = 80,
42
- className,
43
- style,
44
- }: StickerProps) {
45
- return (
46
- <span
47
- className={className}
48
- style={{
49
- display: "inline-flex",
50
- alignItems: "center",
51
- justifyContent: "center",
52
- textAlign: "center",
53
- width: size,
54
- height: size,
55
- padding: 6,
56
- background: toneToBg(tone),
57
- color: "#000",
58
- border: "2px solid var(--neobrut-ink, #000)",
59
- borderRadius: "50%",
60
- boxShadow: "3px 3px 0 0 var(--neobrut-ink, #000)",
61
- transform: `rotate(${rotate}deg)`,
62
- font: "800 14px/1.1 var(--ck-font-display, var(--ck-font-sans))",
63
- letterSpacing: "0.02em",
64
- textTransform: "uppercase",
65
- ...style,
66
- }}
67
- >
68
- {children}
69
- </span>
70
- );
71
- }
@@ -1,4 +0,0 @@
1
- export { Marquee } from "./Marquee";
2
- export type { MarqueeProps } from "./Marquee";
3
- export { Sticker } from "./Sticker";
4
- export type { StickerProps } from "./Sticker";
@@ -1,53 +0,0 @@
1
- import { cn } from "../lib/cn";
2
-
3
- // Circular avatar — image with initials fallback. Use the `name`
4
- // prop to derive initials (first letter of first two whitespace-
5
- // separated tokens). Size scales the whole thing including font.
6
-
7
- export interface AvatarProps {
8
- // Display name — used for initials fallback and as alt text.
9
- name: string;
10
- // Image src. If omitted or fails to load, falls back to initials.
11
- src?: string | null;
12
- size?: number; // px, default 32
13
- className?: string;
14
- }
15
-
16
- function initials(name: string): string {
17
- const parts = name.trim().split(/\s+/).slice(0, 2);
18
- return parts.map((p) => p[0]?.toUpperCase() ?? "").join("") || "?";
19
- }
20
-
21
- export function Avatar({ name, src, size = 32, className }: AvatarProps) {
22
- return (
23
- <span
24
- className={cn("ck-avatar", className)}
25
- style={{
26
- display: "inline-flex",
27
- alignItems: "center",
28
- justifyContent: "center",
29
- width: size,
30
- height: size,
31
- borderRadius: "50%",
32
- background: "var(--ck-bg-muted)",
33
- color: "var(--ck-text-secondary)",
34
- font: `500 ${Math.round(size * 0.4)}px/1 var(--ck-font-sans)`,
35
- overflow: "hidden",
36
- flexShrink: 0,
37
- }}
38
- aria-label={name}
39
- >
40
- {src ? (
41
- <img
42
- src={src}
43
- alt={name}
44
- width={size}
45
- height={size}
46
- style={{ width: "100%", height: "100%", objectFit: "cover" }}
47
- />
48
- ) : (
49
- initials(name)
50
- )}
51
- </span>
52
- );
53
- }
@@ -1,67 +0,0 @@
1
- import { forwardRef, type ButtonHTMLAttributes, type ReactNode } from "react";
2
- import { cn } from "../lib/cn";
3
-
4
- /**
5
- * Button — primitive interactive element. Variants `primary`
6
- * (filled) / `secondary` (outlined) / `ghost` (transparent) /
7
- * `icon` (square 32×32).
8
- *
9
- * The `loading` prop is the canonical way to show that an async
10
- * action triggered by the button is in flight. When true:
11
- *
12
- * - a small ring spinner renders before the children (inherits
13
- * `currentColor` so it reads on every variant),
14
- * - the button is `disabled` natively so clicks are dropped,
15
- * - `aria-busy="true"` is set for AT consumers.
16
- *
17
- * `loading` and `disabled` are distinct states. `disabled` says
18
- * "you can't take this action right now" (form invalid, no
19
- * selection, etc.). `loading` says "we're already taking this
20
- * action — wait". Callers typically wire both:
21
- *
22
- * <Button disabled={!name.trim()} loading={submitting}>
23
- * {submitting ? "Saving…" : "Save"}
24
- * </Button>
25
- *
26
- * Visual: see docs/components/button.
27
- *
28
- * Forwards ref to the underlying <button>. Spreads `...rest` so
29
- * consumers can pass data-* / aria-* / className.
30
- */
31
-
32
- export type ButtonVariant = "primary" | "secondary" | "ghost" | "icon";
33
-
34
- export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
35
- variant?: ButtonVariant | undefined;
36
- /**
37
- * When true, the button renders a leading spinner, sets
38
- * `aria-busy="true"`, and becomes natively `disabled` so click
39
- * handlers no longer fire. Use this for any async submit so the
40
- * user sees the click landed AND can't double-fire the action.
41
- */
42
- loading?: boolean | undefined;
43
- children?: ReactNode;
44
- }
45
-
46
- export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
47
- function Button(
48
- { variant = "primary", className, children, loading, disabled, ...rest },
49
- ref,
50
- ) {
51
- return (
52
- <button
53
- ref={ref}
54
- {...rest}
55
- className={cn("ck-btn", `ck-btn--${variant}`, className)}
56
- disabled={disabled || loading}
57
- aria-busy={loading || undefined}
58
- data-loading={loading || undefined}
59
- >
60
- {loading ? (
61
- <span className="ck-btn-spinner" aria-hidden="true" />
62
- ) : null}
63
- {children}
64
- </button>
65
- );
66
- },
67
- );
@@ -1,41 +0,0 @@
1
- import type { ReactNode, CSSProperties } from "react";
2
- import { cn } from "../lib/cn";
3
-
4
- export interface CardProps {
5
- // Eyebrow + title bar at the top. Pass any nodes; common pattern
6
- // is <span className="ck-eyebrow">...</span> + <h3>...</h3>.
7
- header?: ReactNode;
8
- // Right-aligned actions next to the header (e.g. action buttons).
9
- headerActions?: ReactNode;
10
- // Bottom strip (typically buttons or meta info).
11
- footer?: ReactNode;
12
- children?: ReactNode;
13
- // Disable the default body padding (when the children manage
14
- // their own — e.g. a list with full-bleed rows).
15
- noPad?: boolean;
16
- className?: string;
17
- style?: CSSProperties;
18
- }
19
-
20
- export function Card({
21
- header,
22
- headerActions,
23
- footer,
24
- children,
25
- noPad,
26
- className,
27
- style,
28
- }: CardProps) {
29
- return (
30
- <section className={cn("ck-card", className)} style={style}>
31
- {(header || headerActions) && (
32
- <header className="ck-card__head">
33
- <div style={{ flex: 1, minWidth: 0 }}>{header}</div>
34
- {headerActions}
35
- </header>
36
- )}
37
- <div className={noPad ? undefined : "ck-card__body"}>{children}</div>
38
- {footer && <footer className="ck-card__foot">{footer}</footer>}
39
- </section>
40
- );
41
- }
@@ -1,87 +0,0 @@
1
- import { cn } from "../lib/cn";
2
-
3
- // Custom-styled checkbox — accent-bordered square that becomes filled
4
- // accent + white tick when checked. The native <input type="checkbox">
5
- // renders as a giant white card on dark backgrounds in most browsers,
6
- // which is exactly the bug that prompted this primitive in the parent
7
- // project. Use this anywhere you'd reach for a checkbox.
8
-
9
- export interface CheckboxProps {
10
- checked: boolean;
11
- onChange: (next: boolean) => void;
12
- label?: React.ReactNode;
13
- suffix?: React.ReactNode;
14
- disabled?: boolean;
15
- className?: string;
16
- /**
17
- * Test hook forwarded to the visual button. Consumers with
18
- * getByTestId('…') queries can keep them working across the swap
19
- * from native <input type="checkbox"> to this primitive without
20
- * touching each test file.
21
- */
22
- "data-testid"?: string;
23
- }
24
-
25
- export function Checkbox({
26
- checked,
27
- onChange,
28
- label,
29
- suffix,
30
- disabled,
31
- className,
32
- "data-testid": testId,
33
- }: CheckboxProps) {
34
- return (
35
- <label
36
- className={cn("ck-checkbox", className)}
37
- style={{
38
- display: "inline-flex",
39
- alignItems: "center",
40
- gap: 8,
41
- font: "400 13px/1 var(--ck-font-sans)",
42
- color: "var(--ck-text-secondary)",
43
- cursor: disabled ? "not-allowed" : "pointer",
44
- opacity: disabled ? 0.5 : 1,
45
- userSelect: "none",
46
- }}
47
- >
48
- <button
49
- type="button"
50
- role="checkbox"
51
- aria-checked={checked}
52
- disabled={disabled}
53
- onClick={() => !disabled && onChange(!checked)}
54
- data-testid={testId}
55
- style={{
56
- width: 16,
57
- height: 16,
58
- borderRadius: 4,
59
- border: "1.5px solid var(--ck-accent)",
60
- background: checked ? "var(--ck-accent)" : "transparent",
61
- display: "inline-flex",
62
- alignItems: "center",
63
- justifyContent: "center",
64
- padding: 0,
65
- cursor: disabled ? "not-allowed" : "pointer",
66
- flexShrink: 0,
67
- }}
68
- >
69
- {checked && (
70
- <svg width="10" height="10" viewBox="0 0 12 12" fill="none" aria-hidden>
71
- <path
72
- d="M2.5 6.5L5 9L9.5 3.5"
73
- stroke="#FFFFFF"
74
- strokeWidth="2"
75
- strokeLinecap="round"
76
- strokeLinejoin="round"
77
- />
78
- </svg>
79
- )}
80
- </button>
81
- {label}
82
- {suffix && (
83
- <span style={{ color: "var(--ck-text-tertiary)" }}>{suffix}</span>
84
- )}
85
- </label>
86
- );
87
- }
@@ -1,191 +0,0 @@
1
- import {
2
- forwardRef,
3
- type CSSProperties,
4
- type HTMLAttributes,
5
- type MouseEvent,
6
- type ReactNode,
7
- } from "react";
8
-
9
- import { cn } from "../lib/cn";
10
-
11
- /**
12
- * Chip — a compact pill representing one selected value (a recipient,
13
- * a filter, a tag the user picked). Exists so multi-value inputs
14
- * (MultiCombobox) and token lists share one removable-item shape
15
- * instead of each product hand-rolling a "name ×" span.
16
- *
17
- * Distinct from `Tag` (uppercase mono STATUS label — "PENDING",
18
- * "LIVE"): a Chip is sentence-case body text (13px sans), a pill on a
19
- * muted slab, and is interactive — optional `onClick` turns the label
20
- * into a button (e.g. "edit this entry"), optional `onRemove` adds
21
- * the × button.
22
- *
23
- * Tones: `neutral` (default), `accent`, `warning` (e.g. "needs a
24
- * name"), `critical`. `selected` outlines the chip in the accent
25
- * colour (e.g. the entry currently being edited). `disabled` dims the
26
- * chip and disables both buttons.
27
- *
28
- * Compose with: MultiCombobox (entry row), filter bars, recipient
29
- * lists on detail pages.
30
- *
31
- * Visual: see docs/components/multi-combobox.
32
- *
33
- * Forwards ref to the root <span>. Spreads `...rest` onto it so
34
- * consumers can pass data-* / aria-* / title / style. Chrome presets
35
- * can restyle via the `[data-ck-chip]` / `data-tone` / `data-selected`
36
- * hooks — 0.24.0 ships no per-chrome CSS for it.
37
- */
38
-
39
- export type ChipTone = "neutral" | "accent" | "warning" | "critical";
40
-
41
- export type ChipProps = Omit<HTMLAttributes<HTMLSpanElement>, "onClick"> & {
42
- children: ReactNode;
43
- tone?: ChipTone | undefined;
44
- // Outline the chip in the accent colour (e.g. "being edited").
45
- selected?: boolean | undefined;
46
- // Dim + disable both the label button and the remove button.
47
- disabled?: boolean | undefined;
48
- // When set, the label renders as a <button> (e.g. click-to-edit).
49
- onClick?: (() => void) | undefined;
50
- // When set, a × button renders after the label.
51
- onRemove?: (() => void) | undefined;
52
- // aria-label for the × button. Default "Remove".
53
- removeLabel?: string | undefined;
54
- className?: string | undefined;
55
- };
56
-
57
- const TONE_FG: Record<ChipTone, string> = {
58
- neutral: "var(--ck-text-primary, #111)",
59
- accent: "var(--ck-accent, #4f46e5)",
60
- warning: "var(--ck-warning, #D97706)",
61
- critical: "var(--ck-critical, #DC2626)",
62
- };
63
- const TONE_BG: Record<ChipTone, string> = {
64
- neutral: "var(--ck-bg-muted, #E5E8EE)",
65
- accent: "var(--ck-accent-muted, rgba(79, 70, 229, 0.08))",
66
- warning: "var(--ck-warning-muted, #FEF3E7)",
67
- critical: "var(--ck-critical-muted, #FCEAEA)",
68
- };
69
- const TONE_BORDER: Record<ChipTone, string> = {
70
- neutral: "var(--ck-border-subtle, #DDE0E6)",
71
- accent: "var(--ck-accent-border, rgba(79, 70, 229, 0.35))",
72
- warning: "color-mix(in oklab, var(--ck-warning, #D97706) 35%, transparent)",
73
- critical: "color-mix(in oklab, var(--ck-critical, #DC2626) 35%, transparent)",
74
- };
75
-
76
- // Label text styles — shared by the <span> and <button> renderings so
77
- // the chip looks identical whether or not it is clickable.
78
- const LABEL_STYLE: CSSProperties = {
79
- minWidth: 0,
80
- overflow: "hidden",
81
- textOverflow: "ellipsis",
82
- whiteSpace: "nowrap",
83
- font: "inherit",
84
- color: "inherit",
85
- background: "none",
86
- border: "none",
87
- padding: 0,
88
- margin: 0,
89
- textAlign: "left",
90
- };
91
-
92
- export const Chip = forwardRef<HTMLSpanElement, ChipProps>(function Chip(
93
- {
94
- children,
95
- tone = "neutral",
96
- selected = false,
97
- disabled = false,
98
- onClick,
99
- onRemove,
100
- removeLabel,
101
- className,
102
- style,
103
- ...rest
104
- },
105
- ref,
106
- ) {
107
- const clickable = !!onClick && !disabled;
108
- // Chip buttons act on click but must NOT take focus on mousedown:
109
- // when a host has an inline editor open for another chip, the
110
- // editor's blur (commit + unmount) would otherwise run before this
111
- // click lands and the click is lost. Keyboard focus via Tab is
112
- // unaffected.
113
- const keepFocus = (e: MouseEvent<HTMLButtonElement>): void => e.preventDefault();
114
- return (
115
- <span
116
- ref={ref}
117
- className={cn("ck-chip", className)}
118
- data-ck-chip
119
- data-tone={tone}
120
- data-selected={selected ? "true" : undefined}
121
- data-disabled={disabled ? "true" : undefined}
122
- style={{
123
- display: "inline-flex",
124
- alignItems: "center",
125
- gap: 4,
126
- maxWidth: "100%",
127
- boxSizing: "border-box",
128
- padding: onRemove ? "3px 6px 3px 10px" : "3px 10px",
129
- borderRadius: 999,
130
- background: TONE_BG[tone],
131
- border: `1px solid ${selected ? "var(--ck-accent, #4f46e5)" : TONE_BORDER[tone]}`,
132
- boxShadow: selected ? "0 0 0 2px var(--ck-accent-muted, rgba(79, 70, 229, 0.12))" : "none",
133
- font: "500 13px/1.3 var(--ck-font-sans, system-ui, sans-serif)",
134
- color: TONE_FG[tone],
135
- opacity: disabled ? 0.5 : 1,
136
- cursor: disabled ? "default" : undefined,
137
- transition: "border-color var(--ck-dur-fast, 120ms) var(--ck-ease, ease)",
138
- ...style,
139
- }}
140
- {...rest}
141
- >
142
- {onClick ? (
143
- <button
144
- type="button"
145
- className="ck-chip-label"
146
- onMouseDown={keepFocus}
147
- onClick={onClick}
148
- disabled={disabled}
149
- style={{ ...LABEL_STYLE, cursor: clickable ? "pointer" : "default" }}
150
- >
151
- {children}
152
- </button>
153
- ) : (
154
- <span className="ck-chip-label" style={LABEL_STYLE}>
155
- {children}
156
- </span>
157
- )}
158
- {onRemove ? (
159
- <button
160
- type="button"
161
- className="ck-chip-remove"
162
- aria-label={removeLabel ?? "Remove"}
163
- onMouseDown={keepFocus}
164
- onClick={onRemove}
165
- disabled={disabled}
166
- style={{
167
- display: "inline-flex",
168
- alignItems: "center",
169
- justifyContent: "center",
170
- width: 16,
171
- height: 16,
172
- flexShrink: 0,
173
- padding: 0,
174
- margin: 0,
175
- border: "none",
176
- borderRadius: 999,
177
- background: "none",
178
- color: "inherit",
179
- opacity: 0.7,
180
- font: "inherit",
181
- fontSize: 14,
182
- lineHeight: 1,
183
- cursor: disabled ? "default" : "pointer",
184
- }}
185
- >
186
- ×
187
- </button>
188
- ) : null}
189
- </span>
190
- );
191
- });