@payglocal_ui/flux-ui 0.2.3 → 0.2.5

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/index.cjs CHANGED
@@ -3144,7 +3144,10 @@ var DropdownMenuItem = React27.forwardRef(({ className, inset, ...props }, ref)
3144
3144
  {
3145
3145
  ref,
3146
3146
  className: cn(
3147
- "relative flex cursor-default select-none items-center gap-2 rounded-lg px-3 py-2.5 text-[15px] outline-none transition-colors",
3147
+ // 13px / tighter padding: a menu drops out of a toolbar button or a
3148
+ // compact table control, and at 15px with 10px vertical padding it read
3149
+ // as a larger, separate UI than the control that opened it.
3150
+ "relative flex cursor-default select-none items-center gap-2 rounded-lg px-2.5 py-1.5 text-[13px] outline-none transition-colors",
3148
3151
  "focus:bg-muted focus:text-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
3149
3152
  inset && "pl-8",
3150
3153
  className
@@ -3211,12 +3214,17 @@ var import_jsx_runtime38 = require("react/jsx-runtime");
3211
3214
  var Select = SelectPrimitive.Root;
3212
3215
  var SelectGroup = SelectPrimitive.Group;
3213
3216
  var SelectValue = SelectPrimitive.Value;
3214
- var SelectTrigger = React28.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(
3217
+ var selectTriggerSizes = {
3218
+ md: "h-11 min-h-11 gap-2.5 px-4 py-2 text-[15px]",
3219
+ sm: "h-7 min-h-7 w-auto gap-1 px-2 py-0 text-[12px]"
3220
+ };
3221
+ var SelectTrigger = React28.forwardRef(({ className, children, size = "md", ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(
3215
3222
  SelectPrimitive.Trigger,
3216
3223
  {
3217
3224
  ref,
3218
3225
  className: cn(
3219
- "flex h-11 min-h-11 w-full items-center justify-between gap-2.5 rounded-lg border border-border bg-card px-4 py-2 text-[15px] text-foreground shadow-sm outline-none",
3226
+ "flex w-full items-center justify-between rounded-lg border border-border bg-card text-foreground shadow-sm outline-none",
3227
+ selectTriggerSizes[size],
3220
3228
  "ring-ring/50 focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-50",
3221
3229
  "[&>span]:line-clamp-1",
3222
3230
  className
@@ -3856,13 +3864,16 @@ function DataTable({
3856
3864
  rowKey,
3857
3865
  rowCta,
3858
3866
  rowAction,
3867
+ onRowClick,
3859
3868
  density = "default",
3860
3869
  tableLayout = "fixed",
3861
3870
  theadClassName,
3862
3871
  headerStyle = "surface",
3863
3872
  footerSummary = "range",
3864
3873
  footerCountLabels = { singular: "item", plural: "items" },
3865
- snug = false
3874
+ snug = false,
3875
+ expandable,
3876
+ footerLeading
3866
3877
  }) {
3867
3878
  const isControlled = controlledPage !== void 0;
3868
3879
  const [internalPage, setInternalPage] = (0, import_react8.useState)(1);
@@ -3870,6 +3881,19 @@ function DataTable({
3870
3881
  const setPage = isControlled ? (p) => onPageChange?.(p) : (p) => setInternalPage(p);
3871
3882
  const hasAction = rowAction != null || rowCta != null;
3872
3883
  const hasSpacer = tableLayout === "content";
3884
+ const [internalExpanded, setInternalExpanded] = (0, import_react8.useState)([]);
3885
+ const isExpandControlled = expandable?.expandedKeys !== void 0;
3886
+ const expandedKeys = isExpandControlled ? expandable.expandedKeys : internalExpanded;
3887
+ const hasExpand = expandable != null;
3888
+ const totalColSpan = columns.length + (hasExpand ? 1 : 0) + (hasSpacer ? 1 : 0) + (hasAction ? 1 : 0);
3889
+ const rowExpanded = (row, index) => hasExpand && (expandable.isExpandable?.(row, index) ?? true) && expandedKeys.includes(rowKeys[index]);
3890
+ const toggleExpanded = (key, row, index) => {
3891
+ const isOpen = expandedKeys.includes(key);
3892
+ const next = isOpen ? expandedKeys.filter((k) => k !== key) : [...expandedKeys, key];
3893
+ if (isExpandControlled) expandable.onExpandedChange?.(next);
3894
+ else setInternalExpanded(next);
3895
+ if (!isOpen) expandable.onExpand?.(row, index);
3896
+ };
3873
3897
  const total = totalRows ?? data.length;
3874
3898
  const totalPages = Math.ceil(total / pageSize);
3875
3899
  const paginated = isControlled ? data : data.slice((page - 1) * pageSize, page * pageSize);
@@ -3887,7 +3911,16 @@ function DataTable({
3887
3911
  const headPad = comfortable ? "px-5 py-4" : compactCellPad;
3888
3912
  const footerPad = comfortable ? "px-5 py-4" : compact ? "px-4 py-2.5" : "px-4 py-3.5";
3889
3913
  const headText = comfortable ? "text-[12px] font-medium text-muted-foreground tracking-normal" : compact ? "text-[11px] font-semibold text-muted-foreground" : "text-[11px] font-semibold text-foreground/75 dark:text-foreground/85";
3914
+ const cellPadLeft = comfortable ? 20 : compact ? snug ? 6 : 12 : 16;
3915
+ const expandIndent = 40 + cellPadLeft;
3916
+ const expandGuideLeft = cellPadLeft + 10;
3890
3917
  const actionGutter = comfortable ? 20 : compact ? 12 : 16;
3918
+ const ROW_CLICK_IGNORE = 'button, a, input, select, textarea, label, [role="button"], [role="link"], [role="checkbox"], [role="menuitem"], [role="menu"], [role="dialog"], [data-row-click-ignore]';
3919
+ const isRowClickTarget = (target, rowEl) => {
3920
+ if (!(target instanceof Element)) return false;
3921
+ const interactive = target.closest(ROW_CLICK_IGNORE);
3922
+ return !(interactive && rowEl.contains(interactive));
3923
+ };
3891
3924
  return /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(
3892
3925
  "div",
3893
3926
  {
@@ -3919,6 +3952,7 @@ function DataTable({
3919
3952
  },
3920
3953
  children: [
3921
3954
  tableLayout === "fixed" && /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("colgroup", { children: [
3955
+ hasExpand ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("col", { style: { width: 40 } }) : null,
3922
3956
  columns.map((col) => /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
3923
3957
  "col",
3924
3958
  {
@@ -3948,6 +3982,7 @@ function DataTable({
3948
3982
  headerStyle === "surface" ? "border-border" : "border-border/70"
3949
3983
  ),
3950
3984
  children: [
3985
+ hasExpand ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("th", { className: cn(headPad, "w-10 p-0"), "aria-hidden": true }) : null,
3951
3986
  columns.map((col) => /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
3952
3987
  "th",
3953
3988
  {
@@ -3972,74 +4007,144 @@ function DataTable({
3972
4007
  /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("tbody", { children: isLoading ? Array.from({ length: skeletonRows }).map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
3973
4008
  TableRowSkeleton,
3974
4009
  {
3975
- cols: columns.length,
4010
+ cols: columns.length + (hasExpand ? 1 : 0),
3976
4011
  density,
3977
4012
  snug
3978
4013
  },
3979
4014
  i
3980
- )) : paginated.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("tr", { children: /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("td", { colSpan: columns.length + (hasSpacer ? 1 : 0) + (hasAction ? 1 : 0), children: /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(EmptyState, { title: emptyTitle, description: emptyDescription }) }) }) : paginated.map((row, i) => /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(
3981
- "tr",
3982
- {
3983
- className: cn(
3984
- "group transition-colors duration-150 border-b border-border/60 last:border-b-0",
3985
- comfortable && "min-h-[56px]",
3986
- compact && "min-h-[44px]",
3987
- "hover:bg-muted/40 dark:hover:bg-muted/25",
3988
- hasAction && "hover:shadow-[0_1px_0_rgba(0,0,0,0.04)] dark:hover:shadow-none"
3989
- ),
3990
- children: [
3991
- columns.map((col) => /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
3992
- "td",
3993
- {
3994
- className: cn(
3995
- cellPad,
3996
- "align-middle",
3997
- comfortable ? cn(
3998
- "text-[13px] leading-snug",
3999
- !col.wrap && "whitespace-nowrap"
4000
- ) : compact ? cn(
4001
- "text-[13px] leading-tight",
4002
- !col.wrap && "whitespace-nowrap",
4003
- "overflow-hidden"
4004
- ) : "whitespace-nowrap overflow-hidden",
4005
- col.align === "right" ? "text-right" : col.align === "center" ? "text-center" : "text-left",
4006
- col.cellClassName
4007
- ),
4008
- children: col.render(row, i)
4009
- },
4010
- col.key
4011
- )),
4012
- hasSpacer ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("td", { className: "p-0", "aria-hidden": true }) : null,
4013
- hasAction ? (
4014
- // Zero-width sticky cell pinned to the right edge of the
4015
- // viewport. Its children are positioned absolutely so they
4016
- // float over the row (out of the 0-width cell) — the action
4017
- // stays in view while scrolling and nothing trails the last
4018
- // data column. Everything is revealed on hover only.
4019
- /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("td", { className: "sticky right-0 z-[1] w-0 p-0 align-middle", children: /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
4020
- "span",
4015
+ )) : paginated.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("tr", { children: /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("td", { colSpan: totalColSpan, children: /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(EmptyState, { title: emptyTitle, description: emptyDescription }) }) }) : paginated.flatMap((row, i) => [
4016
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(
4017
+ "tr",
4018
+ {
4019
+ className: cn(
4020
+ "group transition-colors duration-150 border-b border-border/60 last:border-b-0",
4021
+ comfortable && "min-h-[56px]",
4022
+ compact && "min-h-[44px]",
4023
+ "hover:bg-muted/40 dark:hover:bg-muted/25",
4024
+ hasAction && "hover:shadow-[0_1px_0_rgba(0,0,0,0.04)] dark:hover:shadow-none",
4025
+ // Clickable rows read as clickable, and show a focus ring
4026
+ // when reached by keyboard. `focus-visible` only, so a
4027
+ // mouse click does not leave a ring behind on the row.
4028
+ onRowClick && "cursor-pointer focus-visible:outline-none focus-visible:bg-muted/40 dark:focus-visible:bg-muted/25",
4029
+ // An open row takes its panel's background and drops the
4030
+ // divider beneath it, so the row and its detail read as one
4031
+ // block. Hover is pinned to the same value, or moving the
4032
+ // mouse over an open row would make it flicker away from
4033
+ // the panel it belongs to.
4034
+ rowExpanded(row, i) && "border-b-0 bg-muted/40 hover:bg-muted/40 dark:bg-muted/25 dark:hover:bg-muted/25"
4035
+ ),
4036
+ tabIndex: onRowClick ? 0 : void 0,
4037
+ onClick: onRowClick ? (e) => {
4038
+ if (!isRowClickTarget(e.target, e.currentTarget)) return;
4039
+ onRowClick(row, i);
4040
+ } : void 0,
4041
+ onKeyDown: onRowClick ? (e) => {
4042
+ if (e.key !== "Enter" && e.key !== " ") return;
4043
+ if (e.target !== e.currentTarget) return;
4044
+ e.preventDefault();
4045
+ onRowClick(row, i);
4046
+ } : void 0,
4047
+ children: [
4048
+ hasExpand ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("td", { className: cn(cellPad, "w-10 align-middle"), children: expandable.isExpandable?.(row, i) ?? true ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
4049
+ "button",
4021
4050
  {
4022
- className: "absolute top-1/2 -translate-y-1/2 z-[1] inline-flex items-center opacity-0 transition-opacity duration-150 group-hover:opacity-100 group-focus-within:opacity-100",
4023
- style: { right: actionGutter },
4024
- children: rowAction != null ? typeof rowAction === "function" ? rowAction(row, i) : rowAction : /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
4025
- "button",
4051
+ type: "button",
4052
+ "aria-expanded": rowExpanded(row, i),
4053
+ "aria-label": expandable.toggleLabel ?? "Toggle row details",
4054
+ onClick: () => toggleExpanded(rowKeys[i], row, i),
4055
+ className: "inline-flex h-5 w-5 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
4056
+ children: /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
4057
+ import_lucide_react25.ChevronDown,
4026
4058
  {
4027
- type: "button",
4028
- onClick: () => rowCta?.onClick?.(row),
4029
4059
  className: cn(
4030
- "inline-flex items-center font-medium text-foreground bg-card rounded-lg border border-border hover:border-muted-foreground/50 whitespace-nowrap shadow-sm",
4031
- compact ? "px-2.5 py-1 text-[11px]" : "px-3 py-1.5 text-[12px]"
4032
- ),
4033
- children: rowCta?.label
4060
+ "h-3.5 w-3.5 transition-transform duration-150",
4061
+ rowExpanded(row, i) && "rotate-180"
4062
+ )
4034
4063
  }
4035
4064
  )
4036
4065
  }
4037
- ) })
4038
- ) : null
4039
- ]
4040
- },
4041
- rowKeys[i]
4042
- )) })
4066
+ ) : null }) : null,
4067
+ columns.map((col) => /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
4068
+ "td",
4069
+ {
4070
+ className: cn(
4071
+ cellPad,
4072
+ "align-middle",
4073
+ comfortable ? cn(
4074
+ "text-[13px] leading-snug",
4075
+ !col.wrap && "whitespace-nowrap"
4076
+ ) : compact ? cn(
4077
+ "text-[13px] leading-tight",
4078
+ !col.wrap && "whitespace-nowrap",
4079
+ "overflow-hidden"
4080
+ ) : "whitespace-nowrap overflow-hidden",
4081
+ col.align === "right" ? "text-right" : col.align === "center" ? "text-center" : "text-left",
4082
+ col.cellClassName
4083
+ ),
4084
+ children: col.render(row, i)
4085
+ },
4086
+ col.key
4087
+ )),
4088
+ hasSpacer ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("td", { className: "p-0", "aria-hidden": true }) : null,
4089
+ hasAction ? (
4090
+ // Zero-width sticky cell pinned to the right edge of the
4091
+ // viewport. Its children are positioned absolutely so they
4092
+ // float over the row (out of the 0-width cell) — the action
4093
+ // stays in view while scrolling and nothing trails the last
4094
+ // data column. Everything is revealed on hover only.
4095
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("td", { className: "sticky right-0 z-[1] w-0 p-0 align-middle", children: /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
4096
+ "span",
4097
+ {
4098
+ className: "absolute top-1/2 -translate-y-1/2 z-[1] inline-flex items-center opacity-0 transition-opacity duration-150 group-hover:opacity-100 group-focus-within:opacity-100",
4099
+ style: { right: actionGutter },
4100
+ children: rowAction != null ? typeof rowAction === "function" ? rowAction(row, i) : rowAction : /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
4101
+ "button",
4102
+ {
4103
+ type: "button",
4104
+ onClick: () => rowCta?.onClick?.(row),
4105
+ className: cn(
4106
+ "inline-flex items-center font-medium text-foreground bg-card rounded-lg border border-border hover:border-muted-foreground/50 whitespace-nowrap shadow-sm",
4107
+ compact ? "px-2.5 py-1 text-[11px]" : "px-3 py-1.5 text-[12px]"
4108
+ ),
4109
+ children: rowCta?.label
4110
+ }
4111
+ )
4112
+ }
4113
+ ) })
4114
+ ) : null
4115
+ ]
4116
+ },
4117
+ rowKeys[i]
4118
+ ),
4119
+ // The panel spans every column, including the toggle, spacer and
4120
+ // action cells, so it reads as one band under its row rather
4121
+ // than as a cell inside the grid.
4122
+ rowExpanded(row, i) ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
4123
+ "tr",
4124
+ {
4125
+ className: "border-b border-border/60 bg-muted/40 last:border-b-0 dark:bg-muted/25",
4126
+ children: /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("td", { colSpan: totalColSpan, className: "p-0 align-top", children: /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(
4127
+ "div",
4128
+ {
4129
+ className: "relative",
4130
+ style: { paddingLeft: expandIndent, paddingRight: cellPadLeft },
4131
+ children: [
4132
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
4133
+ "span",
4134
+ {
4135
+ "aria-hidden": true,
4136
+ className: "absolute top-0 bottom-4 w-px bg-border",
4137
+ style: { left: expandGuideLeft }
4138
+ }
4139
+ ),
4140
+ expandable.render(row, i)
4141
+ ]
4142
+ }
4143
+ ) })
4144
+ },
4145
+ `${rowKeys[i]}__panel`
4146
+ ) : null
4147
+ ]) })
4043
4148
  ]
4044
4149
  }
4045
4150
  )
@@ -4051,27 +4156,30 @@ function DataTable({
4051
4156
  className: cn(
4052
4157
  "flex items-center gap-4 flex-wrap border-t border-border",
4053
4158
  footerPad,
4054
- footerSummary === "count" && totalPages <= 1 ? "justify-start" : "justify-between"
4159
+ footerSummary === "count" && totalPages <= 1 && !footerLeading ? "justify-start" : "justify-between"
4055
4160
  ),
4056
4161
  children: [
4057
- footerSummary === "count" ? /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("span", { className: "text-[12px] text-muted-foreground tabular-nums", children: [
4058
- /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("span", { className: "font-medium text-foreground", children: total }),
4059
- " ",
4060
- total === 1 ? footerCountLabels.singular : footerCountLabels.plural
4061
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("span", { className: "text-[12px] text-muted-foreground tabular-nums", children: [
4062
- "Showing",
4063
- " ",
4064
- /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("span", { className: "text-foreground font-medium", children: [
4065
- Math.min((page - 1) * pageSize + 1, total),
4066
- "\u2013",
4067
- Math.min(page * pageSize, total)
4068
- ] }),
4069
- " ",
4070
- "of",
4071
- " ",
4072
- /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("span", { className: "text-foreground font-medium", children: total.toLocaleString() }),
4073
- " ",
4074
- total !== 1 ? "results" : "result"
4162
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("div", { className: "flex items-center gap-3", children: [
4163
+ footerLeading,
4164
+ footerSummary === "count" ? /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("span", { className: "text-[12px] text-muted-foreground tabular-nums", children: [
4165
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("span", { className: "font-medium text-foreground", children: total }),
4166
+ " ",
4167
+ total === 1 ? footerCountLabels.singular : footerCountLabels.plural
4168
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("span", { className: "text-[12px] text-muted-foreground tabular-nums", children: [
4169
+ "Showing",
4170
+ " ",
4171
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("span", { className: "text-foreground font-medium", children: [
4172
+ Math.min((page - 1) * pageSize + 1, total),
4173
+ "\u2013",
4174
+ Math.min(page * pageSize, total)
4175
+ ] }),
4176
+ " ",
4177
+ "of",
4178
+ " ",
4179
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("span", { className: "text-foreground font-medium", children: total.toLocaleString() }),
4180
+ " ",
4181
+ total !== 1 ? "results" : "result"
4182
+ ] })
4075
4183
  ] }),
4076
4184
  (footerSummary === "range" || footerSummary === "count") && totalPages > 1 && /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("div", { className: "flex items-center gap-1", children: [
4077
4185
  /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(