@jackbernnie/hiyf 0.2.0 → 0.3.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/AGENTS.md +23 -3
- package/README.md +3 -3
- package/dist/components/Image.d.ts +36 -0
- package/dist/components/Image.js +40 -0
- package/dist/components/Sidebar.d.ts +9 -1
- package/dist/components/Sidebar.js +63 -9
- package/dist/components/Table.d.ts +27 -2
- package/dist/components/Table.js +179 -4
- package/dist/index.d.ts +4 -2
- package/dist/index.js +1 -0
- package/package.json +2 -2
package/AGENTS.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Building UI with `@jackbernnie/hiyf`
|
|
2
2
|
|
|
3
3
|
> Instructions for AI coding agents (Claude Code, Codex, Cursor, …). This project
|
|
4
|
-
> uses the **hiyf** design system with its **lockdown**
|
|
5
|
-
> **only** this system — off-system code fails the build on purpose.
|
|
4
|
+
> uses the **hiyf** AI design protocol (a locked design system) with its **lockdown**
|
|
5
|
+
> lint. Build UI using **only** this system — off-system code fails the build on purpose.
|
|
6
6
|
|
|
7
7
|
## The three rules (these fail `npm run build`)
|
|
8
8
|
|
|
@@ -93,7 +93,7 @@ hatch. A few key APIs:
|
|
|
93
93
|
Full catalog — actions: ButtonGroup, Input, InputGroup, InputOTP, Textarea, Select,
|
|
94
94
|
NativeSelect, Combobox, Checkbox, Switch, Slider, Toggle, ToggleGroup, RadioGroup,
|
|
95
95
|
Label, Field. Feedback/display: Status, Badge, Alert, Card, Progress, Skeleton,
|
|
96
|
-
Spinner, Avatar, Kbd, Empty, Toaster. Layout/data: Separator, AspectRatio,
|
|
96
|
+
Spinner, Avatar, Image, Kbd, Empty, Toaster. Layout/data: Separator, AspectRatio,
|
|
97
97
|
ScrollArea, Table, Carousel, Grid, GridItem. Navigation: Tabs, Accordion,
|
|
98
98
|
Collapsible, Breadcrumb, Pagination, Command, NavigationMenu, Menubar. Overlays:
|
|
99
99
|
Dialog, AlertDialog, Sheet, Drawer, Popover, HoverCard, Tooltip, DropdownMenu,
|
|
@@ -118,6 +118,26 @@ import { Home01Icon, Search01Icon } from '@jackbernnie/hiyf/icons'
|
|
|
118
118
|
`size`: `xs s m l xl`. `color` defaults to `current` (inherits text color, so
|
|
119
119
|
icons inside buttons just work); other values: `muted accent success warning danger`.
|
|
120
120
|
|
|
121
|
+
## Images
|
|
122
|
+
|
|
123
|
+
Raw `<img>` fails the build — render images through `<Image>`:
|
|
124
|
+
|
|
125
|
+
```tsx
|
|
126
|
+
import { Image } from '@jackbernnie/hiyf'
|
|
127
|
+
|
|
128
|
+
<Image src="/logo.png" alt="Logo" width={120} height={32} radius="m" fit="contain" />
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
`radius`: `none s m l xl full`. `fit`: `cover contain fill none`. `alt` is required
|
|
132
|
+
(pass `""` for purely decorative images).
|
|
133
|
+
|
|
134
|
+
## A note on `<Box>` vs `<Text>` className
|
|
135
|
+
|
|
136
|
+
`<Box>` accepts `className` for standard utility classes (`flex`, `items-center`).
|
|
137
|
+
`<Text>` does NOT — it is closed; a `className` on it is dropped and the lint flags
|
|
138
|
+
it. Put layout on a `<Box>` wrapper; use Text props (`variant`, `color`, `align`,
|
|
139
|
+
`truncate`, `monospace`) for everything else.
|
|
140
|
+
|
|
121
141
|
## When something you need doesn't exist
|
|
122
142
|
|
|
123
143
|
Work through this in order. **Don't get stuck, and don't expand the design system
|
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# @jackbernnie/hiyf
|
|
2
2
|
|
|
3
|
-
**human-in-your-face** — a
|
|
4
|
-
component set as a robust base, themed neutral, and closed down
|
|
5
|
-
expressible choices are correct ones.
|
|
3
|
+
**human-in-your-face** — an AI design protocol (a locked, LLM-safe design system):
|
|
4
|
+
the full shadcn/ui component set as a robust base, themed neutral, and closed down
|
|
5
|
+
so the only expressible choices are correct ones.
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
npm install @jackbernnie/hiyf
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image — the closed, sanctioned way to render an image asset.
|
|
3
|
+
*
|
|
4
|
+
* Raw <img> fails the lockdown lint (it is an off-system escape hatch). This is
|
|
5
|
+
* its one home: every image on screen goes through here. There is no
|
|
6
|
+
* className/style escape hatch — size with width/height, shape with radius,
|
|
7
|
+
* control object-fit with fit.
|
|
8
|
+
*/
|
|
9
|
+
declare const RADIUS: {
|
|
10
|
+
readonly none: "rounded-none";
|
|
11
|
+
readonly s: "rounded-sm";
|
|
12
|
+
readonly m: "rounded-md";
|
|
13
|
+
readonly l: "rounded-lg";
|
|
14
|
+
readonly xl: "rounded-xl";
|
|
15
|
+
readonly full: "rounded-full";
|
|
16
|
+
};
|
|
17
|
+
declare const FIT: {
|
|
18
|
+
readonly cover: "object-cover";
|
|
19
|
+
readonly contain: "object-contain";
|
|
20
|
+
readonly fill: "object-fill";
|
|
21
|
+
readonly none: "object-none";
|
|
22
|
+
};
|
|
23
|
+
export type ImageRadius = keyof typeof RADIUS;
|
|
24
|
+
export type ImageFit = keyof typeof FIT;
|
|
25
|
+
export type ImageProps = {
|
|
26
|
+
src: string;
|
|
27
|
+
/** Always required — describe the image, or pass "" for decorative images. */
|
|
28
|
+
alt: string;
|
|
29
|
+
width?: number;
|
|
30
|
+
height?: number;
|
|
31
|
+
radius?: ImageRadius;
|
|
32
|
+
fit?: ImageFit;
|
|
33
|
+
loading?: 'lazy' | 'eager';
|
|
34
|
+
};
|
|
35
|
+
export declare function Image({ src, alt, width, height, radius, fit, loading, }: ImageProps): import("react").JSX.Element;
|
|
36
|
+
export {};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { cn } from '../lib/utils.js';
|
|
3
|
+
|
|
4
|
+
const RADIUS = {
|
|
5
|
+
none: "rounded-none",
|
|
6
|
+
s: "rounded-sm",
|
|
7
|
+
m: "rounded-md",
|
|
8
|
+
l: "rounded-lg",
|
|
9
|
+
xl: "rounded-xl",
|
|
10
|
+
full: "rounded-full"
|
|
11
|
+
};
|
|
12
|
+
const FIT = {
|
|
13
|
+
cover: "object-cover",
|
|
14
|
+
contain: "object-contain",
|
|
15
|
+
fill: "object-fill",
|
|
16
|
+
none: "object-none"
|
|
17
|
+
};
|
|
18
|
+
function Image({
|
|
19
|
+
src,
|
|
20
|
+
alt,
|
|
21
|
+
width,
|
|
22
|
+
height,
|
|
23
|
+
radius = "none",
|
|
24
|
+
fit = "cover",
|
|
25
|
+
loading = "lazy"
|
|
26
|
+
}) {
|
|
27
|
+
return /* @__PURE__ */ jsx(
|
|
28
|
+
"img",
|
|
29
|
+
{
|
|
30
|
+
src,
|
|
31
|
+
alt,
|
|
32
|
+
width,
|
|
33
|
+
height,
|
|
34
|
+
loading,
|
|
35
|
+
className: cn("inline-block max-w-full", RADIUS[radius], FIT[fit])
|
|
36
|
+
}
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export { Image };
|
|
@@ -8,7 +8,8 @@ import * as React from "react";
|
|
|
8
8
|
* - items take an `icon`, can be `active`, and may nest `items` to become a
|
|
9
9
|
* collapsible sub-menu (chevron expands an indented list).
|
|
10
10
|
* - optional `brand` header (icon + title + subtitle) and `user` footer
|
|
11
|
-
* (avatar + name + email), both of which also collapse to
|
|
11
|
+
* (avatar + name + email + optional menu), both of which also collapse to
|
|
12
|
+
* their icon.
|
|
12
13
|
* - `children` is the main inset content; it gets a header with the trigger.
|
|
13
14
|
* - No `className`/`style` escape hatch. Need badges or actions? Add typed
|
|
14
15
|
* fields to SidebarNavItem here.
|
|
@@ -38,10 +39,17 @@ export interface SidebarBrand {
|
|
|
38
39
|
title: string;
|
|
39
40
|
subtitle?: string;
|
|
40
41
|
}
|
|
42
|
+
export interface SidebarUserMenuItem {
|
|
43
|
+
label: string;
|
|
44
|
+
tone?: "default" | "danger";
|
|
45
|
+
onSelect?: () => void;
|
|
46
|
+
separatorBefore?: boolean;
|
|
47
|
+
}
|
|
41
48
|
export interface SidebarUser {
|
|
42
49
|
name: string;
|
|
43
50
|
email?: string;
|
|
44
51
|
avatar?: React.ReactNode;
|
|
52
|
+
menuItems?: SidebarUserMenuItem[];
|
|
45
53
|
}
|
|
46
54
|
export interface SidebarProps {
|
|
47
55
|
groups: SidebarNavGroup[];
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
2
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { SidebarMenuButton, SidebarProvider, Sidebar as Sidebar$1, SidebarHeader, SidebarMenu, SidebarMenuItem, SidebarContent, SidebarGroup, SidebarGroupLabel, SidebarRail, SidebarInset, SidebarTrigger, SidebarMenuSub, SidebarMenuSubItem, SidebarMenuSubButton, useSidebar, SidebarFooter } from './ui/sidebar.js';
|
|
4
5
|
import { Collapsible, CollapsibleTrigger, CollapsibleContent } from './ui/collapsible.js';
|
|
6
|
+
import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuSeparator, DropdownMenuItem } from './ui/dropdown-menu.js';
|
|
5
7
|
import { TooltipProvider } from './ui/tooltip.js';
|
|
6
8
|
|
|
7
9
|
function Chevron() {
|
|
@@ -20,6 +22,25 @@ function Chevron() {
|
|
|
20
22
|
}
|
|
21
23
|
);
|
|
22
24
|
}
|
|
25
|
+
function ChevronsUpDown() {
|
|
26
|
+
return /* @__PURE__ */ jsxs(
|
|
27
|
+
"svg",
|
|
28
|
+
{
|
|
29
|
+
className: "ml-auto size-4 shrink-0 text-sidebar-foreground/60",
|
|
30
|
+
viewBox: "0 0 24 24",
|
|
31
|
+
fill: "none",
|
|
32
|
+
stroke: "currentColor",
|
|
33
|
+
strokeWidth: "2",
|
|
34
|
+
strokeLinecap: "round",
|
|
35
|
+
strokeLinejoin: "round",
|
|
36
|
+
"aria-hidden": "true",
|
|
37
|
+
children: [
|
|
38
|
+
/* @__PURE__ */ jsx("path", { d: "m7 15 5 5 5-5" }),
|
|
39
|
+
/* @__PURE__ */ jsx("path", { d: "m7 9 5-5 5 5" })
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
);
|
|
43
|
+
}
|
|
23
44
|
function NavItem({ item }) {
|
|
24
45
|
if (item.items && item.items.length > 0) {
|
|
25
46
|
return /* @__PURE__ */ jsx(
|
|
@@ -59,6 +80,45 @@ function NavItem({ item }) {
|
|
|
59
80
|
}
|
|
60
81
|
) });
|
|
61
82
|
}
|
|
83
|
+
const UserMenuButton = React.forwardRef(function UserMenuButton2({ user, showChevron, ...props }, ref) {
|
|
84
|
+
return /* @__PURE__ */ jsxs(SidebarMenuButton, { ref, size: "lg", ...props, children: [
|
|
85
|
+
user.avatar && /* @__PURE__ */ jsx("div", { className: "size-8 shrink-0 overflow-hidden rounded-lg", children: user.avatar }),
|
|
86
|
+
/* @__PURE__ */ jsxs("div", { className: "grid flex-1 text-left text-sm leading-tight", children: [
|
|
87
|
+
/* @__PURE__ */ jsx("span", { className: "truncate font-semibold", children: user.name }),
|
|
88
|
+
user.email && /* @__PURE__ */ jsx("span", { className: "truncate text-xs text-sidebar-foreground/70", children: user.email })
|
|
89
|
+
] }),
|
|
90
|
+
showChevron && /* @__PURE__ */ jsx(ChevronsUpDown, {})
|
|
91
|
+
] });
|
|
92
|
+
});
|
|
93
|
+
function UserFooter({ user }) {
|
|
94
|
+
const { isMobile } = useSidebar();
|
|
95
|
+
const menuItems = user.menuItems ?? [];
|
|
96
|
+
if (menuItems.length === 0) {
|
|
97
|
+
return /* @__PURE__ */ jsx(SidebarFooter, { children: /* @__PURE__ */ jsx(SidebarMenu, { children: /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsx(UserMenuButton, { user }) }) }) });
|
|
98
|
+
}
|
|
99
|
+
return /* @__PURE__ */ jsx(SidebarFooter, { children: /* @__PURE__ */ jsx(SidebarMenu, { children: /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxs(DropdownMenu, { children: [
|
|
100
|
+
/* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(UserMenuButton, { user, showChevron: true }) }),
|
|
101
|
+
/* @__PURE__ */ jsx(
|
|
102
|
+
DropdownMenuContent,
|
|
103
|
+
{
|
|
104
|
+
side: isMobile ? "bottom" : "right",
|
|
105
|
+
align: "end",
|
|
106
|
+
sideOffset: 4,
|
|
107
|
+
children: menuItems.map((item, index) => /* @__PURE__ */ jsxs(React.Fragment, { children: [
|
|
108
|
+
item.separatorBefore && /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
|
|
109
|
+
/* @__PURE__ */ jsx(
|
|
110
|
+
DropdownMenuItem,
|
|
111
|
+
{
|
|
112
|
+
variant: item.tone === "danger" ? "destructive" : "default",
|
|
113
|
+
onSelect: item.onSelect,
|
|
114
|
+
children: item.label
|
|
115
|
+
}
|
|
116
|
+
)
|
|
117
|
+
] }, index))
|
|
118
|
+
}
|
|
119
|
+
)
|
|
120
|
+
] }) }) }) });
|
|
121
|
+
}
|
|
62
122
|
function Sidebar({ groups, brand, user, children }) {
|
|
63
123
|
return (
|
|
64
124
|
// The icon-rail tooltips on SidebarMenuButton need a TooltipProvider; this
|
|
@@ -76,13 +136,7 @@ function Sidebar({ groups, brand, user, children }) {
|
|
|
76
136
|
group.label && /* @__PURE__ */ jsx(SidebarGroupLabel, { children: group.label }),
|
|
77
137
|
/* @__PURE__ */ jsx(SidebarMenu, { children: group.items.map((item) => /* @__PURE__ */ jsx(NavItem, { item }, item.label)) })
|
|
78
138
|
] }, groupIndex)) }),
|
|
79
|
-
user && /* @__PURE__ */ jsx(
|
|
80
|
-
user.avatar && /* @__PURE__ */ jsx("div", { className: "size-8 shrink-0 overflow-hidden rounded-lg", children: user.avatar }),
|
|
81
|
-
/* @__PURE__ */ jsxs("div", { className: "grid flex-1 text-left text-sm leading-tight", children: [
|
|
82
|
-
/* @__PURE__ */ jsx("span", { className: "truncate font-semibold", children: user.name }),
|
|
83
|
-
user.email && /* @__PURE__ */ jsx("span", { className: "truncate text-xs text-sidebar-foreground/70", children: user.email })
|
|
84
|
-
] })
|
|
85
|
-
] }) }) }) }),
|
|
139
|
+
user && /* @__PURE__ */ jsx(UserFooter, { user }),
|
|
86
140
|
/* @__PURE__ */ jsx(SidebarRail, {})
|
|
87
141
|
] }),
|
|
88
142
|
/* @__PURE__ */ jsxs(SidebarInset, { children: [
|
|
@@ -6,14 +6,25 @@ import * as React from 'react';
|
|
|
6
6
|
* apply arbitrary one-off styles and compose rows however they like. This wrapper
|
|
7
7
|
* closes it:
|
|
8
8
|
* - `columns` is a typed column descriptor array with `key`, `header`, `align`,
|
|
9
|
-
* and an optional `render` function; no
|
|
9
|
+
* controlled sort/filter metadata, and an optional `render` function; no
|
|
10
|
+
* className escapes.
|
|
10
11
|
* - `data` is a generic array of row objects; the type parameter `T` flows
|
|
11
12
|
* through so column keys and render callbacks are fully type-checked.
|
|
12
13
|
* - `caption` is an optional accessible table caption.
|
|
14
|
+
* - sorting, filtering, and row clicks are controlled callbacks only; Table
|
|
15
|
+
* never mutates, sorts, or filters `data`.
|
|
13
16
|
* - NO `className`/`style` escape hatch. Every table on screen is rendered by
|
|
14
17
|
* the same correct, on-brand structure.
|
|
15
18
|
* - Need a new alignment or row variant? Add it to the column descriptor here.
|
|
16
19
|
*/
|
|
20
|
+
export interface TableFilterOption {
|
|
21
|
+
value: string;
|
|
22
|
+
label: string;
|
|
23
|
+
}
|
|
24
|
+
export type TableSort = {
|
|
25
|
+
key: string;
|
|
26
|
+
direction: 'asc' | 'desc';
|
|
27
|
+
};
|
|
17
28
|
export interface TableColumn<T> {
|
|
18
29
|
/** Property key on each data row (used as default cell content). */
|
|
19
30
|
key: string;
|
|
@@ -21,6 +32,14 @@ export interface TableColumn<T> {
|
|
|
21
32
|
header: string;
|
|
22
33
|
/** Text alignment for both the header and cells. Defaults to 'left'. */
|
|
23
34
|
align?: 'left' | 'right' | 'center';
|
|
35
|
+
/** Whether this column exposes controlled sort actions. */
|
|
36
|
+
sortable?: boolean;
|
|
37
|
+
/** Controlled filter options. The first option conventionally means no filter/all. */
|
|
38
|
+
filterOptions?: TableFilterOption[];
|
|
39
|
+
/** Current controlled filter value for this column. */
|
|
40
|
+
filterValue?: string;
|
|
41
|
+
/** Called when a filter option is selected. */
|
|
42
|
+
onFilterChange?: (value: string) => void;
|
|
24
43
|
/**
|
|
25
44
|
* Optional custom renderer for this column's cell.
|
|
26
45
|
* Receives the full row object and returns a React node.
|
|
@@ -34,6 +53,12 @@ export interface TableProps<T> {
|
|
|
34
53
|
data: T[];
|
|
35
54
|
/** Optional accessible caption rendered below the table. */
|
|
36
55
|
caption?: string;
|
|
56
|
+
/** Current controlled sort state. */
|
|
57
|
+
sort?: TableSort;
|
|
58
|
+
/** Called when a column sort direction is selected. */
|
|
59
|
+
onSortChange?: (sort: TableSort) => void;
|
|
60
|
+
/** Called when a non-interactive part of a row is clicked or keyboard-activated. */
|
|
61
|
+
onRowClick?: (row: T) => void;
|
|
37
62
|
}
|
|
38
63
|
/** Closed generic data table — no className/style. */
|
|
39
|
-
export declare function Table<T extends Record<string, unknown>>({ columns, data, caption, }: TableProps<T>): React.JSX.Element;
|
|
64
|
+
export declare function Table<T extends Record<string, unknown>>({ columns, data, caption, sort, onSortChange, onRowClick, }: TableProps<T>): React.JSX.Element;
|
package/dist/components/Table.js
CHANGED
|
@@ -1,21 +1,196 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
3
3
|
import { Table as Table$1, TableCaption, TableHeader, TableRow, TableHead, TableBody, TableCell } from './ui/table.js';
|
|
4
|
+
import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator } from './ui/dropdown-menu.js';
|
|
4
5
|
|
|
5
6
|
const ALIGN_CLASS = {
|
|
6
7
|
left: "text-left",
|
|
7
8
|
right: "text-right",
|
|
8
9
|
center: "text-center"
|
|
9
10
|
};
|
|
11
|
+
const HEADER_FLEX_CLASS = {
|
|
12
|
+
left: "justify-start",
|
|
13
|
+
right: "justify-end",
|
|
14
|
+
center: "justify-center"
|
|
15
|
+
};
|
|
16
|
+
function CheckGlyph({ visible }) {
|
|
17
|
+
return /* @__PURE__ */ jsx(
|
|
18
|
+
"svg",
|
|
19
|
+
{
|
|
20
|
+
className: visible ? "size-4 shrink-0" : "size-4 shrink-0 opacity-0",
|
|
21
|
+
viewBox: "0 0 24 24",
|
|
22
|
+
fill: "none",
|
|
23
|
+
stroke: "currentColor",
|
|
24
|
+
strokeWidth: "2",
|
|
25
|
+
strokeLinecap: "round",
|
|
26
|
+
strokeLinejoin: "round",
|
|
27
|
+
"aria-hidden": "true",
|
|
28
|
+
children: /* @__PURE__ */ jsx("path", { d: "m20 6-11 11-5-5" })
|
|
29
|
+
}
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
function SortGlyph({ direction }) {
|
|
33
|
+
return /* @__PURE__ */ jsx(
|
|
34
|
+
"svg",
|
|
35
|
+
{
|
|
36
|
+
className: "size-4 shrink-0",
|
|
37
|
+
viewBox: "0 0 24 24",
|
|
38
|
+
fill: "none",
|
|
39
|
+
stroke: "currentColor",
|
|
40
|
+
strokeWidth: "2",
|
|
41
|
+
strokeLinecap: "round",
|
|
42
|
+
strokeLinejoin: "round",
|
|
43
|
+
"aria-hidden": "true",
|
|
44
|
+
children: direction === "asc" ? /* @__PURE__ */ jsx("path", { d: "m18 15-6-6-6 6" }) : /* @__PURE__ */ jsx("path", { d: "m6 9 6 6 6-6" })
|
|
45
|
+
}
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
function ChevronDownGlyph({ active }) {
|
|
49
|
+
return /* @__PURE__ */ jsx(
|
|
50
|
+
"svg",
|
|
51
|
+
{
|
|
52
|
+
className: active ? "size-4 shrink-0" : "size-4 shrink-0 opacity-50 transition-opacity group-hover/header:opacity-100 group-focus-within/header:opacity-100",
|
|
53
|
+
viewBox: "0 0 24 24",
|
|
54
|
+
fill: "none",
|
|
55
|
+
stroke: "currentColor",
|
|
56
|
+
strokeWidth: "2",
|
|
57
|
+
strokeLinecap: "round",
|
|
58
|
+
strokeLinejoin: "round",
|
|
59
|
+
"aria-hidden": "true",
|
|
60
|
+
children: /* @__PURE__ */ jsx("path", { d: "m6 9 6 6 6-6" })
|
|
61
|
+
}
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
function HeaderMenu({
|
|
65
|
+
column,
|
|
66
|
+
sort,
|
|
67
|
+
onSortChange
|
|
68
|
+
}) {
|
|
69
|
+
const activeSort = sort?.key === column.key ? sort : void 0;
|
|
70
|
+
const firstFilterValue = column.filterOptions?.[0]?.value;
|
|
71
|
+
const filterActive = column.filterValue !== void 0 && firstFilterValue !== void 0 && column.filterValue !== firstFilterValue;
|
|
72
|
+
const active = Boolean(activeSort) || filterActive;
|
|
73
|
+
return /* @__PURE__ */ jsxs(DropdownMenu, { children: [
|
|
74
|
+
/* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
75
|
+
"button",
|
|
76
|
+
{
|
|
77
|
+
type: "button",
|
|
78
|
+
className: active ? "inline-flex size-6 items-center justify-center rounded-md text-foreground outline-hidden transition-colors hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring" : "inline-flex size-6 items-center justify-center rounded-md text-muted-foreground outline-hidden transition-colors hover:bg-muted hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring",
|
|
79
|
+
"aria-label": `${column.header} options`,
|
|
80
|
+
children: activeSort ? /* @__PURE__ */ jsx(SortGlyph, { direction: activeSort.direction }) : /* @__PURE__ */ jsx(ChevronDownGlyph, { active })
|
|
81
|
+
}
|
|
82
|
+
) }),
|
|
83
|
+
/* @__PURE__ */ jsxs(DropdownMenuContent, { align: "end", children: [
|
|
84
|
+
column.sortable && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
85
|
+
/* @__PURE__ */ jsxs(
|
|
86
|
+
DropdownMenuItem,
|
|
87
|
+
{
|
|
88
|
+
onSelect: () => onSortChange?.({ key: column.key, direction: "asc" }),
|
|
89
|
+
children: [
|
|
90
|
+
/* @__PURE__ */ jsx(CheckGlyph, { visible: activeSort?.direction === "asc" }),
|
|
91
|
+
"Sort ascending"
|
|
92
|
+
]
|
|
93
|
+
}
|
|
94
|
+
),
|
|
95
|
+
/* @__PURE__ */ jsxs(
|
|
96
|
+
DropdownMenuItem,
|
|
97
|
+
{
|
|
98
|
+
onSelect: () => onSortChange?.({ key: column.key, direction: "desc" }),
|
|
99
|
+
children: [
|
|
100
|
+
/* @__PURE__ */ jsx(CheckGlyph, { visible: activeSort?.direction === "desc" }),
|
|
101
|
+
"Sort descending"
|
|
102
|
+
]
|
|
103
|
+
}
|
|
104
|
+
)
|
|
105
|
+
] }),
|
|
106
|
+
column.sortable && column.filterOptions && column.filterOptions.length > 0 && /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
|
|
107
|
+
column.filterOptions?.map((option) => /* @__PURE__ */ jsxs(
|
|
108
|
+
DropdownMenuItem,
|
|
109
|
+
{
|
|
110
|
+
onSelect: () => column.onFilterChange?.(option.value),
|
|
111
|
+
children: [
|
|
112
|
+
/* @__PURE__ */ jsx(CheckGlyph, { visible: column.filterValue === option.value }),
|
|
113
|
+
option.label
|
|
114
|
+
]
|
|
115
|
+
},
|
|
116
|
+
option.value
|
|
117
|
+
))
|
|
118
|
+
] })
|
|
119
|
+
] });
|
|
120
|
+
}
|
|
121
|
+
function HeaderContent({
|
|
122
|
+
column,
|
|
123
|
+
sort,
|
|
124
|
+
onSortChange
|
|
125
|
+
}) {
|
|
126
|
+
const hasMenu = column.sortable || column.filterOptions && column.filterOptions.length > 0;
|
|
127
|
+
if (!hasMenu) {
|
|
128
|
+
return /* @__PURE__ */ jsx(Fragment, { children: column.header });
|
|
129
|
+
}
|
|
130
|
+
return /* @__PURE__ */ jsxs("div", { className: `flex items-center gap-1 ${HEADER_FLEX_CLASS[column.align ?? "left"]}`, children: [
|
|
131
|
+
/* @__PURE__ */ jsx("span", { children: column.header }),
|
|
132
|
+
/* @__PURE__ */ jsx(HeaderMenu, { column, sort, onSortChange })
|
|
133
|
+
] });
|
|
134
|
+
}
|
|
135
|
+
function rowClickIsFromInteractiveElement(event) {
|
|
136
|
+
return Boolean(
|
|
137
|
+
event.target.closest(
|
|
138
|
+
'button, a, input, textarea, select, [role="menuitem"], [role="checkbox"]'
|
|
139
|
+
)
|
|
140
|
+
);
|
|
141
|
+
}
|
|
10
142
|
function Table({
|
|
11
143
|
columns,
|
|
12
144
|
data,
|
|
13
|
-
caption
|
|
145
|
+
caption,
|
|
146
|
+
sort,
|
|
147
|
+
onSortChange,
|
|
148
|
+
onRowClick
|
|
14
149
|
}) {
|
|
15
150
|
return /* @__PURE__ */ jsxs(Table$1, { children: [
|
|
16
151
|
caption && /* @__PURE__ */ jsx(TableCaption, { children: caption }),
|
|
17
|
-
/* @__PURE__ */ jsx(TableHeader, { children: /* @__PURE__ */ jsx(TableRow, { children: columns.map((col) => /* @__PURE__ */ jsx(
|
|
18
|
-
|
|
152
|
+
/* @__PURE__ */ jsx(TableHeader, { children: /* @__PURE__ */ jsx(TableRow, { children: columns.map((col) => /* @__PURE__ */ jsx(
|
|
153
|
+
TableHead,
|
|
154
|
+
{
|
|
155
|
+
className: col.sortable || col.filterOptions && col.filterOptions.length > 0 ? `${ALIGN_CLASS[col.align ?? "left"]} group/header` : ALIGN_CLASS[col.align ?? "left"],
|
|
156
|
+
children: /* @__PURE__ */ jsx(
|
|
157
|
+
HeaderContent,
|
|
158
|
+
{
|
|
159
|
+
column: col,
|
|
160
|
+
sort,
|
|
161
|
+
onSortChange
|
|
162
|
+
}
|
|
163
|
+
)
|
|
164
|
+
},
|
|
165
|
+
col.key
|
|
166
|
+
)) }) }),
|
|
167
|
+
/* @__PURE__ */ jsx(TableBody, { children: data.map((row, rowIndex) => /* @__PURE__ */ jsx(
|
|
168
|
+
TableRow,
|
|
169
|
+
{
|
|
170
|
+
className: onRowClick ? "cursor-pointer" : void 0,
|
|
171
|
+
tabIndex: onRowClick ? 0 : void 0,
|
|
172
|
+
onClick: onRowClick ? (event) => {
|
|
173
|
+
if (rowClickIsFromInteractiveElement(event)) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
onRowClick(row);
|
|
177
|
+
} : void 0,
|
|
178
|
+
onKeyDown: onRowClick ? (event) => {
|
|
179
|
+
if (event.key !== "Enter" && event.key !== " ") {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
if (event.target.closest(
|
|
183
|
+
'button, a, input, textarea, select, [role="menuitem"], [role="checkbox"]'
|
|
184
|
+
)) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
event.preventDefault();
|
|
188
|
+
onRowClick(row);
|
|
189
|
+
} : void 0,
|
|
190
|
+
children: columns.map((col) => /* @__PURE__ */ jsx(TableCell, { className: ALIGN_CLASS[col.align ?? "left"], children: col.render ? col.render(row) : String(row[col.key] ?? "") }, col.key))
|
|
191
|
+
},
|
|
192
|
+
rowIndex
|
|
193
|
+
)) })
|
|
19
194
|
] });
|
|
20
195
|
}
|
|
21
196
|
|
package/dist/index.d.ts
CHANGED
|
@@ -60,6 +60,8 @@ export { Spinner } from './components/Spinner';
|
|
|
60
60
|
export type { SpinnerProps } from './components/Spinner';
|
|
61
61
|
export { Avatar } from './components/Avatar';
|
|
62
62
|
export type { AvatarProps } from './components/Avatar';
|
|
63
|
+
export { Image } from './components/Image';
|
|
64
|
+
export type { ImageProps, ImageRadius, ImageFit } from './components/Image';
|
|
63
65
|
export { Kbd } from './components/Kbd';
|
|
64
66
|
export type { KbdProps } from './components/Kbd';
|
|
65
67
|
export { Empty } from './components/Empty';
|
|
@@ -73,7 +75,7 @@ export type { AspectRatioProps } from './components/AspectRatio';
|
|
|
73
75
|
export { ScrollArea } from './components/ScrollArea';
|
|
74
76
|
export type { ScrollAreaProps } from './components/ScrollArea';
|
|
75
77
|
export { Table } from './components/Table';
|
|
76
|
-
export type { TableProps, TableColumn } from './components/Table';
|
|
78
|
+
export type { TableProps, TableColumn, TableFilterOption, TableSort } from './components/Table';
|
|
77
79
|
export { Carousel } from './components/Carousel';
|
|
78
80
|
export type { CarouselProps } from './components/Carousel';
|
|
79
81
|
export { Tabs } from './components/Tabs';
|
|
@@ -111,7 +113,7 @@ export type { NavigationMenuProps, NavItem } from './components/NavigationMenu';
|
|
|
111
113
|
export { Menubar } from './components/Menubar';
|
|
112
114
|
export type { MenubarProps, MenuDef, MenubarItemDef } from './components/Menubar';
|
|
113
115
|
export { Sidebar } from './components/Sidebar';
|
|
114
|
-
export type { SidebarProps, SidebarNavGroup, SidebarNavItem, SidebarNavSubItem, SidebarBrand, SidebarUser, } from './components/Sidebar';
|
|
116
|
+
export type { SidebarProps, SidebarNavGroup, SidebarNavItem, SidebarNavSubItem, SidebarBrand, SidebarUser, SidebarUserMenuItem, } from './components/Sidebar';
|
|
115
117
|
export { Resizable } from './components/Resizable';
|
|
116
118
|
export type { ResizableProps } from './components/Resizable';
|
|
117
119
|
export { Chart } from './components/Chart';
|
package/dist/index.js
CHANGED
|
@@ -29,6 +29,7 @@ export { Progress } from './components/Progress.js';
|
|
|
29
29
|
export { Skeleton } from './components/Skeleton.js';
|
|
30
30
|
export { Spinner } from './components/Spinner.js';
|
|
31
31
|
export { Avatar } from './components/Avatar.js';
|
|
32
|
+
export { Image } from './components/Image.js';
|
|
32
33
|
export { Kbd } from './components/Kbd.js';
|
|
33
34
|
export { Empty } from './components/Empty.js';
|
|
34
35
|
export { Toaster } from './components/Toaster.js';
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jackbernnie/hiyf",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "human-in-your-face — a
|
|
3
|
+
"version": "0.3.1",
|
|
4
|
+
"description": "human-in-your-face — an AI design protocol (a locked, LLM-safe design system). Forked from Polar's Orbit (Apache-2.0).",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"sideEffects": [
|