@cosxai/ui 1.0.0-alpha.7 → 1.0.0-alpha.9
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
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
## 1.0.0-alpha.9 (2026-09-30)
|
|
2
|
+
|
|
3
|
+
- WorkspaceRow: `badge` — what the workspace still needs, as an attention
|
|
4
|
+
badge beside the chevron ("Needs setup"; Metaroom Sign In, Set up your
|
|
5
|
+
workspace).
|
|
6
|
+
|
|
7
|
+
## 1.0.0-alpha.8 (2026-09-30)
|
|
8
|
+
|
|
9
|
+
- ThemeSwitch: `defaultOpen` starts it opened (documentation specimens).
|
|
10
|
+
- WorkspaceMark: a logo that failed before React hydrated (a server-rendered
|
|
11
|
+
page) now falls back to the initial too — onError had already fired.
|
|
12
|
+
- design.cosx.co: Patterns · Sign-in, every specimen the real component.
|
|
13
|
+
|
|
1
14
|
## 1.0.0-alpha.7 (2026-09-30)
|
|
2
15
|
|
|
3
16
|
Sign-in patterns checked against design.cosx.co Patterns · Sign-in:
|
package/package.json
CHANGED
|
@@ -21,6 +21,8 @@ export type ThemeSwitchProps = Omit<ComponentProps<'div'>, 'onChange' | 'childre
|
|
|
21
21
|
/** Unset: the ThemeProvider's setter. */
|
|
22
22
|
onChange?: ((mode: ThemeMode) => void) | undefined;
|
|
23
23
|
labels?: ThemeSwitchLabels | undefined;
|
|
24
|
+
/** Start opened (documentation specimens); it folds once the pointer leaves. */
|
|
25
|
+
defaultOpen?: boolean | undefined;
|
|
24
26
|
};
|
|
25
27
|
|
|
26
28
|
/** A click on an option only picks it once the switch has been open this long. */
|
|
@@ -33,11 +35,11 @@ const PICK_AFTER_MS = 800;
|
|
|
33
35
|
* so the first tap on touch (which opens it) steps instead of landing on
|
|
34
36
|
* whatever slid under the finger.
|
|
35
37
|
*/
|
|
36
|
-
export function ThemeSwitch({ value, onChange, labels, className, ...rest }: ThemeSwitchProps) {
|
|
38
|
+
export function ThemeSwitch({ value, onChange, labels, defaultOpen = false, className, ...rest }: ThemeSwitchProps) {
|
|
37
39
|
const theme = useTheme();
|
|
38
40
|
const mode = value ?? theme.mode;
|
|
39
41
|
const setMode = onChange ?? theme.setMode;
|
|
40
|
-
const [open, setOpen] = useState(
|
|
42
|
+
const [open, setOpen] = useState(defaultOpen);
|
|
41
43
|
const openedAt = useRef(0);
|
|
42
44
|
const show = () => {
|
|
43
45
|
if (open) return;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { ChevronRight } from 'lucide-react';
|
|
2
|
-
import { forwardRef, useState, type ComponentProps, type ReactNode } from 'react';
|
|
2
|
+
import { forwardRef, useEffect, useRef, useState, type ComponentProps, type ReactNode } from 'react';
|
|
3
3
|
|
|
4
4
|
import { cn } from '../lib/cn';
|
|
5
|
+
import { Badge } from './Badge';
|
|
5
6
|
import { Spinner } from './Spinner';
|
|
6
7
|
|
|
7
8
|
export type WorkspaceMarkProps = {
|
|
@@ -15,8 +16,15 @@ export type WorkspaceMarkProps = {
|
|
|
15
16
|
/** WorkspaceMark — a workspace's logo, or its initial on the brand yellow. */
|
|
16
17
|
export function WorkspaceMark({ name, logoUrl, size = 36 }: WorkspaceMarkProps) {
|
|
17
18
|
const [failed, setFailed] = useState(false);
|
|
19
|
+
const img = useRef<HTMLImageElement>(null);
|
|
20
|
+
// A server-rendered page can fail the image before React is listening:
|
|
21
|
+
// onError never fires, so look once it is.
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
const el = img.current;
|
|
24
|
+
if (el?.complete && el.naturalWidth === 0) setFailed(true);
|
|
25
|
+
}, [logoUrl]);
|
|
18
26
|
if (logoUrl && !failed) {
|
|
19
|
-
return <img src={logoUrl} alt="" aria-hidden style={{ width: size, height: size }} className="shrink-0 rounded-[8px] object-cover" onError={() => setFailed(true)} />;
|
|
27
|
+
return <img ref={img} src={logoUrl} alt="" aria-hidden style={{ width: size, height: size }} className="shrink-0 rounded-[8px] object-cover" onError={() => setFailed(true)} />;
|
|
20
28
|
}
|
|
21
29
|
return (
|
|
22
30
|
<span aria-hidden style={{ width: size, height: size }} className="grid shrink-0 place-items-center rounded-[8px] bg-brand-field text-[13px] font-semibold text-ink">
|
|
@@ -32,14 +40,17 @@ export type WorkspaceRowProps = Omit<ComponentProps<'button'>, 'children'> & {
|
|
|
32
40
|
logoUrl?: string | undefined;
|
|
33
41
|
/** Being opened: a spinner in place of the chevron. */
|
|
34
42
|
busy?: boolean | undefined;
|
|
43
|
+
/** A state beside the chevron, as an attention badge: "Needs setup". */
|
|
44
|
+
badge?: ReactNode;
|
|
35
45
|
};
|
|
36
46
|
|
|
37
47
|
/**
|
|
38
|
-
* WorkspaceRow — one workspace in a chooser: mark, name, detail, chevron
|
|
48
|
+
* WorkspaceRow — one workspace in a chooser: mark, name, detail, chevron;
|
|
49
|
+
* `badge` says what it still needs ("Needs setup").
|
|
39
50
|
* The whole row is the button; `busy` shows it opening (disable the others).
|
|
40
51
|
*/
|
|
41
52
|
export const WorkspaceRow = forwardRef<HTMLButtonElement, WorkspaceRowProps>(function WorkspaceRow(
|
|
42
|
-
{ name, detail, logoUrl, busy = false, className, ...rest },
|
|
53
|
+
{ name, detail, logoUrl, busy = false, badge, className, ...rest },
|
|
43
54
|
ref,
|
|
44
55
|
) {
|
|
45
56
|
return (
|
|
@@ -58,6 +69,11 @@ export const WorkspaceRow = forwardRef<HTMLButtonElement, WorkspaceRowProps>(fun
|
|
|
58
69
|
<span className="truncate text-[14px] font-medium">{name}</span>
|
|
59
70
|
{detail && <span className="truncate text-[12px] text-fg-secondary">{detail}</span>}
|
|
60
71
|
</span>
|
|
72
|
+
{badge && (
|
|
73
|
+
<span className="flex shrink-0">
|
|
74
|
+
<Badge status="attention">{badge}</Badge>
|
|
75
|
+
</span>
|
|
76
|
+
)}
|
|
61
77
|
{busy ? <Spinner size={16} /> : <ChevronRight size={16} aria-hidden className="text-fg-secondary" />}
|
|
62
78
|
</button>
|
|
63
79
|
);
|