raft-ui 0.0.6 → 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 +2 -8
- package/{src → dist}/styles.css +0 -2
- package/package.json +3 -4
- package/src/components/alert-dialog/alert-dialog.tsx +0 -215
- package/src/components/alert-dialog/index.ts +0 -30
- 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 -499
- 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/dialog/dialog.tsx +0 -152
- package/src/components/dialog/index.ts +0 -26
- 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 -2
- 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 -2
- 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/separator/index.ts +0 -2
- package/src/components/separator/separator.tsx +0 -40
- 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 -7
- package/src/components/toggle-group/toggle-group.tsx +0 -111
- package/src/components/tooltip/index.ts +0 -8
- package/src/components/tooltip/tooltip.tsx +0 -212
- package/src/index.ts +0 -429
- 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 -291
package/src/legacy/Badge.tsx
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import type { ButtonHTMLAttributes, HTMLAttributes, ReactNode } from "react";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Badge tones map to the brutal palette. The primitive owns the bg →
|
|
5
|
-
* caller never writes `bg-brutal-*` via className. Adding a new tone
|
|
6
|
-
* means adding a row here, not a one-off override at the callsite.
|
|
7
|
-
*
|
|
8
|
-
* Hidden context:
|
|
9
|
-
* - PR #1905 / #proj-theme:ac79cf20 stdrc msg=6ce4d8bf — colors /
|
|
10
|
-
* uppercase / tracking all used to leak through caller className;
|
|
11
|
-
* the primitive now owns them.
|
|
12
|
-
* - stdrc msg=6db77368 — "Badges and task status 都应该大写(内容
|
|
13
|
-
* 应该是 Title case ... css 在 Brutal 主题下全大写(将来我们会有
|
|
14
|
-
* 别的主题))". Content stays Title Case (e.g. "In Progress"); the
|
|
15
|
-
* brutal theme's CSS does the visual uppercase + tracking-wide
|
|
16
|
-
* transform here in the primitive. A future non-brutal skin can
|
|
17
|
-
* render the same Title Case content without uppercase.
|
|
18
|
-
* - stdrc msg=ce25da45 (option B msg=65681d15) — status badges that
|
|
19
|
-
* sit next to case-sensitive identifiers (e.g. `task #N @assignee`)
|
|
20
|
-
* must opt out of uppercase, because `@bugen` ≠ `@BUGEN`. Hence
|
|
21
|
-
* `uppercase?: boolean` axis: default true preserves the brutal
|
|
22
|
-
* contract for label-only badges; status callsites pass `false`.
|
|
23
|
-
*/
|
|
24
|
-
export const BADGE_TONE_CLASSES = {
|
|
25
|
-
white: "bg-white text-black",
|
|
26
|
-
yellow: "bg-brutal-yellow text-black",
|
|
27
|
-
pink: "bg-brutal-pink text-black",
|
|
28
|
-
cyan: "bg-brutal-cyan text-black",
|
|
29
|
-
lavender: "bg-brutal-lavender text-black",
|
|
30
|
-
orange: "bg-brutal-orange text-black",
|
|
31
|
-
lime: "bg-brutal-lime text-black",
|
|
32
|
-
red: "bg-brutal-red text-black",
|
|
33
|
-
stone: "bg-brutal-stone text-black",
|
|
34
|
-
} as const;
|
|
35
|
-
|
|
36
|
-
export type BadgeTone = keyof typeof BADGE_TONE_CLASSES;
|
|
37
|
-
|
|
38
|
-
interface BadgeStyleProps {
|
|
39
|
-
/** Background tone from the brutal palette. Defaults to `white`. */
|
|
40
|
-
tone?: BadgeTone;
|
|
41
|
-
/**
|
|
42
|
-
* Apply the brutal uppercase + tracking-wide transform. Defaults to
|
|
43
|
-
* true (label-only badges). Status badges pass `false` because they
|
|
44
|
-
* mix label content with case-sensitive identifiers (`@assignee`).
|
|
45
|
-
*/
|
|
46
|
-
uppercase?: boolean;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function brutalBadgeClassName({
|
|
50
|
-
tone = "white",
|
|
51
|
-
uppercase = true,
|
|
52
|
-
className = "",
|
|
53
|
-
}: BadgeStyleProps & { className?: string } = {}) {
|
|
54
|
-
return [
|
|
55
|
-
"inline-flex h-5 min-w-0 max-w-full items-center gap-1 overflow-hidden whitespace-nowrap border border-black px-1.5 text-[10px] font-bold leading-none",
|
|
56
|
-
uppercase ? "uppercase tracking-wide" : null,
|
|
57
|
-
BADGE_TONE_CLASSES[tone],
|
|
58
|
-
className,
|
|
59
|
-
]
|
|
60
|
-
.filter(Boolean)
|
|
61
|
-
.join(" ");
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export interface BadgeProps extends HTMLAttributes<HTMLSpanElement>, BadgeStyleProps {
|
|
65
|
-
children?: ReactNode;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export function Badge({ tone, uppercase, className = "", children, ...props }: BadgeProps) {
|
|
69
|
-
return (
|
|
70
|
-
<span className={brutalBadgeClassName({ tone, uppercase, className })} {...props}>
|
|
71
|
-
{children}
|
|
72
|
-
</span>
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export interface BadgeButtonProps extends ButtonHTMLAttributes<HTMLButtonElement>, BadgeStyleProps {
|
|
77
|
-
children?: ReactNode;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export function BadgeButton({
|
|
81
|
-
tone,
|
|
82
|
-
uppercase,
|
|
83
|
-
className = "",
|
|
84
|
-
type = "button",
|
|
85
|
-
children,
|
|
86
|
-
...props
|
|
87
|
-
}: BadgeButtonProps) {
|
|
88
|
-
return (
|
|
89
|
-
<button type={type} className={brutalBadgeClassName({ tone, uppercase, className })} {...props}>
|
|
90
|
-
{children}
|
|
91
|
-
</button>
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export default Badge;
|
package/src/legacy/Banner.tsx
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { AlertTriangle, CheckCircle2, CircleAlert, Info } from "lucide-react";
|
|
2
|
-
import type { HTMLAttributes, ReactNode } from "react";
|
|
3
|
-
|
|
4
|
-
export type BannerIntent = "destructive" | "warning" | "info" | "success";
|
|
5
|
-
export type BannerDensity = "sm" | "md" | "lg";
|
|
6
|
-
|
|
7
|
-
const INTENT_SURFACE: Record<BannerIntent, string> = {
|
|
8
|
-
destructive: "bg-brutal-red/20",
|
|
9
|
-
warning: "bg-brutal-orange/20",
|
|
10
|
-
info: "bg-brutal-yellow/30",
|
|
11
|
-
success: "bg-brutal-lime/20",
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
const INTENT_DEFAULT_ICON: Record<BannerIntent, ReactNode> = {
|
|
15
|
-
destructive: <AlertTriangle size={18} className="mt-0.5 shrink-0 text-black" />,
|
|
16
|
-
warning: <CircleAlert size={18} className="mt-0.5 shrink-0 text-black" />,
|
|
17
|
-
info: <Info size={18} className="mt-0.5 shrink-0 text-black" />,
|
|
18
|
-
success: <CheckCircle2 size={18} className="mt-0.5 shrink-0 text-black" />,
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
const DENSITY_PADDING: Record<BannerDensity, string> = {
|
|
22
|
-
sm: "p-2",
|
|
23
|
-
md: "p-3",
|
|
24
|
-
lg: "p-4 shadow-brutal-sm",
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
const DENSITY_TEXT: Record<BannerDensity, string> = {
|
|
28
|
-
sm: "text-xs",
|
|
29
|
-
md: "text-sm",
|
|
30
|
-
lg: "text-sm",
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
const DENSITY_GAP: Record<BannerDensity, string> = {
|
|
34
|
-
sm: "gap-2",
|
|
35
|
-
md: "gap-3",
|
|
36
|
-
lg: "gap-3",
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
export interface BannerProps extends Omit<HTMLAttributes<HTMLDivElement>, "title"> {
|
|
40
|
-
intent: BannerIntent;
|
|
41
|
-
density?: BannerDensity;
|
|
42
|
-
title?: ReactNode;
|
|
43
|
-
children?: ReactNode;
|
|
44
|
-
withIcon?: boolean;
|
|
45
|
-
icon?: ReactNode;
|
|
46
|
-
actions?: ReactNode;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export default function Banner({
|
|
50
|
-
intent,
|
|
51
|
-
density = "md",
|
|
52
|
-
title,
|
|
53
|
-
children,
|
|
54
|
-
icon,
|
|
55
|
-
withIcon = false,
|
|
56
|
-
actions,
|
|
57
|
-
className = "",
|
|
58
|
-
...rest
|
|
59
|
-
}: BannerProps) {
|
|
60
|
-
const resolvedIcon = icon !== undefined ? icon : withIcon ? INTENT_DEFAULT_ICON[intent] : null;
|
|
61
|
-
return (
|
|
62
|
-
<div
|
|
63
|
-
{...rest}
|
|
64
|
-
className={`flex items-start ${DENSITY_GAP[density]} border-2 border-black ${INTENT_SURFACE[intent]} ${DENSITY_PADDING[density]} ${className}`}
|
|
65
|
-
>
|
|
66
|
-
{resolvedIcon}
|
|
67
|
-
<div className={`min-w-0 flex-1 ${DENSITY_TEXT[density]} text-black`}>
|
|
68
|
-
{title && <div className="mb-0.5 font-bold">{title}</div>}
|
|
69
|
-
{children}
|
|
70
|
-
</div>
|
|
71
|
-
{actions && <div className="shrink-0">{actions}</div>}
|
|
72
|
-
</div>
|
|
73
|
-
);
|
|
74
|
-
}
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import { type HTMLAttributes, type ReactNode, useEffect } from "react";
|
|
2
|
-
import { createPortal } from "react-dom";
|
|
3
|
-
|
|
4
|
-
export interface BottomSheetProps extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
|
|
5
|
-
children: ReactNode;
|
|
6
|
-
onClose: () => void;
|
|
7
|
-
/** CSS z-index. Default 50 to match modal-layer surfaces. */
|
|
8
|
-
zIndex?: number;
|
|
9
|
-
/** Click outside the sheet closes it. Default true. */
|
|
10
|
-
dismissOnBackdrop?: boolean;
|
|
11
|
-
/** Lock body scroll while mounted. Default true. */
|
|
12
|
-
lockBodyScroll?: boolean;
|
|
13
|
-
/** Width class for the sheet card. */
|
|
14
|
-
widthClass?: string;
|
|
15
|
-
/** Extra classes on the sheet card. */
|
|
16
|
-
sheetClassName?: string;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Mobile bottom sheet primitive.
|
|
21
|
-
*
|
|
22
|
-
* Unlike Lightbox, this has no dim backdrop. The sheet itself carries the
|
|
23
|
-
* dialog affordance via brutal border + shadow while staying anchored to the
|
|
24
|
-
* bottom safe area.
|
|
25
|
-
*/
|
|
26
|
-
export default function BottomSheet({
|
|
27
|
-
children,
|
|
28
|
-
onClose,
|
|
29
|
-
zIndex = 50,
|
|
30
|
-
dismissOnBackdrop = true,
|
|
31
|
-
lockBodyScroll = true,
|
|
32
|
-
widthClass = "max-w-md",
|
|
33
|
-
sheetClassName = "",
|
|
34
|
-
className = "",
|
|
35
|
-
...rest
|
|
36
|
-
}: BottomSheetProps) {
|
|
37
|
-
useEffect(() => {
|
|
38
|
-
const onKey = (e: KeyboardEvent) => {
|
|
39
|
-
if (e.key === "Escape") onClose();
|
|
40
|
-
};
|
|
41
|
-
document.addEventListener("keydown", onKey);
|
|
42
|
-
return () => document.removeEventListener("keydown", onKey);
|
|
43
|
-
}, [onClose]);
|
|
44
|
-
|
|
45
|
-
useEffect(() => {
|
|
46
|
-
if (!lockBodyScroll) return;
|
|
47
|
-
const prevBodyOverflow = document.body.style.overflow;
|
|
48
|
-
document.body.style.overflow = "hidden";
|
|
49
|
-
return () => {
|
|
50
|
-
document.body.style.overflow = prevBodyOverflow;
|
|
51
|
-
};
|
|
52
|
-
}, [lockBodyScroll]);
|
|
53
|
-
|
|
54
|
-
return createPortal(
|
|
55
|
-
<div
|
|
56
|
-
{...rest}
|
|
57
|
-
className={`fixed inset-x-0 bottom-0 p-3 pb-[max(12px,env(safe-area-inset-bottom))] ${className}`}
|
|
58
|
-
style={{ zIndex, ...rest.style }}
|
|
59
|
-
onClick={
|
|
60
|
-
dismissOnBackdrop
|
|
61
|
-
? (e) => {
|
|
62
|
-
if (e.target === e.currentTarget) onClose();
|
|
63
|
-
}
|
|
64
|
-
: undefined
|
|
65
|
-
}
|
|
66
|
-
>
|
|
67
|
-
<div
|
|
68
|
-
className={`mx-auto border-2 border-black bg-white shadow-brutal-lg ${widthClass} ${sheetClassName}`}
|
|
69
|
-
onClick={(event) => event.stopPropagation()}
|
|
70
|
-
>
|
|
71
|
-
{children}
|
|
72
|
-
</div>
|
|
73
|
-
</div>,
|
|
74
|
-
document.body,
|
|
75
|
-
);
|
|
76
|
-
}
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { Check } from "lucide-react";
|
|
2
|
-
import type { HTMLAttributes } from "react";
|
|
3
|
-
|
|
4
|
-
export type CheckMarkerShape = "square" | "circle";
|
|
5
|
-
export type CheckMarkerSize = "sm" | "md" | "lg";
|
|
6
|
-
export type CheckMarkerTone = "black-fill" | "yellow-fill";
|
|
7
|
-
|
|
8
|
-
const SIZE_CLASS: Record<CheckMarkerSize, string> = {
|
|
9
|
-
sm: "size-3.5",
|
|
10
|
-
md: "size-4",
|
|
11
|
-
lg: "size-5",
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
const ICON_PX: Record<CheckMarkerSize, number> = {
|
|
15
|
-
sm: 10,
|
|
16
|
-
md: 12,
|
|
17
|
-
lg: 13,
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
const STROKE_WIDTH: Record<CheckMarkerSize, number> = {
|
|
21
|
-
sm: 4,
|
|
22
|
-
md: 4,
|
|
23
|
-
lg: 3,
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
export interface CheckMarkerProps extends Omit<HTMLAttributes<HTMLSpanElement>, "size"> {
|
|
27
|
-
checked: boolean;
|
|
28
|
-
shape?: CheckMarkerShape;
|
|
29
|
-
size?: CheckMarkerSize;
|
|
30
|
-
tone?: CheckMarkerTone;
|
|
31
|
-
disabled?: boolean;
|
|
32
|
-
previewOnHover?: boolean;
|
|
33
|
-
className?: string;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export default function CheckMarker({
|
|
37
|
-
checked,
|
|
38
|
-
shape = "square",
|
|
39
|
-
size = "sm",
|
|
40
|
-
tone = "black-fill",
|
|
41
|
-
disabled = false,
|
|
42
|
-
previewOnHover = false,
|
|
43
|
-
className = "",
|
|
44
|
-
...props
|
|
45
|
-
}: CheckMarkerProps) {
|
|
46
|
-
const checkedClasses =
|
|
47
|
-
tone === "yellow-fill" ? "bg-brutal-yellow text-black" : "bg-black text-white";
|
|
48
|
-
const uncheckedClasses = previewOnHover
|
|
49
|
-
? "bg-white text-transparent group-hover:text-black/20"
|
|
50
|
-
: "bg-white text-transparent";
|
|
51
|
-
|
|
52
|
-
return (
|
|
53
|
-
<span
|
|
54
|
-
{...props}
|
|
55
|
-
aria-hidden
|
|
56
|
-
className={[
|
|
57
|
-
"inline-flex shrink-0 items-center justify-center border-2 border-black transition-colors",
|
|
58
|
-
SIZE_CLASS[size],
|
|
59
|
-
shape === "circle" ? "rounded-full" : "",
|
|
60
|
-
checked ? checkedClasses : uncheckedClasses,
|
|
61
|
-
disabled ? "opacity-50" : "",
|
|
62
|
-
className,
|
|
63
|
-
]
|
|
64
|
-
.filter(Boolean)
|
|
65
|
-
.join(" ")}
|
|
66
|
-
>
|
|
67
|
-
{(checked || previewOnHover) && (
|
|
68
|
-
<Check size={ICON_PX[size]} strokeWidth={STROKE_WIDTH[size]} />
|
|
69
|
-
)}
|
|
70
|
-
</span>
|
|
71
|
-
);
|
|
72
|
-
}
|
package/src/legacy/Checkbox.tsx
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
// Neo-Brutalist form checkbox primitive — sr-only native input + shared
|
|
2
|
-
// CheckMarker visual, matching the form checkbox places already hand-rolled
|
|
3
|
-
// before extraction (#proj-permission:1414ca65 msg=dabbb00e):
|
|
4
|
-
//
|
|
5
|
-
// • ReportIssueDialog — 4 options + consent
|
|
6
|
-
// • OwnerOnboardingModal — opt-out
|
|
7
|
-
// • MessageInput — "As Task" toggle
|
|
8
|
-
// • AgentScopesPanel — permission rows (was native `accent-brutal-pink`)
|
|
9
|
-
//
|
|
10
|
-
// Why a custom span over native styling: the brutal design system uses
|
|
11
|
-
// `border-2 border-black` everywhere; native `accent-color` ignores border
|
|
12
|
-
// width and gives a soft fill. The `sr-only` input keeps a11y/keyboard/form
|
|
13
|
-
// behavior intact while letting us paint the visual.
|
|
14
|
-
//
|
|
15
|
-
// API stays minimal — same shape as a native `<input type="checkbox">` plus a
|
|
16
|
-
// `size` prop. Selection-only markers use CheckMarker directly so decorative
|
|
17
|
-
// row selection does not inherit form semantics.
|
|
18
|
-
//
|
|
19
|
-
// The `<label>` wrapper that owns hover / cursor / disabled-opacity stays
|
|
20
|
-
// the caller's job — different consumers compose this checkbox into
|
|
21
|
-
// different row layouts (inline vs stacked, with description vs without),
|
|
22
|
-
// and forcing one row layout would just push complexity into props.
|
|
23
|
-
|
|
24
|
-
import type { InputHTMLAttributes } from "react";
|
|
25
|
-
import CheckMarker, { type CheckMarkerSize } from "./CheckMarker";
|
|
26
|
-
|
|
27
|
-
type Size = Extract<CheckMarkerSize, "sm" | "md">;
|
|
28
|
-
|
|
29
|
-
export interface CheckboxProps extends Omit<
|
|
30
|
-
InputHTMLAttributes<HTMLInputElement>,
|
|
31
|
-
"type" | "size"
|
|
32
|
-
> {
|
|
33
|
-
/** Visual size of the checkbox box. Default `sm` (14px) matches the
|
|
34
|
-
* existing inline callsites; use `md` (16px) for permission rows and
|
|
35
|
-
* other settings-style lists. */
|
|
36
|
-
size?: Size;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Inline sr-only equivalent so the native input stays visually hidden
|
|
40
|
-
// regardless of whether the consumer's Tailwind has the `sr-only`
|
|
41
|
-
// utility class generated. ui-inventory hit this — its Tailwind only
|
|
42
|
-
// scans its own src, so primitive-internal class strings weren't being
|
|
43
|
-
// generated and the OS-default checkbox visibly bled through.
|
|
44
|
-
const SR_ONLY_STYLE = {
|
|
45
|
-
position: "absolute",
|
|
46
|
-
width: 1,
|
|
47
|
-
height: 1,
|
|
48
|
-
padding: 0,
|
|
49
|
-
margin: -1,
|
|
50
|
-
overflow: "hidden",
|
|
51
|
-
clip: "rect(0,0,0,0)",
|
|
52
|
-
whiteSpace: "nowrap",
|
|
53
|
-
borderWidth: 0,
|
|
54
|
-
} as const;
|
|
55
|
-
|
|
56
|
-
export default function Checkbox({
|
|
57
|
-
size = "sm",
|
|
58
|
-
checked,
|
|
59
|
-
disabled,
|
|
60
|
-
className = "",
|
|
61
|
-
...rest
|
|
62
|
-
}: CheckboxProps) {
|
|
63
|
-
return (
|
|
64
|
-
<span className="relative inline-flex shrink-0">
|
|
65
|
-
<input
|
|
66
|
-
type="checkbox"
|
|
67
|
-
checked={checked}
|
|
68
|
-
disabled={disabled}
|
|
69
|
-
style={SR_ONLY_STYLE}
|
|
70
|
-
{...rest}
|
|
71
|
-
/>
|
|
72
|
-
<CheckMarker
|
|
73
|
-
checked={Boolean(checked)}
|
|
74
|
-
disabled={disabled}
|
|
75
|
-
size={size}
|
|
76
|
-
tone="black-fill"
|
|
77
|
-
className={className}
|
|
78
|
-
/>
|
|
79
|
-
</span>
|
|
80
|
-
);
|
|
81
|
-
}
|
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
import { forwardRef } from "react";
|
|
2
|
-
import type { MouseEvent, ReactNode } from "react";
|
|
3
|
-
import AvatarSlot from "./AvatarSlot";
|
|
4
|
-
|
|
5
|
-
type ConversationAuthor =
|
|
6
|
-
| {
|
|
7
|
-
kind: "agent";
|
|
8
|
-
name: string;
|
|
9
|
-
avatarUrl?: string | null;
|
|
10
|
-
subtitle?: string | null;
|
|
11
|
-
}
|
|
12
|
-
| {
|
|
13
|
-
kind: "user";
|
|
14
|
-
name: string;
|
|
15
|
-
avatarUrl?: string | null;
|
|
16
|
-
gravatarHash?: string | null;
|
|
17
|
-
subtitle?: string | null;
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
export interface ConversationPreviewCardProps {
|
|
21
|
-
channelLabel: string;
|
|
22
|
-
author?: ConversationAuthor | null;
|
|
23
|
-
timestamp?: string | null;
|
|
24
|
-
preview: string;
|
|
25
|
-
previewAuthor?: string | null;
|
|
26
|
-
previewLeading?: ReactNode;
|
|
27
|
-
previewLineClampClassName?: "line-clamp-2" | "line-clamp-3";
|
|
28
|
-
previewClassName?: string;
|
|
29
|
-
secondaryPreview?: ReactNode;
|
|
30
|
-
secondaryPreviewClassName?: string;
|
|
31
|
-
ariaLabel?: string;
|
|
32
|
-
title?: string;
|
|
33
|
-
testId?: string;
|
|
34
|
-
marker?: ReactNode;
|
|
35
|
-
footer?: ReactNode;
|
|
36
|
-
action?: ReactNode;
|
|
37
|
-
emphasized?: boolean;
|
|
38
|
-
focused?: boolean;
|
|
39
|
-
active?: boolean;
|
|
40
|
-
onClick?: () => void;
|
|
41
|
-
onContextMenu?: (event: MouseEvent) => void;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const ConversationPreviewCard = forwardRef<HTMLButtonElement, ConversationPreviewCardProps>(
|
|
45
|
-
function ConversationPreviewCard(
|
|
46
|
-
{
|
|
47
|
-
channelLabel,
|
|
48
|
-
author,
|
|
49
|
-
timestamp,
|
|
50
|
-
preview,
|
|
51
|
-
previewAuthor,
|
|
52
|
-
previewLeading,
|
|
53
|
-
previewLineClampClassName = "line-clamp-3",
|
|
54
|
-
previewClassName,
|
|
55
|
-
secondaryPreview,
|
|
56
|
-
secondaryPreviewClassName,
|
|
57
|
-
ariaLabel,
|
|
58
|
-
title,
|
|
59
|
-
testId,
|
|
60
|
-
marker,
|
|
61
|
-
footer,
|
|
62
|
-
action,
|
|
63
|
-
emphasized = false,
|
|
64
|
-
focused = false,
|
|
65
|
-
active = false,
|
|
66
|
-
onClick,
|
|
67
|
-
onContextMenu,
|
|
68
|
-
},
|
|
69
|
-
ref,
|
|
70
|
-
) {
|
|
71
|
-
const interactive = Boolean(onClick || onContextMenu);
|
|
72
|
-
const className = `relative flex w-full items-start gap-3 border-2 p-3 text-left transition-colors hover:border-black hover:shadow-brutal-sm ${
|
|
73
|
-
interactive ? "active:border-black active:shadow-brutal-sm" : "cursor-default"
|
|
74
|
-
} ${
|
|
75
|
-
focused
|
|
76
|
-
? "border-black bg-brutal-cyan/25 shadow-brutal"
|
|
77
|
-
: active
|
|
78
|
-
? "border-black bg-white shadow-brutal-sm"
|
|
79
|
-
: "border-black/30 bg-white"
|
|
80
|
-
}`;
|
|
81
|
-
const content = (
|
|
82
|
-
<>
|
|
83
|
-
<div className="min-w-0 flex-1">
|
|
84
|
-
<div className="mb-1 flex flex-wrap items-center gap-x-2.5 gap-y-1 text-xs leading-4">
|
|
85
|
-
<span className="font-bold text-black/50">{channelLabel}</span>
|
|
86
|
-
{author && !previewAuthor ? (
|
|
87
|
-
<span className="inline-flex min-w-0 items-center gap-1 font-bold text-black">
|
|
88
|
-
{author.kind === "agent" ? (
|
|
89
|
-
<AvatarSlot
|
|
90
|
-
context="preview-mini"
|
|
91
|
-
type="agent"
|
|
92
|
-
agentAvatarUrl={author.avatarUrl ?? null}
|
|
93
|
-
/>
|
|
94
|
-
) : (
|
|
95
|
-
<AvatarSlot
|
|
96
|
-
context="preview-mini"
|
|
97
|
-
type="human"
|
|
98
|
-
humanAvatarUrl={author.avatarUrl ?? null}
|
|
99
|
-
gravatarHash={author.gravatarHash ?? null}
|
|
100
|
-
/>
|
|
101
|
-
)}
|
|
102
|
-
<span className="truncate">{author.name}</span>
|
|
103
|
-
{author.subtitle ? (
|
|
104
|
-
<span className="font-mono text-[10px] text-black/40">{author.subtitle}</span>
|
|
105
|
-
) : null}
|
|
106
|
-
</span>
|
|
107
|
-
) : null}
|
|
108
|
-
{marker}
|
|
109
|
-
{timestamp ? (
|
|
110
|
-
<span className="font-mono text-xs leading-4 text-black/40">{timestamp}</span>
|
|
111
|
-
) : null}
|
|
112
|
-
</div>
|
|
113
|
-
<p
|
|
114
|
-
className={`${previewLineClampClassName} text-sm ${emphasized ? "font-bold" : ""} ${previewClassName ?? ""}`}
|
|
115
|
-
>
|
|
116
|
-
{previewLeading ? (
|
|
117
|
-
<span className="mr-1 inline-flex align-[-1px]">{previewLeading}</span>
|
|
118
|
-
) : null}
|
|
119
|
-
{previewAuthor ? (
|
|
120
|
-
<span className="font-bold text-black/70">{previewAuthor}: </span>
|
|
121
|
-
) : null}
|
|
122
|
-
<span>{preview}</span>
|
|
123
|
-
</p>
|
|
124
|
-
{secondaryPreview ? (
|
|
125
|
-
<p
|
|
126
|
-
className={`mt-1 line-clamp-1 text-xs ${secondaryPreviewClassName ?? "text-black/45"}`}
|
|
127
|
-
>
|
|
128
|
-
{secondaryPreview}
|
|
129
|
-
</p>
|
|
130
|
-
) : null}
|
|
131
|
-
{footer ? <div className="mt-1 flex items-center gap-3">{footer}</div> : null}
|
|
132
|
-
</div>
|
|
133
|
-
{action ? <div className="shrink-0">{action}</div> : null}
|
|
134
|
-
</>
|
|
135
|
-
);
|
|
136
|
-
|
|
137
|
-
if (!interactive) {
|
|
138
|
-
return (
|
|
139
|
-
<div
|
|
140
|
-
aria-label={ariaLabel}
|
|
141
|
-
title={title}
|
|
142
|
-
data-testid={testId}
|
|
143
|
-
data-focused={focused ? "true" : undefined}
|
|
144
|
-
className={className}
|
|
145
|
-
>
|
|
146
|
-
{content}
|
|
147
|
-
</div>
|
|
148
|
-
);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
return (
|
|
152
|
-
<button
|
|
153
|
-
ref={ref}
|
|
154
|
-
type="button"
|
|
155
|
-
onClick={onClick}
|
|
156
|
-
onContextMenu={onContextMenu}
|
|
157
|
-
aria-label={ariaLabel}
|
|
158
|
-
title={title}
|
|
159
|
-
data-testid={testId}
|
|
160
|
-
data-focused={focused ? "true" : undefined}
|
|
161
|
-
className={className}
|
|
162
|
-
>
|
|
163
|
-
{content}
|
|
164
|
-
</button>
|
|
165
|
-
);
|
|
166
|
-
},
|
|
167
|
-
);
|
|
168
|
-
|
|
169
|
-
export default ConversationPreviewCard;
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import type { MouseEvent } from "react";
|
|
2
|
-
import { Dialog } from "@base-ui/react/dialog";
|
|
3
|
-
import { tv } from "../lib/tv";
|
|
4
|
-
|
|
5
|
-
const dismissBackdrop = tv({
|
|
6
|
-
slots: {
|
|
7
|
-
popup: ["fixed inset-0"],
|
|
8
|
-
},
|
|
9
|
-
variants: {
|
|
10
|
-
theme: {
|
|
11
|
-
brutal: {},
|
|
12
|
-
elegant: {},
|
|
13
|
-
},
|
|
14
|
-
},
|
|
15
|
-
defaultVariants: {
|
|
16
|
-
theme: "brutal",
|
|
17
|
-
},
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
export type DismissBackdropProps = {
|
|
21
|
-
onDismiss: () => void;
|
|
22
|
-
zIndex?: number;
|
|
23
|
-
trapContextMenu?: boolean;
|
|
24
|
-
stopPropagation?: boolean;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
export function DismissBackdrop({
|
|
28
|
-
onDismiss,
|
|
29
|
-
zIndex = 50,
|
|
30
|
-
trapContextMenu = false,
|
|
31
|
-
stopPropagation = false,
|
|
32
|
-
}: DismissBackdropProps) {
|
|
33
|
-
const { popup } = dismissBackdrop();
|
|
34
|
-
|
|
35
|
-
return (
|
|
36
|
-
<Dialog.Root
|
|
37
|
-
open
|
|
38
|
-
modal={false}
|
|
39
|
-
onOpenChange={(open) => {
|
|
40
|
-
if (!open) onDismiss();
|
|
41
|
-
}}
|
|
42
|
-
>
|
|
43
|
-
<Dialog.Portal>
|
|
44
|
-
<Dialog.Popup
|
|
45
|
-
data-slot="dismiss-backdrop"
|
|
46
|
-
className={popup()}
|
|
47
|
-
style={{ zIndex }}
|
|
48
|
-
onClick={
|
|
49
|
-
stopPropagation
|
|
50
|
-
? (event: MouseEvent<HTMLDivElement>) => {
|
|
51
|
-
event.stopPropagation();
|
|
52
|
-
event.preventDefault();
|
|
53
|
-
}
|
|
54
|
-
: onDismiss
|
|
55
|
-
}
|
|
56
|
-
onContextMenu={
|
|
57
|
-
trapContextMenu
|
|
58
|
-
? (event: MouseEvent<HTMLDivElement>) => {
|
|
59
|
-
event.preventDefault();
|
|
60
|
-
onDismiss();
|
|
61
|
-
}
|
|
62
|
-
: undefined
|
|
63
|
-
}
|
|
64
|
-
/>
|
|
65
|
-
</Dialog.Portal>
|
|
66
|
-
</Dialog.Root>
|
|
67
|
-
);
|
|
68
|
-
}
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import type { HTMLAttributes, ReactNode } from "react";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Canonical empty-state primitive — used everywhere a surface needs to say
|
|
5
|
-
* "nothing here yet" (ThreadPanel, SavedPanel, ChannelFilesPanel, TasksPanel,
|
|
6
|
-
* NotificationCenter, AgentDetailPanel, AgentActivityLog, MobileComputersPanel,
|
|
7
|
-
* ThreadsInbox, ChatPanel).
|
|
8
|
-
*
|
|
9
|
-
* Design contract locked in 2026-05-15 #proj-uiux:4e20fa91 (task #246) —
|
|
10
|
-
* cindyz raised the screenshot, Joy + 跳虎 + Bugen aligned on B + C:
|
|
11
|
-
*
|
|
12
|
-
* 1. Title is sentence case. Callers pass the literal string they want
|
|
13
|
-
* rendered. This primitive does not force case CSS. Per CLAUDE.md
|
|
14
|
-
* "Text Styles", case-mangling is reserved for section labels (12px)
|
|
15
|
-
* and dialog titles. Empty-state titles are informational hints,
|
|
16
|
-
* neither category.
|
|
17
|
-
*
|
|
18
|
-
* 2. No icon frame. The previous wrapper rendered a soft-gray box that
|
|
19
|
-
* read as OS placeholder default, visually jarring next to
|
|
20
|
-
* brutal-bordered sibling surfaces. Removed. Icons render directly
|
|
21
|
-
* inside a `text-black/40` wrapper, letting the empty state recede
|
|
22
|
-
* naturally as a quiet info hint.
|
|
23
|
-
*
|
|
24
|
-
* 3. Icon size = 36 at the callsite. With the frame gone the icon loses
|
|
25
|
-
* visual mass; size 36 (up from 28) keeps empty states from looking
|
|
26
|
-
* deflated. Muted color carries the weight without needing a box.
|
|
27
|
-
*
|
|
28
|
-
* 4. Title color is muted, not pure black. The "No …" line should
|
|
29
|
-
* sit back as empty-state body copy instead of reading like a primary
|
|
30
|
-
* heading.
|
|
31
|
-
*
|
|
32
|
-
* Contract test: `packages/web/tests/emptyState.test.ts` reverse-greps the
|
|
33
|
-
* source for the banned classes so the soft-frame look can't be
|
|
34
|
-
* resurrected accidentally.
|
|
35
|
-
*/
|
|
36
|
-
export interface EmptyStateProps extends Omit<HTMLAttributes<HTMLDivElement>, "title"> {
|
|
37
|
-
icon: ReactNode;
|
|
38
|
-
title: ReactNode;
|
|
39
|
-
description?: ReactNode;
|
|
40
|
-
action?: ReactNode;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export default function EmptyState({
|
|
44
|
-
icon,
|
|
45
|
-
title,
|
|
46
|
-
description,
|
|
47
|
-
action,
|
|
48
|
-
className = "",
|
|
49
|
-
...props
|
|
50
|
-
}: EmptyStateProps) {
|
|
51
|
-
return (
|
|
52
|
-
<div {...props} className={["px-6 py-12 text-center", className].filter(Boolean).join(" ")}>
|
|
53
|
-
<div className="mb-4 inline-flex items-center justify-center text-black/40">{icon}</div>
|
|
54
|
-
<div className="mb-2 text-lg font-heading font-semibold text-black/60">{title}</div>
|
|
55
|
-
{description ? (
|
|
56
|
-
<div className="mx-auto max-w-[32ch] text-sm leading-relaxed text-black/60">
|
|
57
|
-
{description}
|
|
58
|
-
</div>
|
|
59
|
-
) : null}
|
|
60
|
-
{action ? <div className="mt-5">{action}</div> : null}
|
|
61
|
-
</div>
|
|
62
|
-
);
|
|
63
|
-
}
|