@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,213 +0,0 @@
1
- import {
2
- cloneElement,
3
- isValidElement,
4
- useCallback,
5
- useEffect,
6
- useRef,
7
- useState,
8
- type CSSProperties,
9
- type ReactElement,
10
- type ReactNode,
11
- } from "react";
12
- import { createPortal } from "react-dom";
13
-
14
- // Lightweight tooltip — replaces the native `title=` attribute when
15
- // you want a short reveal delay (default 120 ms vs the OS' ~500 ms)
16
- // and styling that matches the kit's chrome. Portals to body so it
17
- // floats above transformed parents / fixed bars.
18
- //
19
- // Wraps a single React element child; mouse + focus listeners are
20
- // attached via clone so the trigger keeps its own handlers.
21
-
22
- export interface TooltipProps {
23
- content: ReactNode;
24
- children: ReactElement<{
25
- onMouseEnter?: React.MouseEventHandler;
26
- onMouseLeave?: React.MouseEventHandler;
27
- onFocus?: React.FocusEventHandler;
28
- onBlur?: React.FocusEventHandler;
29
- }>;
30
- // ms before showing. Default 120.
31
- delay?: number;
32
- // "top" hovers above the trigger, "bottom" below, "auto" prefers
33
- // top but flips to bottom when the trigger sits near the viewport
34
- // top edge (would otherwise clip against the browser chrome).
35
- // Default "auto".
36
- placement?: "top" | "bottom" | "auto";
37
- // Render even if `content` is falsy. Default false — callers can
38
- // wrap unconditionally without branching JSX.
39
- alwaysRender?: boolean;
40
- // When true, force the tooltip to a single line and truncate
41
- // overflow with an ellipsis. Default false — content wraps to
42
- // multiple lines within `maxWidth`. Existing single-line labels
43
- // (scan-status pill, date row) render identically because they
44
- // fit within maxWidth without wrapping; longer hints (explanatory
45
- // text under form fields, etc.) now display in full.
46
- truncate?: boolean;
47
- }
48
-
49
- // AUTO_FLIP_THRESHOLD_PX = the vertical space "top" placement needs
50
- // above the trigger (tooltip height + gap + a bit of breathing room).
51
- // Trigger positioned closer than this to the viewport top gets a
52
- // bottom-flip.
53
- const AUTO_FLIP_THRESHOLD_PX = 48;
54
-
55
- export function Tooltip({
56
- content,
57
- children,
58
- delay = 120,
59
- placement = "auto",
60
- alwaysRender = false,
61
- truncate = false,
62
- }: TooltipProps) {
63
- const triggerRef = useRef<HTMLElement | null>(null);
64
- const [open, setOpen] = useState(false);
65
- const [pos, setPos] = useState<{ x: number; y: number } | null>(null);
66
- const [resolvedPlacement, setResolvedPlacement] = useState<"top" | "bottom">(
67
- placement === "bottom" ? "bottom" : "top",
68
- );
69
- const timerRef = useRef<number | null>(null);
70
-
71
- const cancelTimer = useCallback(() => {
72
- if (timerRef.current !== null) {
73
- window.clearTimeout(timerRef.current);
74
- timerRef.current = null;
75
- }
76
- }, []);
77
-
78
- const computePos = useCallback(() => {
79
- const el = triggerRef.current;
80
- if (!el) return;
81
- const r = el.getBoundingClientRect();
82
- // "auto" resolves to top when there's enough room above, else
83
- // bottom. Explicit "top" / "bottom" are honoured verbatim.
84
- let effective: "top" | "bottom";
85
- if (placement === "auto") {
86
- effective = r.top < AUTO_FLIP_THRESHOLD_PX ? "bottom" : "top";
87
- } else {
88
- effective = placement;
89
- }
90
- setResolvedPlacement(effective);
91
- setPos({
92
- x: r.left + r.width / 2,
93
- y: effective === "top" ? r.top : r.bottom,
94
- });
95
- }, [placement]);
96
-
97
- const show = useCallback(() => {
98
- cancelTimer();
99
- timerRef.current = window.setTimeout(() => {
100
- computePos();
101
- setOpen(true);
102
- timerRef.current = null;
103
- }, delay);
104
- }, [cancelTimer, computePos, delay]);
105
-
106
- const hide = useCallback(() => {
107
- cancelTimer();
108
- setOpen(false);
109
- }, [cancelTimer]);
110
-
111
- useEffect(() => {
112
- if (!open) return;
113
- const onScrollResize = () => computePos();
114
- window.addEventListener("scroll", onScrollResize, { passive: true, capture: true });
115
- window.addEventListener("resize", onScrollResize);
116
- return () => {
117
- window.removeEventListener("scroll", onScrollResize, { capture: true });
118
- window.removeEventListener("resize", onScrollResize);
119
- };
120
- }, [open, computePos]);
121
-
122
- useEffect(() => cancelTimer, [cancelTimer]);
123
-
124
- if (!isValidElement(children)) return children;
125
- if (!content && !alwaysRender) return children;
126
-
127
- const child = children;
128
- // cloneElement's `Partial<P>` signature isn't compatible with the
129
- // `T | undefined` event-handler types in HTMLAttributes once
130
- // `exactOptionalPropertyTypes: true` is on. The runtime behaviour is fine —
131
- // React happily accepts these props on any host element. We narrow via
132
- // unknown to silence the type system on a known React-types limitation.
133
- const clonedProps = {
134
- ref: (el: HTMLElement | null) => {
135
- triggerRef.current = el;
136
- const childRef = (child as unknown as { ref?: React.Ref<HTMLElement> }).ref;
137
- if (typeof childRef === "function") childRef(el);
138
- else if (childRef && typeof childRef === "object") {
139
- (childRef as { current: HTMLElement | null }).current = el;
140
- }
141
- },
142
- onMouseEnter: (e: React.MouseEvent) => {
143
- child.props.onMouseEnter?.(e);
144
- show();
145
- },
146
- onMouseLeave: (e: React.MouseEvent) => {
147
- child.props.onMouseLeave?.(e);
148
- hide();
149
- },
150
- onFocus: (e: React.FocusEvent) => {
151
- child.props.onFocus?.(e);
152
- show();
153
- },
154
- onBlur: (e: React.FocusEvent) => {
155
- child.props.onBlur?.(e);
156
- hide();
157
- },
158
- };
159
- const clonedTrigger = cloneElement(
160
- child,
161
- clonedProps as unknown as Parameters<typeof cloneElement>[1],
162
- );
163
-
164
- const tooltipStyle: CSSProperties = {
165
- position: "fixed",
166
- left: pos?.x ?? -9999,
167
- top: pos?.y ?? -9999,
168
- transform:
169
- resolvedPlacement === "top"
170
- ? "translate(-50%, calc(-100% - 8px))"
171
- : "translate(-50%, 8px)",
172
- background: "var(--ck-bg-surface)",
173
- color: "var(--ck-text-primary)",
174
- border: "1px solid var(--ck-border-strong)",
175
- borderRadius: "var(--ck-radius-sm)",
176
- boxShadow: "var(--ck-shadow-2)",
177
- padding: "6px 10px",
178
- fontSize: 11,
179
- fontFamily: "var(--ck-font-mono)",
180
- lineHeight: 1.45,
181
- letterSpacing: "0.02em",
182
- pointerEvents: "none",
183
- zIndex: 9999,
184
- maxWidth: 320,
185
- // Default: wrap long hints. Opt-in `truncate` restores the old
186
- // single-line-with-ellipsis behaviour for compact labels where
187
- // horizontal overflow is preferable to a taller tooltip (e.g.
188
- // dense toolbars). Single-line labels shorter than maxWidth
189
- // render identically in both modes — the branch only matters
190
- // when content would need multiple lines.
191
- ...(truncate
192
- ? {
193
- whiteSpace: "nowrap" as const,
194
- overflow: "hidden" as const,
195
- textOverflow: "ellipsis" as const,
196
- }
197
- : {
198
- whiteSpace: "normal" as const,
199
- wordBreak: "break-word" as const,
200
- }),
201
- opacity: open && pos ? 1 : 0,
202
- transition: "opacity 120ms ease",
203
- };
204
-
205
- return (
206
- <>
207
- {clonedTrigger}
208
- {(open || alwaysRender) &&
209
- pos &&
210
- createPortal(<div style={tooltipStyle}>{content}</div>, document.body)}
211
- </>
212
- );
213
- }
@@ -1,53 +0,0 @@
1
- export { Button } from "./Button";
2
- export type { ButtonProps, ButtonVariant } from "./Button";
3
- export { Card } from "./Card";
4
- export type { CardProps } from "./Card";
5
- export { Tag } from "./Tag";
6
- export type { TagProps, TagTone } from "./Tag";
7
- export { CountBadge } from "./CountBadge";
8
- export type { CountBadgeProps } from "./CountBadge";
9
- export { Kbd } from "./Kbd";
10
- export type { KbdProps } from "./Kbd";
11
- export { Avatar } from "./Avatar";
12
- export type { AvatarProps } from "./Avatar";
13
- export { Input } from "./Input";
14
- export type { InputProps } from "./Input";
15
- export { Select } from "./Select";
16
- export type { SelectProps, SelectOption } from "./Select";
17
- export { Combobox } from "./Combobox";
18
- export type {
19
- ComboboxProps,
20
- ComboboxOption,
21
- ComboboxCommit,
22
- ComboboxCommitted,
23
- ComboboxHandle,
24
- } from "./Combobox";
25
- export { Chip } from "./Chip";
26
- export type { ChipProps, ChipTone } from "./Chip";
27
- export { MultiCombobox } from "./MultiCombobox";
28
- export type {
29
- MultiComboboxProps,
30
- MultiComboboxEntry,
31
- MultiComboboxHandle,
32
- MultiComboboxLayout,
33
- } from "./MultiCombobox";
34
- export { Textarea } from "./Textarea";
35
- export type { TextareaProps } from "./Textarea";
36
- export { Checkbox } from "./Checkbox";
37
- export type { CheckboxProps } from "./Checkbox";
38
- export { Radio } from "./Radio";
39
- export type { RadioProps } from "./Radio";
40
- export { ToggleSwitch } from "./ToggleSwitch";
41
- export type { ToggleSwitchProps } from "./ToggleSwitch";
42
- export { Tooltip } from "./Tooltip";
43
- export type { TooltipProps } from "./Tooltip";
44
- export { PageHeader } from "./PageHeader";
45
- export type { PageHeaderProps } from "./PageHeader";
46
- export { MentionCombobox, findActiveMentionStart, extractQuery } from "./MentionCombobox";
47
- export type { MentionComboboxProps, MentionComboboxHandle } from "./MentionCombobox";
48
- export { SignaturePad } from "./SignaturePad";
49
- export type { SignaturePadProps, SignaturePadHandle } from "./SignaturePad";
50
- export { CopyField } from "./CopyField";
51
- export type { CopyFieldProps } from "./CopyField";
52
- export { SegmentedControl } from "./SegmentedControl";
53
- export type { SegmentedControlProps, SegmentedControlOption } from "./SegmentedControl";
@@ -1,132 +0,0 @@
1
- import { useEffect, useState } from "react";
2
- import { Button } from "../primitives/Button";
3
-
4
- // Surfaces the browser's PWA install affordance — `beforeinstallprompt`
5
- // is the Chromium-side hook that fires when a site qualifies for
6
- // install. Without intervention browsers eventually suppress it; we
7
- // catch the event, render a dismissable banner, and trigger the
8
- // prompt only when the user clicks Install.
9
- //
10
- // Dismissal persists in localStorage so the banner doesn't reappear
11
- // on every page load after the user said no thanks.
12
- //
13
- // iOS Safari doesn't fire beforeinstallprompt at all — for that
14
- // platform you need to show your own "Tap share → Add to Home
15
- // Screen" instructions. Detect via `iosSafari` prop or your own
16
- // heuristic and render a different banner if needed.
17
-
18
- interface BeforeInstallPromptEvent extends Event {
19
- prompt: () => Promise<void>;
20
- userChoice: Promise<{ outcome: "accepted" | "dismissed" }>;
21
- }
22
-
23
- export interface InstallPromptBannerProps {
24
- // Heading text.
25
- title?: string;
26
- // Body text below the heading.
27
- message?: string;
28
- installLabel?: string;
29
- dismissLabel?: string;
30
- // localStorage key for the dismissal flag.
31
- storageKey?: string;
32
- // Called after the user installs (accepted) or dismisses.
33
- onResolve?: (outcome: "accepted" | "dismissed") => void;
34
- }
35
-
36
- export function InstallPromptBanner({
37
- title = "Install this app",
38
- message = "Add it to your home screen for a faster, full-screen experience.",
39
- installLabel = "Install",
40
- dismissLabel = "Not now",
41
- storageKey = "ck-install-prompt-dismissed",
42
- onResolve,
43
- }: InstallPromptBannerProps) {
44
- const [evt, setEvt] = useState<BeforeInstallPromptEvent | null>(null);
45
- const [dismissed, setDismissed] = useState(() => {
46
- if (typeof window === "undefined") return true;
47
- return window.localStorage.getItem(storageKey) === "1";
48
- });
49
-
50
- useEffect(() => {
51
- if (dismissed) return;
52
- const onBefore = (e: Event) => {
53
- e.preventDefault();
54
- setEvt(e as BeforeInstallPromptEvent);
55
- };
56
- window.addEventListener("beforeinstallprompt", onBefore);
57
- return () => window.removeEventListener("beforeinstallprompt", onBefore);
58
- }, [dismissed]);
59
-
60
- const dismiss = () => {
61
- window.localStorage.setItem(storageKey, "1");
62
- setDismissed(true);
63
- setEvt(null);
64
- onResolve?.("dismissed");
65
- };
66
-
67
- const install = async () => {
68
- if (!evt) return;
69
- await evt.prompt();
70
- const choice = await evt.userChoice;
71
- setEvt(null);
72
- if (choice.outcome === "accepted") {
73
- window.localStorage.setItem(storageKey, "1");
74
- setDismissed(true);
75
- }
76
- onResolve?.(choice.outcome);
77
- };
78
-
79
- if (dismissed || !evt) return null;
80
-
81
- return (
82
- <div
83
- role="region"
84
- aria-label="Install app"
85
- style={{
86
- position: "fixed",
87
- left: 16,
88
- right: 16,
89
- bottom: `calc(16px + var(--ck-tabbar-height, 0px))`,
90
- zIndex: 90,
91
- maxWidth: 480,
92
- marginLeft: "auto",
93
- marginRight: "auto",
94
- background: "var(--ck-bg-surface)",
95
- border: "1px solid var(--ck-border-subtle)",
96
- borderRadius: "var(--ck-radius-md)",
97
- boxShadow: "var(--ck-shadow-2)",
98
- padding: "14px 16px",
99
- display: "flex",
100
- gap: 12,
101
- alignItems: "center",
102
- fontFamily: "var(--ck-font-sans)",
103
- }}
104
- >
105
- <div style={{ flex: 1, minWidth: 0 }}>
106
- <div
107
- style={{
108
- font: "500 14px/1.3 var(--ck-font-sans)",
109
- color: "var(--ck-text-primary)",
110
- marginBottom: 2,
111
- }}
112
- >
113
- {title}
114
- </div>
115
- <div
116
- style={{
117
- font: "400 12px/1.4 var(--ck-font-sans)",
118
- color: "var(--ck-text-tertiary)",
119
- }}
120
- >
121
- {message}
122
- </div>
123
- </div>
124
- <Button variant="ghost" onClick={dismiss}>
125
- {dismissLabel}
126
- </Button>
127
- <Button variant="primary" onClick={install}>
128
- {installLabel}
129
- </Button>
130
- </div>
131
- );
132
- }
package/src/pwa/index.ts DELETED
@@ -1,4 +0,0 @@
1
- export { InstallPromptBanner } from "./InstallPromptBanner";
2
- export type { InstallPromptBannerProps } from "./InstallPromptBanner";
3
- export { registerSW } from "./registerSW";
4
- export type { RegisterSWOptions } from "./registerSW";
@@ -1,20 +0,0 @@
1
- {
2
- "name": "Your App Name",
3
- "short_name": "AppName",
4
- "description": "One-line app description.",
5
- "start_url": "/",
6
- "display": "standalone",
7
- "orientation": "portrait",
8
- "theme_color": "#0B0D12",
9
- "background_color": "#0B0D12",
10
- "icons": [
11
- { "src": "/icon-192.png", "sizes": "192x192", "type": "image/png" },
12
- { "src": "/icon-512.png", "sizes": "512x512", "type": "image/png" },
13
- {
14
- "src": "/icon-512-maskable.png",
15
- "sizes": "512x512",
16
- "type": "image/png",
17
- "purpose": "maskable"
18
- }
19
- ]
20
- }
@@ -1,55 +0,0 @@
1
- // Thin wrapper around vite-plugin-pwa's `virtual:pwa-register`. Use
2
- // from your app's entry point (main.tsx) so the service worker is
3
- // registered on first paint. Behaviour:
4
- //
5
- // - new SW detected → optional onNeedRefresh callback (typically
6
- // toast prompting reload)
7
- // - SW activated for the first time → onOfflineReady callback
8
- // (typically toast announcing "Ready offline")
9
- //
10
- // The kit doesn't pull in vite-plugin-pwa as a dep — the consumer
11
- // app installs and configures it. This wrapper just centralises
12
- // the registration boilerplate so consumers don't repeat it.
13
-
14
- export interface RegisterSWOptions {
15
- onNeedRefresh?: () => void;
16
- onOfflineReady?: () => void;
17
- onRegistered?: (registration: ServiceWorkerRegistration | undefined) => void;
18
- onRegisterError?: (error: unknown) => void;
19
- }
20
-
21
- // Specifier is constructed from a runtime expression so Vite's
22
- // static import analyser can't see "virtual:pwa-register" as a
23
- // literal — without this, every consumer app would fail to boot
24
- // unless they had vite-plugin-pwa installed AND configured (since
25
- // transform happens before the try/catch runs at runtime).
26
- //
27
- // The eval is the simplest indirection that's also obviously a
28
- // no-op to readers. /* @vite-ignore */ on its own isn't enough
29
- // because Vite still attempts resolution when the specifier is a
30
- // constant string literal.
31
- const PWA_REGISTER_SPECIFIER = ["virtual", "pwa-register"].join(":");
32
-
33
- export async function registerSW(opts: RegisterSWOptions = {}) {
34
- if (typeof window === "undefined") return;
35
- try {
36
- const mod = await import(/* @vite-ignore */ PWA_REGISTER_SPECIFIER);
37
- if (typeof mod.registerSW !== "function") {
38
- console.warn("virtual:pwa-register found but registerSW() is not a function");
39
- return;
40
- }
41
- mod.registerSW({
42
- immediate: true,
43
- onNeedRefresh: opts.onNeedRefresh,
44
- onOfflineReady: opts.onOfflineReady,
45
- onRegisteredSW: (_url: string, reg: ServiceWorkerRegistration | undefined) => {
46
- opts.onRegistered?.(reg);
47
- },
48
- onRegisterError: opts.onRegisterError,
49
- });
50
- } catch (err) {
51
- // vite-plugin-pwa not installed / configured. Silent in dev,
52
- // surface to caller if they care.
53
- opts.onRegisterError?.(err);
54
- }
55
- }
@@ -1,85 +0,0 @@
1
- import type { CSSProperties, ReactNode } from "react";
2
-
3
- // Halftone-tinted image / fill block. Renders a solid ink-color
4
- // background with a halftone dot pattern overlay; if `src` is
5
- // provided, the image sits underneath in `mix-blend-mode: multiply`
6
- // for a duotone-on-paper feel.
7
- //
8
- // Use for hero blocks, empty-state illustrations, photo treatments.
9
-
10
- export interface HalftoneProps {
11
- children?: ReactNode;
12
- // Optional image source. The image is duotoned via blend modes.
13
- src?: string;
14
- alt?: string;
15
- // Ink color of the dot fill. Defaults to riso pink.
16
- ink?: string;
17
- // Dot size in px. Defaults to 1.6.
18
- dotSize?: number;
19
- // Dot grid spacing in px. Defaults to 7.
20
- dotSpacing?: number;
21
- width?: number | string;
22
- height?: number | string;
23
- className?: string;
24
- style?: CSSProperties;
25
- }
26
-
27
- export function Halftone({
28
- children,
29
- src,
30
- alt = "",
31
- ink = "var(--ck-riso-pink, #FF48B0)",
32
- dotSize = 1.6,
33
- dotSpacing = 7,
34
- width = "100%",
35
- height = 200,
36
- className,
37
- style,
38
- }: HalftoneProps) {
39
- return (
40
- <div
41
- className={className}
42
- style={{
43
- position: "relative",
44
- width,
45
- height,
46
- background: ink,
47
- overflow: "hidden",
48
- borderRadius: 4,
49
- ...style,
50
- }}
51
- >
52
- {src && (
53
- <img
54
- src={src}
55
- alt={alt}
56
- style={{
57
- position: "absolute",
58
- inset: 0,
59
- width: "100%",
60
- height: "100%",
61
- objectFit: "cover",
62
- mixBlendMode: "multiply",
63
- filter: "grayscale(1) contrast(1.15)",
64
- pointerEvents: "none",
65
- }}
66
- />
67
- )}
68
- <div
69
- aria-hidden
70
- style={{
71
- position: "absolute",
72
- inset: 0,
73
- pointerEvents: "none",
74
- backgroundImage: `radial-gradient(circle, rgba(26,26,26,0.85) ${dotSize}px, transparent ${dotSize + 0.2}px)`,
75
- backgroundSize: `${dotSpacing}px ${dotSpacing}px`,
76
- mixBlendMode: "multiply",
77
- opacity: 0.55,
78
- }}
79
- />
80
- {children && (
81
- <div style={{ position: "relative", zIndex: 2, color: "#1A1A1A" }}>{children}</div>
82
- )}
83
- </div>
84
- );
85
- }
@@ -1,63 +0,0 @@
1
- import type { CSSProperties, ReactNode } from "react";
2
-
3
- // Misregistration — render text (or any child) twice, with a small
4
- // offset between an "under" duplicate in a second ink color and the
5
- // "top" pass in the primary ink. The signature riso effect.
6
- //
7
- // The under copy is set aria-hidden so screen readers don't double-
8
- // read the content.
9
-
10
- export interface MisregisterProps {
11
- children?: ReactNode;
12
- // Ink color of the foreground (top) pass.
13
- ink?: string;
14
- // Ink color of the misregistered duplicate. Defaults to riso pink.
15
- offsetInk?: string;
16
- // Offset of the duplicate, in px. Defaults to {x:3, y:3}.
17
- offsetX?: number;
18
- offsetY?: number;
19
- // Wrap in inline-block? Defaults true. Pass false for block use.
20
- inline?: boolean;
21
- className?: string;
22
- style?: CSSProperties;
23
- }
24
-
25
- export function Misregister({
26
- children,
27
- ink,
28
- offsetInk = "var(--ck-riso-pink, #FF48B0)",
29
- offsetX = 3,
30
- offsetY = 3,
31
- inline = true,
32
- className,
33
- style,
34
- }: MisregisterProps) {
35
- return (
36
- <span
37
- className={className}
38
- style={{
39
- display: inline ? "inline-block" : "block",
40
- position: "relative",
41
- color: ink ?? "var(--ck-text-primary)",
42
- ...style,
43
- }}
44
- >
45
- <span
46
- aria-hidden
47
- style={{
48
- position: "absolute",
49
- top: offsetY,
50
- left: offsetX,
51
- color: offsetInk,
52
- mixBlendMode: "multiply",
53
- pointerEvents: "none",
54
- width: "100%",
55
- height: "100%",
56
- }}
57
- >
58
- {children}
59
- </span>
60
- <span style={{ position: "relative" }}>{children}</span>
61
- </span>
62
- );
63
- }