raft-ui 0.0.5 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -0
- package/dist/button-CJqMhTPQ.mjs +621 -0
- package/dist/index-CpGciYPW.d.mts +267 -0
- package/dist/index.d.mts +11 -253
- package/dist/index.mjs +32 -587
- package/{src → dist}/styles.css +0 -2
- package/dist/wip.d.mts +133 -15
- package/dist/wip.mjs +289 -16
- package/package.json +7 -10
- package/dist/styled-props-B3SNekJg.d.mts +0 -16
- package/dist/use-theme-Y_h1DKll.mjs +0 -34
- package/src/components/avatar/avatar.tsx +0 -375
- package/src/components/avatar/index.ts +0 -17
- package/src/components/badge/badge.tsx +0 -393
- package/src/components/badge/index.ts +0 -2
- package/src/components/banner/banner.tsx +0 -269
- package/src/components/banner/index.ts +0 -8
- package/src/components/bottom-sheet/bottom-sheet.tsx +0 -152
- package/src/components/bottom-sheet/index.ts +0 -22
- package/src/components/button/button.tsx +0 -497
- package/src/components/button/index.ts +0 -2
- package/src/components/checkbox/checkbox-indicator.tsx +0 -128
- package/src/components/checkbox/checkbox.tsx +0 -350
- package/src/components/checkbox/index.ts +0 -2
- package/src/components/combobox/combobox.tsx +0 -603
- package/src/components/combobox/index.ts +0 -52
- package/src/components/context-menu/context-menu.tsx +0 -380
- package/src/components/context-menu/index.ts +0 -34
- package/src/components/conversation-preview-card/conversation-preview-card.tsx +0 -336
- package/src/components/conversation-preview-card/index.ts +0 -32
- package/src/components/description-list/description-list.tsx +0 -116
- package/src/components/description-list/index.ts +0 -14
- package/src/components/dropdown-menu/dropdown-menu.tsx +0 -405
- package/src/components/dropdown-menu/index.ts +0 -38
- package/src/components/empty-state/empty-state.tsx +0 -177
- package/src/components/empty-state/index.ts +0 -16
- package/src/components/field/field.tsx +0 -125
- package/src/components/field/index.ts +0 -18
- package/src/components/inline-code/index.ts +0 -2
- package/src/components/inline-code/inline-code.tsx +0 -35
- package/src/components/input/index.ts +0 -2
- package/src/components/input/input.tsx +0 -76
- package/src/components/input-group/index.ts +0 -7
- package/src/components/input-group/input-group.tsx +0 -215
- package/src/components/kbd/index.ts +0 -2
- package/src/components/kbd/kbd.tsx +0 -43
- package/src/components/label/index.ts +0 -7
- package/src/components/label/label.tsx +0 -99
- package/src/components/lightbox/index.ts +0 -24
- package/src/components/lightbox/lightbox.tsx +0 -245
- package/src/components/list-item/index.ts +0 -20
- package/src/components/list-item/list-item.tsx +0 -263
- package/src/components/media-list-item/index.ts +0 -20
- package/src/components/media-list-item/media-list-item.tsx +0 -169
- package/src/components/notification-center/index.ts +0 -39
- package/src/components/notification-center/notification-center.tsx +0 -558
- package/src/components/panel-header/index.ts +0 -22
- package/src/components/panel-header/panel-header.tsx +0 -199
- package/src/components/popover/index.ts +0 -30
- package/src/components/popover/popover.tsx +0 -248
- package/src/components/preview-shell/index.ts +0 -2
- package/src/components/preview-shell/preview-shell.tsx +0 -51
- package/src/components/quoted-message-card/index.ts +0 -36
- package/src/components/quoted-message-card/quoted-message-card.tsx +0 -391
- package/src/components/radio-group/index.ts +0 -6
- package/src/components/radio-group/radio-group.tsx +0 -119
- package/src/components/section-header/index.ts +0 -16
- package/src/components/section-header/section-header.tsx +0 -117
- package/src/components/section-label/index.ts +0 -2
- package/src/components/section-label/section-label.tsx +0 -38
- package/src/components/segmented-control/index.ts +0 -12
- package/src/components/segmented-control/segmented-control.tsx +0 -129
- package/src/components/select/index.ts +0 -40
- package/src/components/select/select.tsx +0 -382
- package/src/components/spinner/index.ts +0 -2
- package/src/components/spinner/spinner.tsx +0 -228
- package/src/components/status/index.ts +0 -2
- package/src/components/status/status.tsx +0 -152
- package/src/components/tabs/index.ts +0 -24
- package/src/components/tabs/tabs.tsx +0 -451
- package/src/components/text/index.ts +0 -2
- package/src/components/text/text.tsx +0 -131
- package/src/components/textarea/index.ts +0 -2
- package/src/components/textarea/textarea.tsx +0 -136
- package/src/components/toggle-group/index.ts +0 -12
- package/src/components/toggle-group/toggle-group.tsx +0 -111
- package/src/components/tooltip/index.ts +0 -14
- package/src/components/tooltip/tooltip.tsx +0 -212
- package/src/index.ts +0 -426
- package/src/legacy/AttentionDot.tsx +0 -28
- package/src/legacy/AvatarListRow.tsx +0 -87
- package/src/legacy/AvatarSlot.tsx +0 -157
- package/src/legacy/Badge.tsx +0 -95
- package/src/legacy/Banner.tsx +0 -74
- package/src/legacy/BottomSheet.tsx +0 -76
- package/src/legacy/CheckMarker.tsx +0 -72
- package/src/legacy/Checkbox.tsx +0 -81
- package/src/legacy/ConversationPreviewCard.tsx +0 -169
- package/src/legacy/DismissBackdrop.tsx +0 -68
- package/src/legacy/EmptyState.tsx +0 -63
- package/src/legacy/ExperimentalBadge.tsx +0 -19
- package/src/legacy/FormField.tsx +0 -106
- package/src/legacy/KeyValueRow.tsx +0 -67
- package/src/legacy/Lightbox.tsx +0 -119
- package/src/legacy/NotificationCenter.tsx +0 -217
- package/src/legacy/PanelHeader.tsx +0 -94
- package/src/legacy/PreviewShell.tsx +0 -40
- package/src/legacy/QuotedMessageCard.tsx +0 -181
- package/src/legacy/ReorderablePanelTabs.tsx +0 -156
- package/src/legacy/SandboxedPreviewFrame.tsx +0 -59
- package/src/legacy/SearchInput.tsx +0 -23
- package/src/legacy/SectionEyebrow.tsx +0 -60
- package/src/legacy/SectionHeader.tsx +0 -68
- package/src/legacy/SegmentedControl.tsx +0 -74
- package/src/legacy/SlugInput.tsx +0 -29
- package/src/legacy/Spinner.tsx +0 -89
- package/src/legacy/StatusDot.tsx +0 -51
- package/src/legacy/SurfaceListItem.tsx +0 -32
- package/src/legacy/Textarea.tsx +0 -137
- package/src/legacy/button.tsx +0 -100
- package/src/legacy/context-menu-divider.tsx +0 -12
- package/src/legacy/menu-item.tsx +0 -66
- package/src/legacy/selection-popover.tsx +0 -212
- package/src/legacy/server-switcher-menu.tsx +0 -203
- package/src/legacy/source.css +0 -240
- package/src/lib/cn.ts +0 -1
- package/src/lib/styled-props.ts +0 -11
- package/src/lib/theme/theme-context.ts +0 -28
- package/src/lib/theme/theme-provider.tsx +0 -181
- package/src/lib/theme/use-theme.ts +0 -12
- package/src/lib/tv.ts +0 -29
- package/src/lib/without-children.ts +0 -6
- package/src/wip.ts +0 -233
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
import { useEffect, useRef, type ComponentPropsWithRef, type ReactNode } from "react";
|
|
2
|
-
import { Button as BaseButton } from "@base-ui/react/button";
|
|
3
|
-
import { Check, Plus } from "lucide-react";
|
|
4
|
-
import { tv } from "../lib/tv.ts";
|
|
5
|
-
import { useThemeFamily } from "../lib/theme/use-theme.ts";
|
|
6
|
-
|
|
7
|
-
// "Switch server" dropdown content: joined servers + join-community actions + a
|
|
8
|
-
// "switch or create" action. App-domain data and navigation stay caller-owned,
|
|
9
|
-
// but the source menu's UI primitive contract (open gate, outside click, ESC,
|
|
10
|
-
// current unread hiding) is preserved here. `card-brutal` maps to border-2
|
|
11
|
-
// border-black bg-white --shadow-hover.
|
|
12
|
-
const serverSwitcherMenu = tv({
|
|
13
|
-
slots: {
|
|
14
|
-
root: ["z-50 flex flex-col overflow-hidden"],
|
|
15
|
-
scroller: ["min-h-0 flex-1 overflow-y-auto overscroll-contain"],
|
|
16
|
-
serverRow: [
|
|
17
|
-
"flex w-full items-center gap-2 px-3 py-2 [@media(max-height:600px)]:py-1",
|
|
18
|
-
"text-sm transition-colors",
|
|
19
|
-
],
|
|
20
|
-
check: ["shrink-0"],
|
|
21
|
-
serverText: ["min-w-0 flex-1 text-left"],
|
|
22
|
-
serverName: ["truncate"],
|
|
23
|
-
serverSlug: ["truncate font-mono text-xs text-black/40"],
|
|
24
|
-
unread: [
|
|
25
|
-
"ml-auto shrink-0 rounded border border-black bg-brutal-pink px-1.5 py-0.5",
|
|
26
|
-
"text-[10px] font-bold leading-none text-white",
|
|
27
|
-
],
|
|
28
|
-
actionRow: [
|
|
29
|
-
"flex w-full items-center justify-start gap-2 px-3 py-2 [@media(max-height:600px)]:py-1",
|
|
30
|
-
"text-left text-sm transition-colors",
|
|
31
|
-
],
|
|
32
|
-
actionLabel: ["min-w-0 flex-1 text-left"],
|
|
33
|
-
},
|
|
34
|
-
variants: {
|
|
35
|
-
theme: {
|
|
36
|
-
brutal: {
|
|
37
|
-
root: ["border-2 border-black bg-white shadow-[var(--shadow-hover)]"],
|
|
38
|
-
serverRow: ["font-medium text-black hover:bg-brutal-yellow"],
|
|
39
|
-
actionRow: ["font-bold text-black hover:bg-brutal-pink"],
|
|
40
|
-
},
|
|
41
|
-
// Elegant styling is deferred; see the component TODO.
|
|
42
|
-
elegant: {},
|
|
43
|
-
},
|
|
44
|
-
current: {
|
|
45
|
-
true: {
|
|
46
|
-
serverRow: "font-bold",
|
|
47
|
-
check: "opacity-100",
|
|
48
|
-
},
|
|
49
|
-
false: {
|
|
50
|
-
serverRow: "",
|
|
51
|
-
check: "opacity-0",
|
|
52
|
-
},
|
|
53
|
-
},
|
|
54
|
-
},
|
|
55
|
-
defaultVariants: {
|
|
56
|
-
current: false,
|
|
57
|
-
},
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
export type ServerSwitcherServer = {
|
|
61
|
-
id: string;
|
|
62
|
-
name: ReactNode;
|
|
63
|
-
slug: string;
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
export type ServerSwitcherJoinOption = {
|
|
67
|
-
slug: string;
|
|
68
|
-
label: ReactNode;
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
export type ServerSwitcherMenuProps = Omit<ComponentPropsWithRef<"div">, "children"> & {
|
|
72
|
-
/** Matches the source menu's open gate. Omit to render as plain composed content. */
|
|
73
|
-
open?: boolean;
|
|
74
|
-
/** Called by outside click, ESC, server select, and switch/create. */
|
|
75
|
-
onClose?: () => void;
|
|
76
|
-
servers: ServerSwitcherServer[];
|
|
77
|
-
currentServerId?: string;
|
|
78
|
-
/** Unread counts keyed by server id. The current server hides its own count. */
|
|
79
|
-
serverUnreadCounts?: Record<string, number>;
|
|
80
|
-
onSelectServer: (server: ServerSwitcherServer) => void;
|
|
81
|
-
/** Community join actions, rendered above "Switch or create server". */
|
|
82
|
-
joinOptions?: ServerSwitcherJoinOption[];
|
|
83
|
-
onJoinCommunity?: (slug: string) => void;
|
|
84
|
-
onSwitchOrCreate?: () => void;
|
|
85
|
-
testId?: string;
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
export function ServerSwitcherMenu({
|
|
89
|
-
servers,
|
|
90
|
-
currentServerId,
|
|
91
|
-
serverUnreadCounts = {},
|
|
92
|
-
onSelectServer,
|
|
93
|
-
joinOptions = [],
|
|
94
|
-
onJoinCommunity,
|
|
95
|
-
onSwitchOrCreate,
|
|
96
|
-
open = true,
|
|
97
|
-
onClose,
|
|
98
|
-
className,
|
|
99
|
-
testId = "server-switcher-menu",
|
|
100
|
-
onMouseDown,
|
|
101
|
-
...props
|
|
102
|
-
}: ServerSwitcherMenuProps) {
|
|
103
|
-
const theme = useThemeFamily();
|
|
104
|
-
const menuRef = useRef<HTMLDivElement | null>(null);
|
|
105
|
-
const onCloseRef = useRef(onClose);
|
|
106
|
-
const { root, scroller, actionRow, actionLabel } = serverSwitcherMenu({ theme });
|
|
107
|
-
|
|
108
|
-
useEffect(() => {
|
|
109
|
-
onCloseRef.current = onClose;
|
|
110
|
-
}, [onClose]);
|
|
111
|
-
|
|
112
|
-
useEffect(() => {
|
|
113
|
-
if (!open || !onClose) return;
|
|
114
|
-
const handleMouseDown = (event: MouseEvent) => {
|
|
115
|
-
if (menuRef.current?.contains(event.target as Node)) return;
|
|
116
|
-
onCloseRef.current?.();
|
|
117
|
-
};
|
|
118
|
-
const handleKeyDown = (event: KeyboardEvent) => {
|
|
119
|
-
if (event.key === "Escape") onCloseRef.current?.();
|
|
120
|
-
};
|
|
121
|
-
document.addEventListener("mousedown", handleMouseDown);
|
|
122
|
-
document.addEventListener("keydown", handleKeyDown);
|
|
123
|
-
return () => {
|
|
124
|
-
document.removeEventListener("mousedown", handleMouseDown);
|
|
125
|
-
document.removeEventListener("keydown", handleKeyDown);
|
|
126
|
-
};
|
|
127
|
-
}, [open, onClose]);
|
|
128
|
-
|
|
129
|
-
if (!open) return null;
|
|
130
|
-
|
|
131
|
-
return (
|
|
132
|
-
<div
|
|
133
|
-
{...props}
|
|
134
|
-
ref={menuRef}
|
|
135
|
-
data-slot="server-switcher-menu"
|
|
136
|
-
data-testid={testId}
|
|
137
|
-
className={root({ className })}
|
|
138
|
-
onMouseDown={(event) => {
|
|
139
|
-
onMouseDown?.(event);
|
|
140
|
-
event.stopPropagation();
|
|
141
|
-
}}
|
|
142
|
-
>
|
|
143
|
-
<div className={scroller()}>
|
|
144
|
-
{servers.map((server) => {
|
|
145
|
-
const isCurrent = server.id === currentServerId;
|
|
146
|
-
const unread = serverUnreadCounts[server.id] || 0;
|
|
147
|
-
const {
|
|
148
|
-
serverRow: serverRowSlot,
|
|
149
|
-
check,
|
|
150
|
-
serverText,
|
|
151
|
-
serverName,
|
|
152
|
-
serverSlug,
|
|
153
|
-
unread: unreadSlot,
|
|
154
|
-
} = serverSwitcherMenu({ theme, current: isCurrent });
|
|
155
|
-
return (
|
|
156
|
-
<BaseButton
|
|
157
|
-
key={server.id}
|
|
158
|
-
type="button"
|
|
159
|
-
onClick={() => {
|
|
160
|
-
onSelectServer(server);
|
|
161
|
-
onClose?.();
|
|
162
|
-
}}
|
|
163
|
-
className={serverRowSlot()}
|
|
164
|
-
>
|
|
165
|
-
<Check size={14} className={check()} />
|
|
166
|
-
<div className={serverText()}>
|
|
167
|
-
<div className={serverName()}>{server.name}</div>
|
|
168
|
-
<div className={serverSlug()}>/{server.slug}</div>
|
|
169
|
-
</div>
|
|
170
|
-
{!isCurrent && unread > 0 && <span className={unreadSlot()}>{unread}</span>}
|
|
171
|
-
</BaseButton>
|
|
172
|
-
);
|
|
173
|
-
})}
|
|
174
|
-
</div>
|
|
175
|
-
|
|
176
|
-
<div className="border-t-2 border-black" />
|
|
177
|
-
|
|
178
|
-
{joinOptions.map((option) => (
|
|
179
|
-
<BaseButton
|
|
180
|
-
key={option.slug}
|
|
181
|
-
type="button"
|
|
182
|
-
onClick={() => onJoinCommunity?.(option.slug)}
|
|
183
|
-
className={actionRow()}
|
|
184
|
-
>
|
|
185
|
-
<Plus size={14} />
|
|
186
|
-
<span className={actionLabel()}>{option.label}</span>
|
|
187
|
-
</BaseButton>
|
|
188
|
-
))}
|
|
189
|
-
|
|
190
|
-
<BaseButton
|
|
191
|
-
type="button"
|
|
192
|
-
onClick={() => {
|
|
193
|
-
onClose?.();
|
|
194
|
-
onSwitchOrCreate?.();
|
|
195
|
-
}}
|
|
196
|
-
className={actionRow()}
|
|
197
|
-
>
|
|
198
|
-
<Plus size={14} />
|
|
199
|
-
<span className={actionLabel()}>Switch or Create Server</span>
|
|
200
|
-
</BaseButton>
|
|
201
|
-
</div>
|
|
202
|
-
);
|
|
203
|
-
}
|
package/src/legacy/source.css
DELETED
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
.btn-brutal,
|
|
2
|
-
.btn-brutal-sm,
|
|
3
|
-
.card-brutal,
|
|
4
|
-
.input-brutal {
|
|
5
|
-
--shadow: 2px 2px 0px var(--color-black);
|
|
6
|
-
--shadow-hover: 4px 4px 0px var(--color-black);
|
|
7
|
-
--shadow-active: 1px 1px 0px var(--color-black);
|
|
8
|
-
--shadow-lg: 6px 6px 0px var(--color-black);
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
.card-brutal {
|
|
12
|
-
border: 2px solid var(--color-black);
|
|
13
|
-
background-color: var(--color-white);
|
|
14
|
-
box-shadow: var(--shadow-hover);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
.btn-brutal {
|
|
18
|
-
border: 2px solid var(--color-black);
|
|
19
|
-
font-family: var(--heading-font);
|
|
20
|
-
font-weight: 700;
|
|
21
|
-
box-shadow: var(--shadow-hover);
|
|
22
|
-
transition-property: all;
|
|
23
|
-
transition-duration: 100ms;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
.btn-brutal:hover {
|
|
27
|
-
transform: translateY(-1px);
|
|
28
|
-
box-shadow: var(--shadow-lg);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
.btn-brutal:active {
|
|
32
|
-
transform: translate(2px, 2px);
|
|
33
|
-
box-shadow: var(--shadow-active);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
.btn-brutal:disabled {
|
|
37
|
-
pointer-events: none;
|
|
38
|
-
cursor: not-allowed;
|
|
39
|
-
opacity: 0.5;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
.btn-brutal-sm {
|
|
43
|
-
border: 2px solid var(--color-black);
|
|
44
|
-
font-family: var(--heading-font);
|
|
45
|
-
font-weight: 700;
|
|
46
|
-
box-shadow: var(--shadow);
|
|
47
|
-
transition-property: all;
|
|
48
|
-
transition-duration: 100ms;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
.btn-brutal-sm:hover {
|
|
52
|
-
transform: translateY(-1px);
|
|
53
|
-
box-shadow: var(--shadow-hover);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
.btn-brutal-sm:active {
|
|
57
|
-
transform: translate(1px, 1px);
|
|
58
|
-
box-shadow: var(--shadow-active);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
.btn-brutal-sm:disabled {
|
|
62
|
-
pointer-events: none;
|
|
63
|
-
opacity: 0.4;
|
|
64
|
-
box-shadow: var(--shadow);
|
|
65
|
-
transform: none;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
.shadow-brutal-sm {
|
|
69
|
-
box-shadow: var(--shadow);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
.hover\:shadow-brutal-sm:hover {
|
|
73
|
-
box-shadow: var(--shadow);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
.active\:shadow-brutal-sm:active {
|
|
77
|
-
box-shadow: var(--shadow);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
.shadow-brutal {
|
|
81
|
-
box-shadow: var(--shadow-hover);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
.shadow-brutal-lg {
|
|
85
|
-
box-shadow: var(--shadow-lg);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
.focus-within\:shadow-brutal:focus-within {
|
|
89
|
-
box-shadow: var(--shadow-hover);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
.scrollbar-none {
|
|
93
|
-
scrollbar-width: none;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
.scrollbar-none::-webkit-scrollbar {
|
|
97
|
-
display: none;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
.h-panel-header {
|
|
101
|
-
height: 62px;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
.size-icon-header {
|
|
105
|
-
width: 36px;
|
|
106
|
-
height: 36px;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
@layer components {
|
|
110
|
-
.input-brutal {
|
|
111
|
-
border: 2px solid var(--color-black);
|
|
112
|
-
background-color: var(--color-white);
|
|
113
|
-
padding: 0.5rem 0.75rem;
|
|
114
|
-
font-family: var(--heading-font);
|
|
115
|
-
box-shadow: var(--shadow);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
.input-brutal:focus {
|
|
119
|
-
outline: none;
|
|
120
|
-
box-shadow: var(--shadow-hover);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
@layer utilities {
|
|
125
|
-
.border-brutal-red {
|
|
126
|
-
border-color: var(--color-brutal-red-400);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
.border-brutal-yellow {
|
|
130
|
-
border-color: var(--color-brutal-yellow-400);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
.ring-brutal-red\/60 {
|
|
134
|
-
--tw-ring-color: oklch(0.711 0.1682 28.04 / 0.6);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
.bg-brutal-yellow {
|
|
138
|
-
background-color: var(--color-brutal-yellow-400);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
.bg-brutal-yellow\/30 {
|
|
142
|
-
background-color: oklch(0.8829 0.1623 91.9 / 0.3);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
.bg-brutal-yellow\/45 {
|
|
146
|
-
background-color: oklch(0.8829 0.1623 91.9 / 0.45);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
.bg-brutal-pink {
|
|
150
|
-
background-color: var(--color-brutal-pink-400);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
.bg-brutal-pink\/35 {
|
|
154
|
-
background-color: oklch(0.7489 0.1623 0.7 / 0.35);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
.bg-brutal-cyan {
|
|
158
|
-
background-color: var(--color-brutal-cyan-400);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
.bg-brutal-cyan\/15 {
|
|
162
|
-
background-color: oklch(0.7833 0.1353 219.21 / 0.15);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
.bg-brutal-cyan\/35 {
|
|
166
|
-
background-color: oklch(0.7833 0.1353 219.21 / 0.35);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
.bg-brutal-lavender {
|
|
170
|
-
background-color: var(--color-brutal-purple-300);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
.bg-brutal-lavender\/30 {
|
|
174
|
-
background-color: oklch(0.783 0.0783 294.53 / 0.3);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
.bg-brutal-lavender\/45 {
|
|
178
|
-
background-color: oklch(0.783 0.0783 294.53 / 0.45);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
.bg-brutal-orange {
|
|
182
|
-
background-color: var(--color-brutal-orange-400);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
.bg-brutal-orange\/20 {
|
|
186
|
-
background-color: oklch(0.7855 0.1227 50.11 / 0.2);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
.bg-brutal-orange\/30 {
|
|
190
|
-
background-color: oklch(0.7855 0.1227 50.11 / 0.3);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
.bg-brutal-orange\/40 {
|
|
194
|
-
background-color: oklch(0.7855 0.1227 50.11 / 0.4);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
.bg-brutal-lime {
|
|
198
|
-
background-color: var(--color-brutal-lime-400);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
.bg-brutal-lime\/20 {
|
|
202
|
-
background-color: oklch(0.8272 0.1354 130.07 / 0.2);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
.bg-brutal-lime\/45 {
|
|
206
|
-
background-color: oklch(0.8272 0.1354 130.07 / 0.45);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
.bg-brutal-red {
|
|
210
|
-
background-color: var(--color-brutal-red-400);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
.bg-brutal-red\/5 {
|
|
214
|
-
background-color: oklch(0.711 0.1682 28.04 / 0.05);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
.bg-brutal-red\/20 {
|
|
218
|
-
background-color: oklch(0.711 0.1682 28.04 / 0.2);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
.bg-brutal-stone {
|
|
222
|
-
background-color: var(--color-brutal-stone-400);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
.bg-brutal-cream {
|
|
226
|
-
background-color: var(--color-brutal-cream-100);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
.text-brutal-pink {
|
|
230
|
-
color: var(--color-brutal-pink-400);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
.text-brutal-red {
|
|
234
|
-
color: var(--color-brutal-red-400);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
.text-brutal-yellow {
|
|
238
|
-
color: var(--color-brutal-yellow-400);
|
|
239
|
-
}
|
|
240
|
-
}
|
package/src/lib/cn.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { cn } from "cnfast";
|
package/src/lib/styled-props.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Base UI components type `className` as `string | ((state) => string)`, but our
|
|
3
|
-
* tv() recipes only accept a plain string. `StyledProps` narrows a Base UI
|
|
4
|
-
* component's props to a string `className` so they can flow into a recipe.
|
|
5
|
-
*
|
|
6
|
-
* The state-callback form is intentionally dropped: our components express every
|
|
7
|
-
* state through `data-*` variants in the recipe, so a caller never needs it.
|
|
8
|
-
*/
|
|
9
|
-
export type StyledProps<P> = Omit<P, "className"> & {
|
|
10
|
-
className?: string;
|
|
11
|
-
};
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { createContext } from "react";
|
|
2
|
-
|
|
3
|
-
/** Visual style family. Mode (light/dark/system) is a separate axis. */
|
|
4
|
-
export type Theme = "brutal" | "elegant";
|
|
5
|
-
export type ThemeMode = "light" | "dark" | "system";
|
|
6
|
-
export type ResolvedThemeMode = Exclude<ThemeMode, "system">;
|
|
7
|
-
export type SetThemeOptions = {
|
|
8
|
-
mode?: ThemeMode;
|
|
9
|
-
};
|
|
10
|
-
export type SetTheme = {
|
|
11
|
-
(theme: "brutal"): void;
|
|
12
|
-
(theme: "elegant", options?: SetThemeOptions): void;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
export type ThemeState = {
|
|
16
|
-
theme: Theme;
|
|
17
|
-
setTheme: SetTheme;
|
|
18
|
-
mode: ThemeMode;
|
|
19
|
-
resolvedMode: ResolvedThemeMode;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
export const ThemeContext = createContext<Theme>("brutal");
|
|
23
|
-
export const ThemeStateContext = createContext<ThemeState>({
|
|
24
|
-
theme: "brutal",
|
|
25
|
-
setTheme: () => undefined,
|
|
26
|
-
mode: "light",
|
|
27
|
-
resolvedMode: "light",
|
|
28
|
-
});
|
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
useCallback,
|
|
5
|
-
useEffect,
|
|
6
|
-
useMemo,
|
|
7
|
-
useState,
|
|
8
|
-
useSyncExternalStore,
|
|
9
|
-
type ReactNode,
|
|
10
|
-
} from "react";
|
|
11
|
-
import {
|
|
12
|
-
ThemeContext,
|
|
13
|
-
ThemeStateContext,
|
|
14
|
-
type ResolvedThemeMode,
|
|
15
|
-
type SetTheme,
|
|
16
|
-
type SetThemeOptions,
|
|
17
|
-
type Theme,
|
|
18
|
-
type ThemeState,
|
|
19
|
-
type ThemeMode,
|
|
20
|
-
} from "./theme-context.ts";
|
|
21
|
-
|
|
22
|
-
export interface ThemeProviderProps {
|
|
23
|
-
/**
|
|
24
|
-
* Controlled visual style family. Keep using this for isolated previews and
|
|
25
|
-
* component matrix surfaces that should not own global DOM state.
|
|
26
|
-
*/
|
|
27
|
-
theme?: Theme;
|
|
28
|
-
defaultTheme?: Theme;
|
|
29
|
-
onThemeChange?: (theme: Theme) => void;
|
|
30
|
-
/** Controlled light/dark behavior for the elegant family. */
|
|
31
|
-
mode?: ThemeMode;
|
|
32
|
-
defaultMode?: ThemeMode;
|
|
33
|
-
onModeChange?: (mode: ThemeMode) => void;
|
|
34
|
-
/**
|
|
35
|
-
* Persist uncontrolled family and mode selections. The mode key appends
|
|
36
|
-
* "-mode" unless `modeStorageKey` is provided.
|
|
37
|
-
*/
|
|
38
|
-
storageKey?: string;
|
|
39
|
-
modeStorageKey?: string;
|
|
40
|
-
/**
|
|
41
|
-
* Sync the active family/mode onto `document.documentElement`.
|
|
42
|
-
* Defaults to true for app-level providers. Set to false for nested previews
|
|
43
|
-
* or component islands that should not mutate the app root.
|
|
44
|
-
*/
|
|
45
|
-
syncDom?: boolean;
|
|
46
|
-
children: ReactNode;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function readStoredTheme(storageKey: string | undefined, fallback: Theme): Theme {
|
|
50
|
-
if (typeof window === "undefined" || !storageKey) {
|
|
51
|
-
return fallback;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const stored = window.localStorage.getItem(storageKey);
|
|
55
|
-
return stored === "brutal" || stored === "elegant" ? stored : fallback;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function readStoredMode(storageKey: string | undefined, fallback: ThemeMode): ThemeMode {
|
|
59
|
-
if (typeof window === "undefined" || !storageKey) {
|
|
60
|
-
return fallback;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const stored = window.localStorage.getItem(storageKey);
|
|
64
|
-
return stored === "light" || stored === "dark" || stored === "system" ? stored : fallback;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function writeStorage(key: string | undefined, value: string) {
|
|
68
|
-
if (typeof window !== "undefined" && key) {
|
|
69
|
-
window.localStorage.setItem(key, value);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function subscribeSystemMode(onStoreChange: () => void) {
|
|
74
|
-
if (typeof window === "undefined") {
|
|
75
|
-
return () => undefined;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const media = window.matchMedia("(prefers-color-scheme: dark)");
|
|
79
|
-
media.addEventListener("change", onStoreChange);
|
|
80
|
-
return () => media.removeEventListener("change", onStoreChange);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function getSystemMode(): ResolvedThemeMode {
|
|
84
|
-
if (typeof window === "undefined") {
|
|
85
|
-
return "light";
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export function ThemeProvider({
|
|
92
|
-
theme,
|
|
93
|
-
defaultTheme = "brutal",
|
|
94
|
-
onThemeChange,
|
|
95
|
-
mode,
|
|
96
|
-
defaultMode = "light",
|
|
97
|
-
onModeChange,
|
|
98
|
-
storageKey,
|
|
99
|
-
modeStorageKey = storageKey ? `${storageKey}-mode` : undefined,
|
|
100
|
-
syncDom = true,
|
|
101
|
-
children,
|
|
102
|
-
}: ThemeProviderProps) {
|
|
103
|
-
const [uncontrolledTheme, setUncontrolledTheme] = useState<Theme>(() =>
|
|
104
|
-
readStoredTheme(storageKey, defaultTheme),
|
|
105
|
-
);
|
|
106
|
-
const [uncontrolledMode, setUncontrolledMode] = useState<ThemeMode>(() =>
|
|
107
|
-
readStoredMode(modeStorageKey, defaultMode),
|
|
108
|
-
);
|
|
109
|
-
const activeTheme = theme ?? uncontrolledTheme;
|
|
110
|
-
const activeMode = mode ?? uncontrolledMode;
|
|
111
|
-
const systemMode = useSyncExternalStore(
|
|
112
|
-
subscribeSystemMode,
|
|
113
|
-
getSystemMode,
|
|
114
|
-
() => "light" as ResolvedThemeMode,
|
|
115
|
-
);
|
|
116
|
-
const resolvedMode: ResolvedThemeMode = activeMode === "system" ? systemMode : activeMode;
|
|
117
|
-
|
|
118
|
-
const commitTheme = useCallback(
|
|
119
|
-
(nextTheme: Theme) => {
|
|
120
|
-
if (theme === undefined) {
|
|
121
|
-
setUncontrolledTheme(nextTheme);
|
|
122
|
-
writeStorage(storageKey, nextTheme);
|
|
123
|
-
}
|
|
124
|
-
onThemeChange?.(nextTheme);
|
|
125
|
-
},
|
|
126
|
-
[onThemeChange, storageKey, theme],
|
|
127
|
-
);
|
|
128
|
-
|
|
129
|
-
const commitMode = useCallback(
|
|
130
|
-
(nextMode: ThemeMode) => {
|
|
131
|
-
if (mode === undefined) {
|
|
132
|
-
setUncontrolledMode(nextMode);
|
|
133
|
-
writeStorage(modeStorageKey, nextMode);
|
|
134
|
-
}
|
|
135
|
-
onModeChange?.(nextMode);
|
|
136
|
-
},
|
|
137
|
-
[mode, modeStorageKey, onModeChange],
|
|
138
|
-
);
|
|
139
|
-
|
|
140
|
-
const setTheme = useCallback<SetTheme>(
|
|
141
|
-
(nextTheme: Theme, options?: SetThemeOptions) => {
|
|
142
|
-
commitTheme(nextTheme);
|
|
143
|
-
if (nextTheme === "brutal") {
|
|
144
|
-
commitMode("light");
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
if (options?.mode) {
|
|
148
|
-
commitMode(options.mode);
|
|
149
|
-
}
|
|
150
|
-
},
|
|
151
|
-
[commitMode, commitTheme],
|
|
152
|
-
);
|
|
153
|
-
|
|
154
|
-
const themeState = useMemo<ThemeState>(
|
|
155
|
-
() => ({
|
|
156
|
-
theme: activeTheme,
|
|
157
|
-
setTheme,
|
|
158
|
-
mode: activeMode,
|
|
159
|
-
resolvedMode,
|
|
160
|
-
}),
|
|
161
|
-
[activeMode, activeTheme, resolvedMode, setTheme],
|
|
162
|
-
);
|
|
163
|
-
|
|
164
|
-
useEffect(() => {
|
|
165
|
-
if (!syncDom) {
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const root = document.documentElement;
|
|
170
|
-
root.dataset.theme = activeTheme;
|
|
171
|
-
root.classList.toggle("light", activeTheme === "elegant" && activeMode === "light");
|
|
172
|
-
root.classList.toggle("dark", activeTheme === "elegant" && activeMode === "dark");
|
|
173
|
-
root.style.colorScheme = activeTheme === "elegant" ? resolvedMode : "light";
|
|
174
|
-
}, [activeMode, activeTheme, resolvedMode, syncDom]);
|
|
175
|
-
|
|
176
|
-
return (
|
|
177
|
-
<ThemeStateContext.Provider value={themeState}>
|
|
178
|
-
<ThemeContext.Provider value={activeTheme}>{children}</ThemeContext.Provider>
|
|
179
|
-
</ThemeStateContext.Provider>
|
|
180
|
-
);
|
|
181
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { use } from "react";
|
|
2
|
-
import { ThemeContext, ThemeStateContext, type Theme, type ThemeState } from "./theme-context.ts";
|
|
3
|
-
|
|
4
|
-
/** Read the active style family for theme-aware component recipes. */
|
|
5
|
-
export function useThemeFamily(): Theme {
|
|
6
|
-
return use(ThemeContext);
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
/** Read and update the application-level RUI theme selection. */
|
|
10
|
-
export function useTheme(): ThemeState {
|
|
11
|
-
return use(ThemeStateContext);
|
|
12
|
-
}
|
package/src/lib/tv.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { cn } from "cnfast";
|
|
2
|
-
import { tv as tvBase, type TVLite } from "tailwind-variants/lite";
|
|
3
|
-
|
|
4
|
-
export type { VariantProps } from "tailwind-variants/lite";
|
|
5
|
-
|
|
6
|
-
type SlotFunction = (props?: unknown) => string;
|
|
7
|
-
type SlotRecord = Record<string, SlotFunction>;
|
|
8
|
-
type RecipeFunction = ((props?: unknown) => string | SlotRecord) & Record<string, unknown>;
|
|
9
|
-
|
|
10
|
-
export const tv: TVLite = ((options: unknown) => {
|
|
11
|
-
const recipe = tvBase(options as never) as unknown as RecipeFunction;
|
|
12
|
-
|
|
13
|
-
const wrappedRecipe = ((props?: unknown) => {
|
|
14
|
-
const result = recipe(props);
|
|
15
|
-
|
|
16
|
-
if (typeof result === "string") {
|
|
17
|
-
return cn(result);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
return Object.fromEntries(
|
|
21
|
-
Object.entries(result).map(([slotName, slot]) => [
|
|
22
|
-
slotName,
|
|
23
|
-
(slotProps?: unknown) => cn(slot(slotProps)),
|
|
24
|
-
]),
|
|
25
|
-
);
|
|
26
|
-
}) as RecipeFunction;
|
|
27
|
-
|
|
28
|
-
return Object.assign(wrappedRecipe, recipe);
|
|
29
|
-
}) as unknown as TVLite;
|