@openwarelabs/ui 0.1.1 → 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.
@@ -1,14 +1,25 @@
1
+ import { type CSSProperties } from "react";
1
2
  export interface AvatarProps {
2
3
  /** The person's name; its initials show when there's no image. */
3
4
  name: string;
4
5
  /** A photo URL. If it fails to load, the initials show instead. */
5
6
  src?: string;
6
- /** sm 24px, md 28px, lg 36px.
7
+ /** sm 24px, md 28px, lg 36px, xl 48px, or any size in pixels, such as 96 for a team page. The initials scale with it.
7
8
  * @default "md" */
8
- size?: "sm" | "md" | "lg";
9
+ size?: AvatarSize;
9
10
  /** Give it when the avatar stands alone; beside the name, leave it out and it's hidden from screen readers. */
10
11
  label?: string;
11
12
  className?: string;
12
13
  }
14
+ export type AvatarSize = "sm" | "md" | "lg" | "xl" | number;
15
+ /**
16
+ * The class and style that size an avatar: a named size's class, or a pixel size through --ow-avatar-size.
17
+ * @internal
18
+ */
19
+ export declare function avatarSize(size: AvatarSize): {
20
+ className: string;
21
+ style?: CSSProperties;
22
+ pixels: number;
23
+ };
13
24
  /** A round picture of a person: their photo, else their initials on the brand colour, else a person icon. */
14
25
  export declare function Avatar({ name, src, size, label, className }: AvatarProps): import("react").JSX.Element;
@@ -1,14 +1,25 @@
1
+ import { type CSSProperties } from "react";
1
2
  export interface AvatarProps {
2
3
  /** The person's name; its initials show when there's no image. */
3
4
  name: string;
4
5
  /** A photo URL. If it fails to load, the initials show instead. */
5
6
  src?: string;
6
- /** sm 24px, md 28px, lg 36px.
7
+ /** sm 24px, md 28px, lg 36px, xl 48px, or any size in pixels, such as 96 for a team page. The initials scale with it.
7
8
  * @default "md" */
8
- size?: "sm" | "md" | "lg";
9
+ size?: AvatarSize;
9
10
  /** Give it when the avatar stands alone; beside the name, leave it out and it's hidden from screen readers. */
10
11
  label?: string;
11
12
  className?: string;
12
13
  }
14
+ export type AvatarSize = "sm" | "md" | "lg" | "xl" | number;
15
+ /**
16
+ * The class and style that size an avatar: a named size's class, or a pixel size through --ow-avatar-size.
17
+ * @internal
18
+ */
19
+ export declare function avatarSize(size: AvatarSize): {
20
+ className: string;
21
+ style?: CSSProperties;
22
+ pixels: number;
23
+ };
13
24
  /** A round picture of a person: their photo, else their initials on the brand colour, else a person icon. */
14
25
  export declare function Avatar({ name, src, size, label, className }: AvatarProps): import("react").JSX.Element;
@@ -1,14 +1,14 @@
1
1
  import { type ReactNode } from "react";
2
- import { type AvatarProps } from "./Avatar.cjs";
2
+ import { type AvatarSize } from "./Avatar.cjs";
3
3
  export interface AvatarGroupProps {
4
4
  /** Avatars. */
5
5
  children: ReactNode;
6
6
  /** Shows this many, then a "+3" count.
7
7
  * @default 4 */
8
8
  max?: number;
9
- /** Applied to every avatar in the group.
9
+ /** Applied to every avatar in the group: a named size or pixels.
10
10
  * @default "md" */
11
- size?: AvatarProps["size"];
11
+ size?: AvatarSize;
12
12
  /** Names the group for screen readers, e.g. "5 people on this project". */
13
13
  label?: string;
14
14
  className?: string;
@@ -20,8 +20,9 @@ export interface UserProps {
20
20
  /** A second line: an email, a role. */
21
21
  description?: ReactNode;
22
22
  src?: string;
23
- /** @default "md" */
24
- size?: AvatarProps["size"];
23
+ /** The avatar's size; the name and gap grow with it from xl (48px) up.
24
+ * @default "md" */
25
+ size?: AvatarSize;
25
26
  className?: string;
26
27
  }
27
28
  /** A person: avatar, name and a line about them, as in an account menu. */
@@ -1,14 +1,14 @@
1
1
  import { type ReactNode } from "react";
2
- import { type AvatarProps } from "./Avatar.js";
2
+ import { type AvatarSize } from "./Avatar.js";
3
3
  export interface AvatarGroupProps {
4
4
  /** Avatars. */
5
5
  children: ReactNode;
6
6
  /** Shows this many, then a "+3" count.
7
7
  * @default 4 */
8
8
  max?: number;
9
- /** Applied to every avatar in the group.
9
+ /** Applied to every avatar in the group: a named size or pixels.
10
10
  * @default "md" */
11
- size?: AvatarProps["size"];
11
+ size?: AvatarSize;
12
12
  /** Names the group for screen readers, e.g. "5 people on this project". */
13
13
  label?: string;
14
14
  className?: string;
@@ -20,8 +20,9 @@ export interface UserProps {
20
20
  /** A second line: an email, a role. */
21
21
  description?: ReactNode;
22
22
  src?: string;
23
- /** @default "md" */
24
- size?: AvatarProps["size"];
23
+ /** The avatar's size; the name and gap grow with it from xl (48px) up.
24
+ * @default "md" */
25
+ size?: AvatarSize;
25
26
  className?: string;
26
27
  }
27
28
  /** A person: avatar, name and a line about them, as in an account menu. */
@@ -3,7 +3,7 @@ export interface StepItem {
3
3
  title: ReactNode;
4
4
  /** A line under the title. Hidden once the step is done, in the vertical layout. */
5
5
  description?: ReactNode;
6
- /** A control on the right of the current step, such as a small Button. */
6
+ /** A control on the right of the step while it's the current one, such as a small Button. Other steps don't show it. */
7
7
  action?: ReactNode;
8
8
  /** Overrides the status worked out from `current`. */
9
9
  status?: "done" | "current" | "upcoming";
@@ -3,7 +3,7 @@ export interface StepItem {
3
3
  title: ReactNode;
4
4
  /** A line under the title. Hidden once the step is done, in the vertical layout. */
5
5
  description?: ReactNode;
6
- /** A control on the right of the current step, such as a small Button. */
6
+ /** A control on the right of the step while it's the current one, such as a small Button. Other steps don't show it. */
7
7
  action?: ReactNode;
8
8
  /** Overrides the status worked out from `current`. */
9
9
  status?: "done" | "current" | "upcoming";
@@ -41,6 +41,11 @@ export interface TableProps<Row> {
41
41
  selectedKeys?: Key[];
42
42
  defaultSelectedKeys?: Key[];
43
43
  onSelectionChange?(keys: Key[]): void;
44
+ /**
45
+ * Names a row for its checkbox, read out as "Select …": the person, the file, the project. Defaults to the
46
+ * first column's value when it's text or a number, else "row 3".
47
+ */
48
+ rowLabel?(row: Row): string;
44
49
  /** Shown in place of the rows when there are none, such as an EmptyState. */
45
50
  emptyContent?: ReactNode;
46
51
  /** `compact` rows are 36px; `comfortable` 44px.
@@ -52,4 +57,4 @@ export interface TableProps<Row> {
52
57
  className?: string;
53
58
  }
54
59
  /** Rows of records with sortable columns and optional selection, in a bordered card. */
55
- export declare function Table<Row extends object>({ columns, rows, rowKey, caption, hideCaption, sort, defaultSort, onSortChange, manualSort, selectionMode, selectedKeys, defaultSelectedKeys, onSelectionChange, emptyContent, density, stickyHeader, maxHeight, className, }: TableProps<Row>): import("react").JSX.Element;
60
+ export declare function Table<Row extends object>({ columns, rows, rowKey, caption, hideCaption, sort, defaultSort, onSortChange, manualSort, selectionMode, selectedKeys, defaultSelectedKeys, onSelectionChange, rowLabel, emptyContent, density, stickyHeader, maxHeight, className, }: TableProps<Row>): import("react").JSX.Element;
@@ -41,6 +41,11 @@ export interface TableProps<Row> {
41
41
  selectedKeys?: Key[];
42
42
  defaultSelectedKeys?: Key[];
43
43
  onSelectionChange?(keys: Key[]): void;
44
+ /**
45
+ * Names a row for its checkbox, read out as "Select …": the person, the file, the project. Defaults to the
46
+ * first column's value when it's text or a number, else "row 3".
47
+ */
48
+ rowLabel?(row: Row): string;
44
49
  /** Shown in place of the rows when there are none, such as an EmptyState. */
45
50
  emptyContent?: ReactNode;
46
51
  /** `compact` rows are 36px; `comfortable` 44px.
@@ -52,4 +57,4 @@ export interface TableProps<Row> {
52
57
  className?: string;
53
58
  }
54
59
  /** Rows of records with sortable columns and optional selection, in a bordered card. */
55
- export declare function Table<Row extends object>({ columns, rows, rowKey, caption, hideCaption, sort, defaultSort, onSortChange, manualSort, selectionMode, selectedKeys, defaultSelectedKeys, onSelectionChange, emptyContent, density, stickyHeader, maxHeight, className, }: TableProps<Row>): import("react").JSX.Element;
60
+ export declare function Table<Row extends object>({ columns, rows, rowKey, caption, hideCaption, sort, defaultSort, onSortChange, manualSort, selectionMode, selectedKeys, defaultSelectedKeys, onSelectionChange, rowLabel, emptyContent, density, stickyHeader, maxHeight, className, }: TableProps<Row>): import("react").JSX.Element;
@@ -4,15 +4,15 @@ export interface TooltipProps {
4
4
  label: ReactNode;
5
5
  /** The thing it explains: usually an icon-only Button with the same words as its aria-label. */
6
6
  children: ReactNode;
7
- /** Which side it prefers; it flips when there's no room.
7
+ /** Which side it prefers; it flips to the opposite side when there's no room. `right` suits icons in a folded sidebar.
8
8
  * @default "top" */
9
- placement?: "top" | "bottom";
9
+ placement?: "top" | "bottom" | "left" | "right";
10
10
  /** Milliseconds of hovering before it shows. Moving to a neighbour while one is showing skips the wait.
11
11
  * @default 400 */
12
12
  delay?: number;
13
13
  }
14
14
  /**
15
- * A small dark label above its child on hover or keyboard focus. It's for sighted users only: the child
15
+ * A small dark label beside its child (above, unless `placement` says otherwise) on hover or keyboard focus. It's for sighted users only: the child
16
16
  * carries the same words for screen readers (an aria-label, or visually hidden text), so they aren't read twice.
17
17
  * It sits in the top layer, so scroll areas and dialogs can't clip it.
18
18
  */
@@ -4,15 +4,15 @@ export interface TooltipProps {
4
4
  label: ReactNode;
5
5
  /** The thing it explains: usually an icon-only Button with the same words as its aria-label. */
6
6
  children: ReactNode;
7
- /** Which side it prefers; it flips when there's no room.
7
+ /** Which side it prefers; it flips to the opposite side when there's no room. `right` suits icons in a folded sidebar.
8
8
  * @default "top" */
9
- placement?: "top" | "bottom";
9
+ placement?: "top" | "bottom" | "left" | "right";
10
10
  /** Milliseconds of hovering before it shows. Moving to a neighbour while one is showing skips the wait.
11
11
  * @default 400 */
12
12
  delay?: number;
13
13
  }
14
14
  /**
15
- * A small dark label above its child on hover or keyboard focus. It's for sighted users only: the child
15
+ * A small dark label beside its child (above, unless `placement` says otherwise) on hover or keyboard focus. It's for sighted users only: the child
16
16
  * carries the same words for screen readers (an aria-label, or visually hidden text), so they aren't read twice.
17
17
  * It sits in the top layer, so scroll areas and dialogs can't clip it.
18
18
  */
@@ -39,6 +39,7 @@
39
39
  back into it. useFloating sets data-side and --ow-floating-origin; usePresence sets data-state. */
40
40
  .ow-floating {
41
41
  --ow-floating-shift: -4px;
42
+ --ow-floating-shift-x: 0px;
42
43
  transform-origin: var(--ow-floating-origin, top center);
43
44
  }
44
45
 
@@ -46,6 +47,16 @@
46
47
  --ow-floating-shift: 4px;
47
48
  }
48
49
 
50
+ .ow-floating[data-side="left"] {
51
+ --ow-floating-shift: 0px;
52
+ --ow-floating-shift-x: 4px;
53
+ }
54
+
55
+ .ow-floating[data-side="right"] {
56
+ --ow-floating-shift: 0px;
57
+ --ow-floating-shift-x: -4px;
58
+ }
59
+
49
60
  .ow-floating[data-state="open"] {
50
61
  animation: ow-floating-in var(--ow-duration-base) var(--ow-ease-emphasized);
51
62
  }
@@ -58,14 +69,14 @@
58
69
  @keyframes ow-floating-in {
59
70
  from {
60
71
  opacity: 0;
61
- transform: translateY(var(--ow-floating-shift)) scale(0.96);
72
+ transform: translate(var(--ow-floating-shift-x), var(--ow-floating-shift)) scale(0.96);
62
73
  }
63
74
  }
64
75
 
65
76
  @keyframes ow-floating-out {
66
77
  to {
67
78
  opacity: 0;
68
- transform: translateY(var(--ow-floating-shift)) scale(0.97);
79
+ transform: translate(var(--ow-floating-shift-x), var(--ow-floating-shift)) scale(0.97);
69
80
  }
70
81
  }
71
82
 
@@ -494,6 +505,8 @@
494
505
  border-radius: var(--ow-radius-container);
495
506
  background: var(--ow-track);
496
507
  padding: var(--ow-space-2xs);
508
+ /* A hairline edge, so the well shows on the canvas as well as on a surface. */
509
+ box-shadow: inset 0 0 0 1px var(--ow-border);
497
510
  }
498
511
 
499
512
  .ow-segmented--full {
@@ -797,6 +810,8 @@
797
810
  .ow-input-group {
798
811
  display: flex;
799
812
  width: 100%;
813
+ /* Shrink in a narrow grid or flex column instead of pushing past it. */
814
+ min-width: 0;
800
815
  height: 40px;
801
816
  align-items: center;
802
817
  gap: var(--ow-space-sm);
@@ -843,6 +858,8 @@
843
858
  }
844
859
 
845
860
  .ow-input-group__input {
861
+ /* A percentage width lets the field's minimum size come from its start and end content alone. */
862
+ width: 100%;
846
863
  min-width: 0;
847
864
  height: 100%;
848
865
  flex: 1;
@@ -1449,7 +1466,7 @@
1449
1466
  width: 16px;
1450
1467
  height: 16px;
1451
1468
  border-radius: var(--ow-radius-pill);
1452
- background: #ffffff;
1469
+ background: var(--ow-knob);
1453
1470
  box-shadow: var(--ow-shadow-control);
1454
1471
  transform: translateX(2px);
1455
1472
  transition:
@@ -1471,7 +1488,7 @@
1471
1488
  }
1472
1489
 
1473
1490
  /* components/Slider/Slider.css */
1474
- /* Slider: native range inputs over a drawn track. The thumb is the switch's knob: white, round, with a soft shadow. */
1491
+ /* Slider: native range inputs over a drawn track. The thumb is the switch's knob (ow-knob): round, with a soft shadow. */
1475
1492
  .ow-slider {
1476
1493
  display: grid;
1477
1494
  gap: var(--ow-space-sm);
@@ -1562,7 +1579,7 @@
1562
1579
  appearance: none;
1563
1580
  border: 1px solid var(--ow-border-strong);
1564
1581
  border-radius: 50%;
1565
- background: #ffffff;
1582
+ background: var(--ow-knob);
1566
1583
  box-shadow: 0 1px 3px rgba(15, 23, 42, 0.18);
1567
1584
  pointer-events: auto;
1568
1585
  cursor: grab;
@@ -1575,7 +1592,7 @@
1575
1592
  height: 18px;
1576
1593
  border: 1px solid var(--ow-border-strong);
1577
1594
  border-radius: 50%;
1578
- background: #ffffff;
1595
+ background: var(--ow-knob);
1579
1596
  box-shadow: 0 1px 3px rgba(15, 23, 42, 0.18);
1580
1597
  pointer-events: auto;
1581
1598
  cursor: grab;
@@ -2185,7 +2202,7 @@
2185
2202
  }
2186
2203
 
2187
2204
  /* components/Tooltip/Tooltip.css */
2188
- /* Tooltip: a small dark label in the top layer, above the child (or below when there's no room). */
2205
+ /* Tooltip: a small dark label in the top layer, on the side of the child it prefers (or the other side when there's no room). */
2189
2206
  .ow-tooltip {
2190
2207
  display: inline-flex;
2191
2208
  }
@@ -3989,6 +4006,19 @@
3989
4006
  font-size: 13px;
3990
4007
  }
3991
4008
 
4009
+ .ow-avatar--xl {
4010
+ width: 48px;
4011
+ height: 48px;
4012
+ font-size: 17px;
4013
+ }
4014
+
4015
+ /* Any size in pixels, through --ow-avatar-size: the initials stay in proportion. */
4016
+ .ow-avatar--custom {
4017
+ width: var(--ow-avatar-size);
4018
+ height: var(--ow-avatar-size);
4019
+ font-size: calc(var(--ow-avatar-size) * 0.36);
4020
+ }
4021
+
3992
4022
  .ow-avatar__image {
3993
4023
  width: 100%;
3994
4024
  height: 100%;
@@ -4025,6 +4055,18 @@
4025
4055
  gap: var(--ow-space-md);
4026
4056
  }
4027
4057
 
4058
+ .ow-user--xl {
4059
+ gap: var(--ow-space-lg);
4060
+ }
4061
+
4062
+ .ow-user--xl .ow-user__name {
4063
+ font-size: 15px;
4064
+ }
4065
+
4066
+ .ow-user--xl .ow-user__description {
4067
+ font-size: 13px;
4068
+ }
4069
+
4028
4070
  .ow-user__text {
4029
4071
  display: grid;
4030
4072
  min-width: 0;
@@ -1,5 +1,5 @@
1
1
  import { type CSSProperties, type RefObject } from "react";
2
- export type Placement = "bottom-start" | "bottom-end" | "bottom" | "top-start" | "top-end" | "top";
2
+ export type Placement = "bottom-start" | "bottom-end" | "bottom" | "top-start" | "top-end" | "top" | "left" | "right";
3
3
  interface Options {
4
4
  open: boolean;
5
5
  anchorRef: RefObject<HTMLElement | null>;
@@ -14,7 +14,7 @@ interface Options {
14
14
  }
15
15
  /**
16
16
  * Positions a floating element (a popover, a listbox) against its anchor with fixed coordinates, flips it above
17
- * or below when there's no room, keeps it on screen, and follows the anchor when the page scrolls or resizes.
17
+ * or below (or, for "left" and "right", to the other side) when there's no room, keeps it on screen, and follows the anchor when the page scrolls or resizes.
18
18
  * It also puts the element in the browser's top layer (popover="manual"), so dialogs and overflow can't clip it.
19
19
  * Pass the element's `mounted` from usePresence as `open`, so it stays placed while it animates out.
20
20
  */
@@ -1,5 +1,5 @@
1
1
  import { type CSSProperties, type RefObject } from "react";
2
- export type Placement = "bottom-start" | "bottom-end" | "bottom" | "top-start" | "top-end" | "top";
2
+ export type Placement = "bottom-start" | "bottom-end" | "bottom" | "top-start" | "top-end" | "top" | "left" | "right";
3
3
  interface Options {
4
4
  open: boolean;
5
5
  anchorRef: RefObject<HTMLElement | null>;
@@ -14,7 +14,7 @@ interface Options {
14
14
  }
15
15
  /**
16
16
  * Positions a floating element (a popover, a listbox) against its anchor with fixed coordinates, flips it above
17
- * or below when there's no room, keeps it on screen, and follows the anchor when the page scrolls or resizes.
17
+ * or below (or, for "left" and "right", to the other side) when there's no room, keeps it on screen, and follows the anchor when the page scrolls or resizes.
18
18
  * It also puts the element in the browser's top layer (popover="manual"), so dialogs and overflow can't clip it.
19
19
  * Pass the element's `mounted` from usePresence as `open`, so it stays placed while it animates out.
20
20
  */
package/dist/index.cjs CHANGED
@@ -1172,6 +1172,25 @@ function useFloating({
1172
1172
  const rect = anchor.getBoundingClientRect();
1173
1173
  const width = Math.max(floating.offsetWidth, matchWidth ? rect.width : 0);
1174
1174
  const height = floating.scrollHeight;
1175
+ if (placement === "left" || placement === "right") {
1176
+ const roomLeft = rect.left - offset - margin;
1177
+ const roomRight = window.innerWidth - rect.right - offset - margin;
1178
+ const onLeft = placement === "left" ? width <= roomLeft || roomLeft > roomRight : width > roomRight && roomLeft > roomRight;
1179
+ const top2 = Math.min(Math.max(margin, rect.top + rect.height / 2 - height / 2), window.innerHeight - height - margin);
1180
+ floating.dataset.side = onLeft ? "left" : "right";
1181
+ setStyle({
1182
+ position: "fixed",
1183
+ inset: "auto",
1184
+ margin: 0,
1185
+ left: onLeft ? "auto" : rect.right + offset,
1186
+ right: onLeft ? window.innerWidth - rect.left + offset : "auto",
1187
+ top: top2,
1188
+ bottom: "auto",
1189
+ maxHeight: Math.max(120, Math.min(maxHeight, window.innerHeight - 2 * margin)),
1190
+ ["--ow-floating-origin"]: `${onLeft ? "right" : "left"} center`
1191
+ });
1192
+ return;
1193
+ }
1175
1194
  const below = window.innerHeight - rect.bottom - offset - margin;
1176
1195
  const above = rect.top - offset - margin;
1177
1196
  const wantsTop = placement.startsWith("top");
@@ -3643,7 +3662,7 @@ function Steps({ items: items2, current = 0, orientation = "vertical", title, la
3643
3662
  ] }),
3644
3663
  item.description && (orientation === "horizontal" || status !== "done") ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "ow-steps__description", children: item.description }) : null
3645
3664
  ] }),
3646
- item.action && status !== "done" && orientation === "vertical" ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "ow-steps__action", children: item.action }) : null
3665
+ item.action && status === "current" && orientation === "vertical" ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "ow-steps__action", children: item.action }) : null
3647
3666
  ]
3648
3667
  },
3649
3668
  index
@@ -3726,6 +3745,7 @@ function Table({
3726
3745
  selectedKeys,
3727
3746
  defaultSelectedKeys = [],
3728
3747
  onSelectionChange,
3748
+ rowLabel,
3729
3749
  emptyContent = "Nothing here yet.",
3730
3750
  density = "comfortable",
3731
3751
  stickyHeader = false,
@@ -3735,6 +3755,11 @@ function Table({
3735
3755
  const [currentSort, setSort] = useControllableState(sort, defaultSort, onSortChange);
3736
3756
  const [selected, setSelected] = useControllableState(selectedKeys, defaultSelectedKeys, onSelectionChange);
3737
3757
  const keyOf = (row) => typeof rowKey === "function" ? rowKey(row) : row[rowKey];
3758
+ const labelOf = (row, index) => {
3759
+ if (rowLabel) return rowLabel(row);
3760
+ const first = columns[0] ? row[columns[0].key] : void 0;
3761
+ return typeof first === "string" || typeof first === "number" ? String(first) : `row ${index + 1}`;
3762
+ };
3738
3763
  const field = (row, key) => row[key];
3739
3764
  const sorted = (0, import_react41.useMemo)(() => {
3740
3765
  if (!currentSort || manualSort) return rows;
@@ -3791,7 +3816,7 @@ function Table({
3791
3816
  );
3792
3817
  })
3793
3818
  ] }) }),
3794
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("tbody", { children: sorted.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("tr", { children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("td", { className: "ow-table__empty", colSpan: columns.length + (selectable ? 1 : 0), children: emptyContent }) }) : sorted.map((row) => {
3819
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("tbody", { children: sorted.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("tr", { children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("td", { className: "ow-table__empty", colSpan: columns.length + (selectable ? 1 : 0), children: emptyContent }) }) : sorted.map((row, index) => {
3795
3820
  const key = keyOf(row);
3796
3821
  const isSelected = selected.includes(key);
3797
3822
  return /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("tr", { className: cx(isSelected && "ow-table__row--selected"), children: [
@@ -3799,7 +3824,7 @@ function Table({
3799
3824
  Checkbox,
3800
3825
  {
3801
3826
  size: "sm",
3802
- "aria-label": "Select row",
3827
+ "aria-label": `Select ${labelOf(row, index)}`,
3803
3828
  checked: isSelected,
3804
3829
  onChange: (event) => setSelected(
3805
3830
  event.target.checked ? [...selected, key] : selected.filter((k) => k !== key)
@@ -4029,16 +4054,25 @@ function ColorSwatch({ color, name, value, size = "md", shape = "square", classN
4029
4054
  // src/components/Avatar/Avatar.tsx
4030
4055
  var import_react43 = require("react");
4031
4056
  var import_jsx_runtime45 = require("react/jsx-runtime");
4057
+ var namedSizes = { sm: 24, md: 28, lg: 36, xl: 48 };
4058
+ function avatarSize(size) {
4059
+ if (typeof size === "number") {
4060
+ return { className: "ow-avatar--custom", style: { "--ow-avatar-size": `${size}px` }, pixels: size };
4061
+ }
4062
+ return { className: `ow-avatar--${size}`, pixels: namedSizes[size] };
4063
+ }
4032
4064
  function Avatar({ name, src, size = "md", label, className }) {
4033
4065
  const initials = getInitials(name);
4034
4066
  const [failedImage, setFailedImage] = (0, import_react43.useState)();
4035
4067
  const showImage = src && src !== failedImage;
4068
+ const sizing = avatarSize(size);
4036
4069
  return /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
4037
4070
  "span",
4038
4071
  {
4039
- className: cx("ow-avatar", `ow-avatar--${size}`, !initials && !showImage && "ow-avatar--empty", className),
4072
+ className: cx("ow-avatar", sizing.className, !initials && !showImage && "ow-avatar--empty", className),
4073
+ style: sizing.style,
4040
4074
  ...label ? { role: "img", "aria-label": label } : { "aria-hidden": true },
4041
- children: showImage ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("img", { src, alt: "", className: "ow-avatar__image", onError: () => setFailedImage(src) }) : initials || /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(UserIcon, { size: size === "sm" ? 13 : size === "lg" ? 18 : 15 })
4075
+ children: showImage ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("img", { src, alt: "", className: "ow-avatar__image", onError: () => setFailedImage(src) }) : initials || /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(UserIcon, { size: Math.round(sizing.pixels * 0.52) })
4042
4076
  }
4043
4077
  );
4044
4078
  }
@@ -4048,11 +4082,12 @@ var import_react44 = require("react");
4048
4082
  var import_jsx_runtime46 = require("react/jsx-runtime");
4049
4083
  function AvatarGroup({ children, max = 4, size = "md", label, className }) {
4050
4084
  const avatars = import_react44.Children.toArray(children).filter(import_react44.isValidElement);
4085
+ const sizing = avatarSize(size);
4051
4086
  const shown = avatars.slice(0, max);
4052
4087
  const hidden = avatars.length - shown.length;
4053
4088
  return /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { role: "group", "aria-label": label, className: cx("ow-avatar-group", className), children: [
4054
4089
  shown.map((avatar) => (0, import_react44.cloneElement)(avatar, { size })),
4055
- hidden > 0 ? /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("span", { className: cx("ow-avatar", `ow-avatar--${size}`, "ow-avatar--count"), children: [
4090
+ hidden > 0 ? /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("span", { className: cx("ow-avatar", sizing.className, "ow-avatar--count"), style: sizing.style, children: [
4056
4091
  /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("span", { "aria-hidden": "true", children: [
4057
4092
  "+",
4058
4093
  hidden
@@ -4065,8 +4100,12 @@ function AvatarGroup({ children, max = 4, size = "md", label, className }) {
4065
4100
  ] }) : null
4066
4101
  ] });
4067
4102
  }
4103
+ function userSize(size) {
4104
+ if (typeof size !== "number") return `ow-user--${size}`;
4105
+ return size >= 48 ? "ow-user--xl" : size >= 36 ? "ow-user--lg" : size >= 28 ? "ow-user--md" : "ow-user--sm";
4106
+ }
4068
4107
  function User({ name, description, src, size = "md", className }) {
4069
- return /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: cx("ow-user", `ow-user--${size}`, className), children: [
4108
+ return /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: cx("ow-user", userSize(size), className), children: [
4070
4109
  /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(Avatar, { name, src, size }),
4071
4110
  /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "ow-user__text", children: [
4072
4111
  /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("span", { className: "ow-user__name", children: name }),