@abydahana/aksara-ui 1.1.0 → 1.1.1
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/dist/aksara.css +2099 -23
- package/dist/aksara.min.css +1 -1
- package/dist/components/data/index.d.ts +1 -0
- package/dist/components/data/index.js +1 -0
- package/dist/components/data/settings.d.ts +50 -0
- package/dist/components/data/settings.js +53 -0
- package/dist/components/navigation/index.d.ts +1 -0
- package/dist/components/navigation/index.js +1 -0
- package/dist/components/navigation/sidebar.d.ts +81 -0
- package/dist/components/navigation/sidebar.js +82 -0
- package/package.json +3 -1
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export interface SettingsGroupProps {
|
|
2
|
+
children?: string;
|
|
3
|
+
/** Optional uppercase heading rendered above the group */
|
|
4
|
+
title?: string;
|
|
5
|
+
className?: string;
|
|
6
|
+
attributes?: Record<string, unknown>;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Container for a group of settings-style rows.
|
|
10
|
+
* Renders as `.list-group` with an optional uppercase section heading above.
|
|
11
|
+
*/
|
|
12
|
+
export declare function SettingsGroup(props?: SettingsGroupProps): string;
|
|
13
|
+
export declare const settingsGroup: typeof SettingsGroup;
|
|
14
|
+
export interface SettingsItemProps {
|
|
15
|
+
/** Main label text */
|
|
16
|
+
label: string;
|
|
17
|
+
/** Optional supporting description rendered below the label */
|
|
18
|
+
description?: string;
|
|
19
|
+
/** Icon HTML string, e.g. `<span class="mdi mdi-lock-outline">` */
|
|
20
|
+
icon?: string;
|
|
21
|
+
/** Value text shown on the right */
|
|
22
|
+
value?: string;
|
|
23
|
+
/** Arbitrary trailing HTML on the right, e.g. a switch or select */
|
|
24
|
+
trailing?: string;
|
|
25
|
+
/** Renders the item as `<a href>` */
|
|
26
|
+
href?: string;
|
|
27
|
+
/** Renders the item as `<button>` (when no href) */
|
|
28
|
+
action?: boolean;
|
|
29
|
+
/** Shows a `›` chevron on the trailing edge */
|
|
30
|
+
showChevron?: boolean;
|
|
31
|
+
active?: boolean;
|
|
32
|
+
disabled?: boolean;
|
|
33
|
+
className?: string;
|
|
34
|
+
attributes?: Record<string, unknown>;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* A settings-style row using `.list-group-item` with an inner flex layout.
|
|
38
|
+
* Composable as a link, button, or static element.
|
|
39
|
+
*
|
|
40
|
+
* @example Link row
|
|
41
|
+
* SettingsItem({ label: "Private profile", icon: '<span class="mdi mdi-lock-outline">', value: "Public", showChevron: true, href: "#privacy" })
|
|
42
|
+
*
|
|
43
|
+
* @example Toggle row
|
|
44
|
+
* SettingsItem({ label: "Floods & Inundations", icon: '<span class="mdi mdi-water-alert">', trailing: '<input type="checkbox" role="switch" class="form-check-input" checked />' })
|
|
45
|
+
*
|
|
46
|
+
* @example Description row
|
|
47
|
+
* SettingsItem({ label: "Verified reports only", description: "Only display verified observations.", trailing: '<input type="checkbox" role="switch" class="form-check-input" />' })
|
|
48
|
+
*/
|
|
49
|
+
export declare function SettingsItem(props: SettingsItemProps): string;
|
|
50
|
+
export declare const settingsItem: typeof SettingsItem;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { classNames, escapeHtml, toAttributes } from "../utils";
|
|
2
|
+
/**
|
|
3
|
+
* Container for a group of settings-style rows.
|
|
4
|
+
* Renders as `.list-group` with an optional uppercase section heading above.
|
|
5
|
+
*/
|
|
6
|
+
export function SettingsGroup(props = {}) {
|
|
7
|
+
const { children = "", title = "", className = "", attributes } = props;
|
|
8
|
+
const baseAttrs = toAttributes(attributes);
|
|
9
|
+
const groupHtml = `<div class="${classNames("list-group", className)}"${baseAttrs}>${children}</div>`;
|
|
10
|
+
if (!title)
|
|
11
|
+
return groupHtml.trim();
|
|
12
|
+
return `<div><p class="text-xs font-800 uppercase tracking-wider text-subtle mb-2 px-1">${escapeHtml(title)}</p><div class="${classNames("list-group", className)}">${children}</div></div>`.trim();
|
|
13
|
+
}
|
|
14
|
+
export const settingsGroup = SettingsGroup;
|
|
15
|
+
/**
|
|
16
|
+
* A settings-style row using `.list-group-item` with an inner flex layout.
|
|
17
|
+
* Composable as a link, button, or static element.
|
|
18
|
+
*
|
|
19
|
+
* @example Link row
|
|
20
|
+
* SettingsItem({ label: "Private profile", icon: '<span class="mdi mdi-lock-outline">', value: "Public", showChevron: true, href: "#privacy" })
|
|
21
|
+
*
|
|
22
|
+
* @example Toggle row
|
|
23
|
+
* SettingsItem({ label: "Floods & Inundations", icon: '<span class="mdi mdi-water-alert">', trailing: '<input type="checkbox" role="switch" class="form-check-input" checked />' })
|
|
24
|
+
*
|
|
25
|
+
* @example Description row
|
|
26
|
+
* SettingsItem({ label: "Verified reports only", description: "Only display verified observations.", trailing: '<input type="checkbox" role="switch" class="form-check-input" />' })
|
|
27
|
+
*/
|
|
28
|
+
export function SettingsItem(props) {
|
|
29
|
+
const { label, description = "", icon = "", value = "", trailing = "", href, action = false, showChevron = false, active = false, disabled = false, className = "", attributes } = props;
|
|
30
|
+
const isLink = Boolean(href);
|
|
31
|
+
const Tag = isLink ? "a" : action ? "button" : "div";
|
|
32
|
+
const classes = classNames("list-group-item", (action || isLink) && "list-group-item-action", active && "active", disabled && "disabled", className);
|
|
33
|
+
const baseAttrs = toAttributes({
|
|
34
|
+
href,
|
|
35
|
+
type: Tag === "button" ? "button" : undefined,
|
|
36
|
+
disabled: Tag === "button" && disabled ? true : undefined,
|
|
37
|
+
"aria-current": active ? "true" : undefined,
|
|
38
|
+
"aria-disabled": disabled ? "true" : undefined,
|
|
39
|
+
...attributes
|
|
40
|
+
});
|
|
41
|
+
const iconHtml = icon
|
|
42
|
+
? `<span class="flex items-center justify-center w-7 h-7 shrink-0 text-lg" aria-hidden="true">${icon}</span>`
|
|
43
|
+
: "";
|
|
44
|
+
const labelBlock = description
|
|
45
|
+
? `<span class="flex-1 min-w-0"><span class="font-600 block">${escapeHtml(label)}</span><span class="text-subtle text-sm block mt-1">${escapeHtml(description)}</span></span>`
|
|
46
|
+
: `<span class="flex-1 font-600">${escapeHtml(label)}</span>`;
|
|
47
|
+
const valueHtml = value ? `<span class="text-subtle text-sm">${escapeHtml(value)}</span>` : "";
|
|
48
|
+
const chevronHtml = showChevron
|
|
49
|
+
? `<span class="text-subtle opacity-50 text-sm ms-1" aria-hidden="true">›</span>`
|
|
50
|
+
: "";
|
|
51
|
+
return `<${Tag} class="${classes}"${baseAttrs}><div class="flex items-center gap-3">${iconHtml}${labelBlock}${valueHtml}${trailing}${chevronHtml}</div></${Tag}>`.trim();
|
|
52
|
+
}
|
|
53
|
+
export const settingsItem = SettingsItem;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
export interface SidebarNavItemProps {
|
|
2
|
+
/** Unique ID (used for tab panel targeting) */
|
|
3
|
+
id?: string;
|
|
4
|
+
/** Label text */
|
|
5
|
+
label: string;
|
|
6
|
+
/** Icon HTML string */
|
|
7
|
+
icon?: string;
|
|
8
|
+
/** Badge text or count */
|
|
9
|
+
badge?: string;
|
|
10
|
+
/** Active state */
|
|
11
|
+
active?: boolean;
|
|
12
|
+
/** Disabled state */
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
/** Link mode: render as <a href="..."> */
|
|
15
|
+
href?: string;
|
|
16
|
+
/** Tab mode: render as <button data-tabs="#panelId"> */
|
|
17
|
+
panelTarget?: string;
|
|
18
|
+
className?: string;
|
|
19
|
+
attributes?: Record<string, unknown>;
|
|
20
|
+
}
|
|
21
|
+
export interface SidebarNavProps {
|
|
22
|
+
items: SidebarNavItemProps[];
|
|
23
|
+
/** Brand section title at the top */
|
|
24
|
+
title?: string;
|
|
25
|
+
/** Brand section subtitle */
|
|
26
|
+
subtitle?: string;
|
|
27
|
+
/** Brand icon HTML */
|
|
28
|
+
brandIcon?: string;
|
|
29
|
+
className?: string;
|
|
30
|
+
attributes?: Record<string, unknown>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Sidebar navigation that supports two modes:
|
|
34
|
+
*
|
|
35
|
+
* - **Tab mode** (`panelTarget` prop): renders `<button data-tabs="#panelId">` —
|
|
36
|
+
* integrates with Aksara's tab JS to show/hide content panels.
|
|
37
|
+
* - **Link mode** (`href` prop): renders `<a href="...">` — plain navigation links.
|
|
38
|
+
*
|
|
39
|
+
* @example Tab mode
|
|
40
|
+
* SidebarNav({
|
|
41
|
+
* title: "Settings",
|
|
42
|
+
* items: [
|
|
43
|
+
* { label: "Privacy", icon: '<span class="mdi mdi-lock-outline">', panelTarget: "#panel-privacy", active: true },
|
|
44
|
+
* { label: "Notifications", icon: '<span class="mdi mdi-bell-outline">', panelTarget: "#panel-notif" },
|
|
45
|
+
* ]
|
|
46
|
+
* })
|
|
47
|
+
*
|
|
48
|
+
* @example Link mode
|
|
49
|
+
* SidebarNav({
|
|
50
|
+
* items: [
|
|
51
|
+
* { label: "Dashboard", icon: '<span class="mdi mdi-home-outline">', href: "/dashboard", active: true },
|
|
52
|
+
* { label: "Settings", icon: '<span class="mdi mdi-cog-outline">', href: "/settings" },
|
|
53
|
+
* ]
|
|
54
|
+
* })
|
|
55
|
+
*/
|
|
56
|
+
export declare function SidebarNav(props: SidebarNavProps): string;
|
|
57
|
+
export declare const sidebarNav: typeof SidebarNav;
|
|
58
|
+
export interface SidebarNavHeaderProps {
|
|
59
|
+
label: string;
|
|
60
|
+
className?: string;
|
|
61
|
+
}
|
|
62
|
+
/** A group header label inside a SidebarNav */
|
|
63
|
+
export declare function SidebarNavHeader(props: SidebarNavHeaderProps): string;
|
|
64
|
+
export declare const sidebarNavHeader: typeof SidebarNavHeader;
|
|
65
|
+
export interface SidebarLayoutProps {
|
|
66
|
+
sidebar: string;
|
|
67
|
+
content: string;
|
|
68
|
+
className?: string;
|
|
69
|
+
attributes?: Record<string, unknown>;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Two-column layout wrapper: sidebar pane on the left, content area on the right.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* SidebarLayout({
|
|
76
|
+
* sidebar: SidebarNav({ ... }),
|
|
77
|
+
* content: '<div class="p-5">Panel content here</div>'
|
|
78
|
+
* })
|
|
79
|
+
*/
|
|
80
|
+
export declare function SidebarLayout(props: SidebarLayoutProps): string;
|
|
81
|
+
export declare const sidebarLayout: typeof SidebarLayout;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { classNames, escapeHtml, toAttributes } from "../utils";
|
|
2
|
+
/**
|
|
3
|
+
* Sidebar navigation that supports two modes:
|
|
4
|
+
*
|
|
5
|
+
* - **Tab mode** (`panelTarget` prop): renders `<button data-tabs="#panelId">` —
|
|
6
|
+
* integrates with Aksara's tab JS to show/hide content panels.
|
|
7
|
+
* - **Link mode** (`href` prop): renders `<a href="...">` — plain navigation links.
|
|
8
|
+
*
|
|
9
|
+
* @example Tab mode
|
|
10
|
+
* SidebarNav({
|
|
11
|
+
* title: "Settings",
|
|
12
|
+
* items: [
|
|
13
|
+
* { label: "Privacy", icon: '<span class="mdi mdi-lock-outline">', panelTarget: "#panel-privacy", active: true },
|
|
14
|
+
* { label: "Notifications", icon: '<span class="mdi mdi-bell-outline">', panelTarget: "#panel-notif" },
|
|
15
|
+
* ]
|
|
16
|
+
* })
|
|
17
|
+
*
|
|
18
|
+
* @example Link mode
|
|
19
|
+
* SidebarNav({
|
|
20
|
+
* items: [
|
|
21
|
+
* { label: "Dashboard", icon: '<span class="mdi mdi-home-outline">', href: "/dashboard", active: true },
|
|
22
|
+
* { label: "Settings", icon: '<span class="mdi mdi-cog-outline">', href: "/settings" },
|
|
23
|
+
* ]
|
|
24
|
+
* })
|
|
25
|
+
*/
|
|
26
|
+
export function SidebarNav(props) {
|
|
27
|
+
const { items = [], title = "", subtitle = "", brandIcon = "", className = "", attributes } = props;
|
|
28
|
+
const baseAttrs = toAttributes(attributes);
|
|
29
|
+
// Brand section
|
|
30
|
+
const brandHtml = title
|
|
31
|
+
? `<div class="sidebar-nav-brand">${brandIcon ? `<span class="sidebar-nav-icon">${brandIcon}</span>` : ""}<div><span class="sidebar-nav-brand-title">${escapeHtml(title)}</span>${subtitle ? `<span class="sidebar-nav-brand-subtitle">${escapeHtml(subtitle)}</span>` : ""}</div></div>`
|
|
32
|
+
: "";
|
|
33
|
+
// Items
|
|
34
|
+
const itemsHtml = items
|
|
35
|
+
.map((item) => {
|
|
36
|
+
const isLink = Boolean(item.href);
|
|
37
|
+
const isTab = Boolean(item.panelTarget) && !isLink;
|
|
38
|
+
const Tag = isLink ? "a" : "button";
|
|
39
|
+
const itemClasses = classNames("sidebar-nav-item", item.active && "active", item.disabled && "disabled", item.className);
|
|
40
|
+
const itemAttrs = toAttributes({
|
|
41
|
+
href: isLink ? item.href : undefined,
|
|
42
|
+
type: !isLink ? "button" : undefined,
|
|
43
|
+
"data-tabs": isTab ? item.panelTarget : undefined,
|
|
44
|
+
role: isTab ? "tab" : undefined,
|
|
45
|
+
"aria-selected": isTab ? (item.active ? "true" : "false") : undefined,
|
|
46
|
+
tabindex: isTab ? (item.active ? "0" : "-1") : undefined,
|
|
47
|
+
id: isTab && item.id ? `sidebar-tab-${item.id}` : item.id,
|
|
48
|
+
"aria-controls": isTab && item.id ? item.id : undefined,
|
|
49
|
+
disabled: !isLink && item.disabled ? true : undefined,
|
|
50
|
+
"aria-disabled": item.disabled ? "true" : undefined,
|
|
51
|
+
"aria-current": isLink && item.active ? "page" : undefined,
|
|
52
|
+
...item.attributes
|
|
53
|
+
});
|
|
54
|
+
const iconHtml = item.icon ? `<span class="sidebar-nav-icon" aria-hidden="true">${item.icon}</span>` : "";
|
|
55
|
+
const badgeHtml = item.badge ? `<span class="sidebar-nav-badge">${escapeHtml(item.badge)}</span>` : "";
|
|
56
|
+
return `<${Tag} class="${itemClasses}"${itemAttrs}>${iconHtml}<span class="sidebar-nav-label">${escapeHtml(item.label)}</span>${badgeHtml}</${Tag}>`;
|
|
57
|
+
})
|
|
58
|
+
.join("");
|
|
59
|
+
return `<nav class="${classNames("sidebar-nav", className)}"${baseAttrs}>${brandHtml}${itemsHtml}</nav>`.trim();
|
|
60
|
+
}
|
|
61
|
+
export const sidebarNav = SidebarNav;
|
|
62
|
+
/** A group header label inside a SidebarNav */
|
|
63
|
+
export function SidebarNavHeader(props) {
|
|
64
|
+
const { label, className = "" } = props;
|
|
65
|
+
return `<span class="${classNames("sidebar-nav-header", className)}">${escapeHtml(label)}</span>`;
|
|
66
|
+
}
|
|
67
|
+
export const sidebarNavHeader = SidebarNavHeader;
|
|
68
|
+
/**
|
|
69
|
+
* Two-column layout wrapper: sidebar pane on the left, content area on the right.
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* SidebarLayout({
|
|
73
|
+
* sidebar: SidebarNav({ ... }),
|
|
74
|
+
* content: '<div class="p-5">Panel content here</div>'
|
|
75
|
+
* })
|
|
76
|
+
*/
|
|
77
|
+
export function SidebarLayout(props) {
|
|
78
|
+
const { sidebar, content, className = "", attributes } = props;
|
|
79
|
+
const baseAttrs = toAttributes(attributes);
|
|
80
|
+
return `<div class="${classNames("sidebar-layout", className)}"${baseAttrs}><div class="sidebar-pane">${sidebar}</div><div class="sidebar-content">${content}</div></div>`.trim();
|
|
81
|
+
}
|
|
82
|
+
export const sidebarLayout = SidebarLayout;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abydahana/aksara-ui",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -81,6 +81,8 @@
|
|
|
81
81
|
"lint:fix": "eslint src eslint.config.ts --fix",
|
|
82
82
|
"format": "prettier --write . && eslint src eslint.config.ts --fix",
|
|
83
83
|
"format:check": "prettier --check . && eslint src eslint.config.ts",
|
|
84
|
+
"validate": "npm run check && npm run format:check && npm run test && npm run build",
|
|
85
|
+
"setup:hooks": "git config core.hooksPath .githooks && chmod +x .githooks/pre-commit",
|
|
84
86
|
"prepare": "npm run build",
|
|
85
87
|
"all": "npm run format && npm run lint && npm run test && npm run build",
|
|
86
88
|
"prepublishOnly": "npm run all"
|