@mshuja288/aksuicore 0.2.0
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 +46 -0
- package/PLAN.md +139 -0
- package/README.md +192 -0
- package/VERSIONING.md +33 -0
- package/dist/aksui.css +1633 -0
- package/dist/index.cjs +1145 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +182 -0
- package/dist/index.d.ts +182 -0
- package/dist/index.js +1127 -0
- package/dist/index.js.map +1 -0
- package/package.json +72 -0
- package/src/index.ts +5 -0
- package/src/patterns/AppShell.tsx +69 -0
- package/src/patterns/RecordWorkspace.tsx +567 -0
- package/src/patterns/index.ts +2 -0
- package/src/primitives/Button.tsx +29 -0
- package/src/primitives/DateField.tsx +255 -0
- package/src/primitives/Dialog.tsx +45 -0
- package/src/primitives/FormActions.tsx +36 -0
- package/src/primitives/Pagination.tsx +75 -0
- package/src/primitives/Select.tsx +195 -0
- package/src/primitives/SideNav.tsx +35 -0
- package/src/primitives/Snackbar.tsx +87 -0
- package/src/primitives/Topbar.tsx +89 -0
- package/src/primitives/index.ts +9 -0
- package/src/theme/aksui.css +70 -0
- package/src/theme/index.ts +3 -0
- package/src/theme/patterns.css +733 -0
- package/src/theme/primitives.css +732 -0
- package/src/theme/shell.css +92 -0
- package/src/theme/styles.css +4 -0
- package/src/theme/tokens.ts +22 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useSyncExternalStore } from "react";
|
|
4
|
+
import { createPortal } from "react-dom";
|
|
5
|
+
|
|
6
|
+
function useIsClient() {
|
|
7
|
+
return useSyncExternalStore(
|
|
8
|
+
() => () => {},
|
|
9
|
+
() => true,
|
|
10
|
+
() => false,
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function SuccessIcon() {
|
|
15
|
+
return (
|
|
16
|
+
<svg className="snackbar-icon" viewBox="0 0 24 24" aria-hidden="true">
|
|
17
|
+
<circle cx="12" cy="12" r="9" fill="none" stroke="currentColor" strokeWidth="1.8" />
|
|
18
|
+
<path
|
|
19
|
+
d="M8.2 12.2 10.8 14.8 15.8 9.6"
|
|
20
|
+
fill="none"
|
|
21
|
+
stroke="currentColor"
|
|
22
|
+
strokeWidth="1.8"
|
|
23
|
+
strokeLinecap="round"
|
|
24
|
+
strokeLinejoin="round"
|
|
25
|
+
/>
|
|
26
|
+
</svg>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function AlertIcon() {
|
|
31
|
+
return (
|
|
32
|
+
<svg className="snackbar-icon" viewBox="0 0 24 24" aria-hidden="true">
|
|
33
|
+
<path
|
|
34
|
+
d="M12 3.8 3.8 19.2h16.4L12 3.8Z"
|
|
35
|
+
fill="none"
|
|
36
|
+
stroke="currentColor"
|
|
37
|
+
strokeWidth="1.8"
|
|
38
|
+
strokeLinejoin="round"
|
|
39
|
+
/>
|
|
40
|
+
<path d="M12 10v4.2" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" />
|
|
41
|
+
<circle cx="12" cy="16.8" r="1" fill="currentColor" />
|
|
42
|
+
</svg>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function Snackbar({
|
|
47
|
+
message,
|
|
48
|
+
success,
|
|
49
|
+
onDismiss,
|
|
50
|
+
}: {
|
|
51
|
+
message: string;
|
|
52
|
+
success: boolean;
|
|
53
|
+
onDismiss?: () => void;
|
|
54
|
+
}) {
|
|
55
|
+
const isClient = useIsClient();
|
|
56
|
+
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
if (!message || !onDismiss) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const timeout = window.setTimeout(onDismiss, success ? 3200 : 4800);
|
|
63
|
+
return () => window.clearTimeout(timeout);
|
|
64
|
+
}, [message, success, onDismiss]);
|
|
65
|
+
|
|
66
|
+
if (!isClient || !message) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return createPortal(
|
|
71
|
+
<div className="snackbar-host" aria-live="polite">
|
|
72
|
+
<div className={success ? "snackbar success" : "snackbar"} role="status">
|
|
73
|
+
{success ? <SuccessIcon /> : <AlertIcon />}
|
|
74
|
+
<p>{message}</p>
|
|
75
|
+
{onDismiss ? (
|
|
76
|
+
<button type="button" className="snackbar-close" aria-label="Dismiss" onClick={onDismiss}>
|
|
77
|
+
×
|
|
78
|
+
</button>
|
|
79
|
+
) : null}
|
|
80
|
+
</div>
|
|
81
|
+
</div>,
|
|
82
|
+
document.body,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** @deprecated Prefer Snackbar — alias kept for configurator migration */
|
|
87
|
+
export const StatusMessage = Snackbar;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef, useState, type ReactNode } from "react";
|
|
4
|
+
import { Button } from "./Button";
|
|
5
|
+
|
|
6
|
+
export function Topbar({
|
|
7
|
+
title,
|
|
8
|
+
logoSrc,
|
|
9
|
+
logoAlt = "Logo",
|
|
10
|
+
username = "",
|
|
11
|
+
onLogout,
|
|
12
|
+
children,
|
|
13
|
+
}: {
|
|
14
|
+
title: string;
|
|
15
|
+
logoSrc?: string;
|
|
16
|
+
logoAlt?: string;
|
|
17
|
+
username?: string;
|
|
18
|
+
onLogout?: () => void;
|
|
19
|
+
children?: ReactNode;
|
|
20
|
+
}) {
|
|
21
|
+
const [menuOpen, setMenuOpen] = useState(false);
|
|
22
|
+
const menuRef = useRef<HTMLDivElement>(null);
|
|
23
|
+
const initial = (username || "U").slice(0, 1).toUpperCase();
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (!menuOpen) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function onPointerDown(event: MouseEvent) {
|
|
31
|
+
if (menuRef.current && !menuRef.current.contains(event.target as Node)) {
|
|
32
|
+
setMenuOpen(false);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
document.addEventListener("mousedown", onPointerDown);
|
|
37
|
+
return () => document.removeEventListener("mousedown", onPointerDown);
|
|
38
|
+
}, [menuOpen]);
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<header className="topbar">
|
|
42
|
+
<div className="topbar-brand">
|
|
43
|
+
{logoSrc ? (
|
|
44
|
+
<img
|
|
45
|
+
className="topbar-logo"
|
|
46
|
+
src={logoSrc}
|
|
47
|
+
alt={logoAlt}
|
|
48
|
+
onError={(event) => {
|
|
49
|
+
event.currentTarget.hidden = true;
|
|
50
|
+
}}
|
|
51
|
+
/>
|
|
52
|
+
) : null}
|
|
53
|
+
<h1>{title}</h1>
|
|
54
|
+
</div>
|
|
55
|
+
<div className="topbar-actions" ref={menuRef}>
|
|
56
|
+
{children}
|
|
57
|
+
{onLogout ? (
|
|
58
|
+
<>
|
|
59
|
+
<button
|
|
60
|
+
type="button"
|
|
61
|
+
className="avatar-button"
|
|
62
|
+
aria-haspopup="menu"
|
|
63
|
+
aria-expanded={menuOpen}
|
|
64
|
+
onClick={() => setMenuOpen((open) => !open)}
|
|
65
|
+
title={username || "Account"}
|
|
66
|
+
>
|
|
67
|
+
<span className="avatar">{initial}</span>
|
|
68
|
+
</button>
|
|
69
|
+
{menuOpen ? (
|
|
70
|
+
<div className="avatar-menu" role="menu">
|
|
71
|
+
{username ? <p className="avatar-name">{username}</p> : null}
|
|
72
|
+
<Button
|
|
73
|
+
variant="ghost"
|
|
74
|
+
role="menuitem"
|
|
75
|
+
onClick={() => {
|
|
76
|
+
setMenuOpen(false);
|
|
77
|
+
onLogout();
|
|
78
|
+
}}
|
|
79
|
+
>
|
|
80
|
+
Logout
|
|
81
|
+
</Button>
|
|
82
|
+
</div>
|
|
83
|
+
) : null}
|
|
84
|
+
</>
|
|
85
|
+
) : null}
|
|
86
|
+
</div>
|
|
87
|
+
</header>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { Button, type ButtonVariant } from "./Button";
|
|
2
|
+
export { Select, AppSelect, type SelectOption, type AppSelectOption } from "./Select";
|
|
3
|
+
export { DateField } from "./DateField";
|
|
4
|
+
export { Dialog, FormDialog } from "./Dialog";
|
|
5
|
+
export { Snackbar, StatusMessage } from "./Snackbar";
|
|
6
|
+
export { Topbar } from "./Topbar";
|
|
7
|
+
export { SideNav, type SideNavItem } from "./SideNav";
|
|
8
|
+
export { Pagination, type PageSizeOption } from "./Pagination";
|
|
9
|
+
export { FormActions } from "./FormActions";
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
/* Canonical aksui tokens */
|
|
3
|
+
--aks-brand: #1e2735;
|
|
4
|
+
--aks-accent: #e67e22;
|
|
5
|
+
--aks-ink: #1e2735;
|
|
6
|
+
--aks-muted: #667085;
|
|
7
|
+
--aks-line: #e6e4df;
|
|
8
|
+
--aks-field: #f6f5f3;
|
|
9
|
+
--aks-card: #ffffff;
|
|
10
|
+
--aks-danger: #b42318;
|
|
11
|
+
--aks-danger-bg: #fff1f0;
|
|
12
|
+
--aks-ok: #067647;
|
|
13
|
+
--aks-ok-bg: #ecfdf3;
|
|
14
|
+
--aks-radius: 8px;
|
|
15
|
+
--aks-row-height: 34px;
|
|
16
|
+
--aks-table-header-height: 36px;
|
|
17
|
+
--aks-side-rail-collapsed: 52px;
|
|
18
|
+
--aks-side-rail-expanded: 280px;
|
|
19
|
+
--aks-preview-panel-width: 300px;
|
|
20
|
+
|
|
21
|
+
/*
|
|
22
|
+
* Compatibility aliases for apps migrating from aksconfigurator_ui tokens.
|
|
23
|
+
* Prefer --aks-* in new code.
|
|
24
|
+
*/
|
|
25
|
+
--brand: var(--aks-brand);
|
|
26
|
+
--accent: var(--aks-accent);
|
|
27
|
+
--ink: var(--aks-ink);
|
|
28
|
+
--muted: var(--aks-muted);
|
|
29
|
+
--line: var(--aks-line);
|
|
30
|
+
--field: var(--aks-field);
|
|
31
|
+
--card: var(--aks-card);
|
|
32
|
+
--danger: var(--aks-danger);
|
|
33
|
+
--danger-bg: var(--aks-danger-bg);
|
|
34
|
+
--ok: var(--aks-ok);
|
|
35
|
+
--ok-bg: var(--aks-ok-bg);
|
|
36
|
+
--radius: var(--aks-radius);
|
|
37
|
+
--row-height: var(--aks-row-height);
|
|
38
|
+
--table-header-height: var(--aks-table-header-height);
|
|
39
|
+
--side-rail-collapsed: var(--aks-side-rail-collapsed);
|
|
40
|
+
--side-rail-expanded: var(--aks-side-rail-expanded);
|
|
41
|
+
--preview-panel-width: var(--aks-preview-panel-width);
|
|
42
|
+
|
|
43
|
+
color-scheme: light;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
*,
|
|
47
|
+
*::before,
|
|
48
|
+
*::after {
|
|
49
|
+
box-sizing: border-box;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
html,
|
|
53
|
+
body {
|
|
54
|
+
margin: 0;
|
|
55
|
+
width: 100%;
|
|
56
|
+
min-height: 100%;
|
|
57
|
+
font-family: "Segoe UI Variable", "Segoe UI", system-ui, sans-serif;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
body {
|
|
61
|
+
min-height: 100dvh;
|
|
62
|
+
color: var(--aks-ink);
|
|
63
|
+
background: #f3f2ef;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.aksui-root {
|
|
67
|
+
font-family: "Segoe UI Variable", "Segoe UI", system-ui, sans-serif;
|
|
68
|
+
color: var(--aks-ink);
|
|
69
|
+
background: #f3f2ef;
|
|
70
|
+
}
|