@cosxai/ui 1.0.0-alpha.7 → 1.0.0-alpha.8
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,10 @@
|
|
|
1
|
+
## 1.0.0-alpha.8 (2026-09-30)
|
|
2
|
+
|
|
3
|
+
- ThemeSwitch: `defaultOpen` starts it opened (documentation specimens).
|
|
4
|
+
- WorkspaceMark: a logo that failed before React hydrated (a server-rendered
|
|
5
|
+
page) now falls back to the initial too — onError had already fired.
|
|
6
|
+
- design.cosx.co: Patterns · Sign-in, every specimen the real component.
|
|
7
|
+
|
|
1
8
|
## 1.0.0-alpha.7 (2026-09-30)
|
|
2
9
|
|
|
3
10
|
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,5 +1,5 @@
|
|
|
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
5
|
import { Spinner } from './Spinner';
|
|
@@ -15,8 +15,15 @@ export type WorkspaceMarkProps = {
|
|
|
15
15
|
/** WorkspaceMark — a workspace's logo, or its initial on the brand yellow. */
|
|
16
16
|
export function WorkspaceMark({ name, logoUrl, size = 36 }: WorkspaceMarkProps) {
|
|
17
17
|
const [failed, setFailed] = useState(false);
|
|
18
|
+
const img = useRef<HTMLImageElement>(null);
|
|
19
|
+
// A server-rendered page can fail the image before React is listening:
|
|
20
|
+
// onError never fires, so look once it is.
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
const el = img.current;
|
|
23
|
+
if (el?.complete && el.naturalWidth === 0) setFailed(true);
|
|
24
|
+
}, [logoUrl]);
|
|
18
25
|
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)} />;
|
|
26
|
+
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
27
|
}
|
|
21
28
|
return (
|
|
22
29
|
<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">
|