@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 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** lint. Build UI using
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 personal, LLM-safe design system: the full shadcn/ui
4
- component set as a robust base, themed neutral, and closed down so the only
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 their icon.
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 { jsx, jsxs } from 'react/jsx-runtime';
3
- import { SidebarProvider, Sidebar as Sidebar$1, SidebarHeader, SidebarMenu, SidebarMenuItem, SidebarMenuButton, SidebarContent, SidebarGroup, SidebarGroupLabel, SidebarFooter, SidebarRail, SidebarInset, SidebarTrigger, SidebarMenuSub, SidebarMenuSubItem, SidebarMenuSubButton } from './ui/sidebar.js';
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(SidebarFooter, { children: /* @__PURE__ */ jsx(SidebarMenu, { children: /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxs(SidebarMenuButton, { size: "lg", children: [
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 className escapes.
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;
@@ -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(TableHead, { className: ALIGN_CLASS[col.align ?? "left"], children: col.header }, col.key)) }) }),
18
- /* @__PURE__ */ jsx(TableBody, { children: data.map((row, rowIndex) => /* @__PURE__ */ jsx(TableRow, { 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)) }, rowIndex)) })
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.2.0",
4
- "description": "human-in-your-face — a personal, LLM-safe design system. Forked from Polar's Orbit (Apache-2.0).",
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": [