@l3mpire/ui 3.5.0 → 3.6.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.
package/USAGE.md CHANGED
@@ -156,13 +156,15 @@ icon-spin classes are the load-bearing ones; the rest are decorative.
156
156
 
157
157
  | Class | Usage |
158
158
  |---|---|
159
- | `bg-ai-gradient` | Brand AI gradient (blue → violet → indigo, 125deg) |
159
+ | `bg-ai-gradient` | Decorative brand AI gradient (blue → violet → indigo, 125deg) |
160
+ | `bg-ai-subtle` / `-hover` / `-pressed` | Translucent AI wash (ghost AI surfaces) |
161
+ | `bg-ai-strong` / `-hover` / `-pressed` | Vivid AI gradient (solid AI fill) |
162
+ | `ai-grad-text` | Gradient-clipped text/icons (outlined/ghost AI); follows `[data-ai-text]` host hover/pressed |
160
163
  | `ai-shimmer-text` | Text with a tertiary → info → tertiary sweep (2.1s) |
161
164
  | `ai-icon-spin` | Wrapper that rotates its child icon (see FA Kit caveat below) |
162
165
  | `ai-skeleton` | Surface skeleton with a horizontal sheen sweep |
163
166
  | `ai-cell-reveal` | One-shot blur(3px) → clear, 320ms fade-in for cells |
164
167
  | `ai-pulse` | Breathing scale + opacity for dot indicators |
165
- | `ai-gradient-text` | Flat info-blue accent for AI labels |
166
168
  | `ai-orb` | Decorative gradient disc with a soft glow |
167
169
  | `ai-halo` | Conic-gradient halo that rotates around an orb |
168
170
  | `ai-ring` | Pulsing concentric ring |
@@ -236,12 +238,19 @@ import { faPaperPlaneOutline } from "@l3mpire/icons";
236
238
  <Button appearance="solid" intent="brand" size="md" badge={99}>
237
239
  Notifications
238
240
  </Button>
241
+
242
+ {/* AI intent — brand→violet gradient. Solid = vivid fill + white label;
243
+ outlined/ghost = gradient-clipped label & icons. No disabled-specific
244
+ art (falls back to the appearance's disabled style). */}
245
+ <Button appearance="solid" intent="ai" leftIcon={faStarsOutline}>Ask AI</Button>
246
+ <Button appearance="outlined" intent="ai" leftIcon={faStarsOutline}>Ask AI</Button>
247
+ <Button appearance="ghost" intent="ai" leftIcon={faStarsOutline}>Ask AI</Button>
239
248
  ```
240
249
 
241
250
  | Prop | Values |
242
251
  |---|---|
243
252
  | `appearance` | `"solid"`, `"outlined"`, `"ghost"` |
244
- | `intent` | `"brand"`, `"alert"` |
253
+ | `intent` | `"brand"`, `"alert"`, `"ai"` |
245
254
  | `size` | `"sm"` (24px), `"md"` (32px, radius 10px), `"lg"` (40px, radius 12px) |
246
255
  | `leftIcon` | `IconDefinition` |
247
256
  | `rightIcon` | `IconDefinition` |
package/dist/index.d.mts CHANGED
@@ -489,7 +489,7 @@ declare const BrowserTab: React.ForwardRefExoticComponent<BrowserTabProps & Reac
489
489
 
490
490
  declare const buttonVariants: (props?: ({
491
491
  appearance?: "solid" | "outlined" | "ghost" | null | undefined;
492
- intent?: "alert" | "brand" | null | undefined;
492
+ intent?: "alert" | "brand" | "ai" | null | undefined;
493
493
  size?: "sm" | "md" | "lg" | null | undefined;
494
494
  iconOnly?: boolean | null | undefined;
495
495
  fullWidth?: boolean | null | undefined;
@@ -652,7 +652,7 @@ interface InputLabelProps extends React.ComponentPropsWithoutRef<typeof LabelPri
652
652
  declare const InputLabel: React.ForwardRefExoticComponent<InputLabelProps & React.RefAttributes<HTMLLabelElement>>;
653
653
 
654
654
  declare const infoMessageVariants: (props?: ({
655
- type?: "alert" | "success" | "warning" | "info" | "ai" | "empty" | null | undefined;
655
+ type?: "alert" | "success" | "warning" | "ai" | "info" | "empty" | null | undefined;
656
656
  } & class_variance_authority_types.ClassProp) | undefined) => string;
657
657
  interface InfoMessageProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "title">, VariantProps<typeof infoMessageVariants> {
658
658
  title: React.ReactNode;
package/dist/index.d.ts CHANGED
@@ -489,7 +489,7 @@ declare const BrowserTab: React.ForwardRefExoticComponent<BrowserTabProps & Reac
489
489
 
490
490
  declare const buttonVariants: (props?: ({
491
491
  appearance?: "solid" | "outlined" | "ghost" | null | undefined;
492
- intent?: "alert" | "brand" | null | undefined;
492
+ intent?: "alert" | "brand" | "ai" | null | undefined;
493
493
  size?: "sm" | "md" | "lg" | null | undefined;
494
494
  iconOnly?: boolean | null | undefined;
495
495
  fullWidth?: boolean | null | undefined;
@@ -652,7 +652,7 @@ interface InputLabelProps extends React.ComponentPropsWithoutRef<typeof LabelPri
652
652
  declare const InputLabel: React.ForwardRefExoticComponent<InputLabelProps & React.RefAttributes<HTMLLabelElement>>;
653
653
 
654
654
  declare const infoMessageVariants: (props?: ({
655
- type?: "alert" | "success" | "warning" | "info" | "ai" | "empty" | null | undefined;
655
+ type?: "alert" | "success" | "warning" | "ai" | "info" | "empty" | null | undefined;
656
656
  } & class_variance_authority_types.ClassProp) | undefined) => string;
657
657
  interface InfoMessageProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "title">, VariantProps<typeof infoMessageVariants> {
658
658
  title: React.ReactNode;
package/dist/index.js CHANGED
@@ -700,7 +700,8 @@ var buttonVariants = (0, import_class_variance_authority3.cva)(
700
700
  },
701
701
  intent: {
702
702
  brand: [],
703
- alert: []
703
+ alert: [],
704
+ ai: []
704
705
  },
705
706
  size: {
706
707
  sm: [
@@ -822,6 +823,48 @@ var buttonVariants = (0, import_class_variance_authority3.cva)(
822
823
  "active:text-btn-ghost-alert-text-pressed"
823
824
  ]
824
825
  },
826
+ // ── Solid + AI ─────────────────────────────────────────────────────
827
+ // Vivid brand→violet gradient fill, white label/icons, violet outer
828
+ // border + a translucent-white inner border (via inset shadow).
829
+ {
830
+ appearance: "solid",
831
+ intent: "ai",
832
+ class: [
833
+ "bg-ai-strong",
834
+ "text-[var(--core-text-main-invert-primary)]",
835
+ "border-[var(--violet-500)]",
836
+ "shadow-[inset_0_0_0_1px_rgb(255_255_255/0.6),0_1px_3px_0_var(--shadow-4)]",
837
+ "hover:bg-ai-strong-hover",
838
+ "active:bg-ai-strong-pressed"
839
+ ]
840
+ },
841
+ // ── Outlined + AI ──────────────────────────────────────────────────
842
+ // Neutral surface (same as outlined brand), brand-blue border that
843
+ // deepens per state; label/icons are gradient-clipped (see component).
844
+ {
845
+ appearance: "outlined",
846
+ intent: "ai",
847
+ class: [
848
+ "bg-gradient-to-t from-btn-outlined-neutral-bg-default from-[10%] to-btn-outlined-neutral-bg-gradient-to-default",
849
+ "border-[var(--interactive-bg-primary-dark-default)]",
850
+ "shadow-[0_1px_3px_0_var(--shadow-4)]",
851
+ "hover:from-btn-outlined-neutral-bg-hover hover:from-[0%] hover:to-btn-outlined-neutral-bg-gradient-to-hover",
852
+ "hover:border-[var(--interactive-bg-primary-dark-hover)]",
853
+ "active:from-btn-outlined-neutral-bg-pressed active:to-btn-outlined-neutral-bg-gradient-to-pressed",
854
+ "active:border-[var(--interactive-bg-primary-dark-pressed)]"
855
+ ]
856
+ },
857
+ // ── Ghost + AI ─────────────────────────────────────────────────────
858
+ // Subtle translucent gradient wash, no border; gradient-clipped text.
859
+ {
860
+ appearance: "ghost",
861
+ intent: "ai",
862
+ class: [
863
+ "bg-ai-subtle border-transparent",
864
+ "hover:bg-ai-subtle-hover",
865
+ "active:bg-ai-subtle-pressed"
866
+ ]
867
+ },
825
868
  // ── Icon-only size overrides ───────────────────────────────────────
826
869
  { size: "sm", iconOnly: true, class: "w-6 min-w-0 px-0" },
827
870
  { size: "md", iconOnly: true, class: "w-8 min-w-0 px-0" },
@@ -884,6 +927,8 @@ var Button = React6.forwardRef(
884
927
  const isDisabled = disabled || loading;
885
928
  const isIconOnly = iconOnlyProp ?? !children;
886
929
  const iconSize = iconSizeMap[resolvedSize];
930
+ const aiGradientText = intent === "ai" && appearance !== "solid" && !isDisabled;
931
+ const aiTextClass = aiGradientText ? "ai-grad-text" : void 0;
887
932
  const variantClasses = buttonVariants({
888
933
  appearance,
889
934
  intent,
@@ -893,7 +938,7 @@ var Button = React6.forwardRef(
893
938
  className
894
939
  });
895
940
  const labelNode = !isIconOnly ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: textWrapperClass(resolvedSize), children: [
896
- children,
941
+ aiGradientText ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: aiTextClass, children }) : children,
897
942
  badge != null && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ButtonBadge, { size: resolvedSize, intent, children: badge })
898
943
  ] }) : null;
899
944
  if (asChild) {
@@ -906,11 +951,12 @@ var Button = React6.forwardRef(
906
951
  className: cn(variantClasses),
907
952
  disabled: isDisabled,
908
953
  "aria-busy": loading || void 0,
954
+ "data-ai-text": aiGradientText || void 0,
909
955
  ...props,
910
956
  children: [
911
- loading ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_icons5.Icon, { icon: import_icons5.faSpinnerSolid, size: iconSize, className: "animate-spin" }) : leftIcon ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_icons5.Icon, { icon: leftIcon, size: iconSize }) : null,
957
+ loading ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_icons5.Icon, { icon: import_icons5.faSpinnerSolid, size: iconSize, className: "animate-spin" }) : leftIcon ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_icons5.Icon, { icon: leftIcon, size: iconSize, className: aiTextClass }) : null,
912
958
  labelNode,
913
- rightIcon && !loading && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_icons5.Icon, { icon: rightIcon, size: iconSize })
959
+ rightIcon && !loading && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_icons5.Icon, { icon: rightIcon, size: iconSize, className: aiTextClass })
914
960
  ]
915
961
  }
916
962
  );
@@ -7268,6 +7314,18 @@ function ColumnFilterPopover({
7268
7314
  ) })
7269
7315
  ] });
7270
7316
  }
7317
+ function pinnedStickyStyle(pinned, offset, zIndex) {
7318
+ if (pinned === "left") return { position: "sticky", left: offset, zIndex };
7319
+ if (pinned === "right") return { position: "sticky", right: offset, zIndex };
7320
+ return {};
7321
+ }
7322
+ function pinnedDividerClass(pinned) {
7323
+ if (pinned === "left")
7324
+ return "shadow-[1px_0_0_0_var(--core-border-main-primary)]";
7325
+ if (pinned === "right")
7326
+ return "shadow-[-1px_0_0_0_var(--core-border-main-primary)]";
7327
+ return "";
7328
+ }
7271
7329
  function DraggableHeaderCell({
7272
7330
  header,
7273
7331
  enableSorting,
@@ -7295,6 +7353,7 @@ function DraggableHeaderCell({
7295
7353
  disabled: !canDrag
7296
7354
  });
7297
7355
  const pinned = header.column.getIsPinned();
7356
+ const pinOffset = pinned === "left" ? header.column.getStart("left") : pinned === "right" ? header.column.getAfter("right") : void 0;
7298
7357
  const style = {
7299
7358
  width: enableColumnResizing ? header.getSize() : void 0,
7300
7359
  minWidth: enableColumnResizing ? header.column.columnDef.minSize : void 0,
@@ -7305,18 +7364,7 @@ function DraggableHeaderCell({
7305
7364
  // Pinning sticky positioning — must override the default `relative` we
7306
7365
  // also use for the resize handle. The resize handle is still absolutely
7307
7366
  // positioned because it references `right: 0` relative to the cell.
7308
- ...pinned === "left" ? {
7309
- position: "sticky",
7310
- left: header.column.getStart("left"),
7311
- zIndex: isDragging ? 5 : 3
7312
- } : pinned === "right" ? {
7313
- position: "sticky",
7314
- right: header.column.getAfter("right"),
7315
- zIndex: isDragging ? 5 : 3
7316
- } : {
7317
- position: "relative",
7318
- zIndex: isDragging ? 1 : void 0
7319
- }
7367
+ ...pinned ? pinnedStickyStyle(pinned, pinOffset, isDragging ? 5 : 3) : { position: "relative", zIndex: isDragging ? 1 : void 0 }
7320
7368
  };
7321
7369
  return /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)(
7322
7370
  TableHead,
@@ -7330,8 +7378,7 @@ function DraggableHeaderCell({
7330
7378
  // Pinned columns keep their bg so the scrolling content can pass
7331
7379
  // under them. The default `bg-table-head-bg-default` is already on
7332
7380
  // <th> so we only need to add a divider on the boundary.
7333
- pinned === "left" && "shadow-[1px_0_0_0_var(--core-border-main-primary)]",
7334
- pinned === "right" && "shadow-[-1px_0_0_0_var(--core-border-main-primary)]"
7381
+ pinnedDividerClass(pinned)
7335
7382
  ),
7336
7383
  onClick: canSort ? header.column.getToggleSortingHandler() : void 0,
7337
7384
  onMouseEnter: () => setIsHovered(true),
@@ -7560,15 +7607,22 @@ function DataTableInner({
7560
7607
  }).filter(Boolean),
7561
7608
  [columns]
7562
7609
  );
7610
+ const lastSyncedColumnIdsRef = React54.useRef(null);
7563
7611
  React54.useEffect(() => {
7564
7612
  if (!enableColumnDrag) return;
7613
+ const signature = allColumnIds.join(" ");
7614
+ if (signature === lastSyncedColumnIdsRef.current) return;
7615
+ lastSyncedColumnIdsRef.current = signature;
7565
7616
  if (columnOrder.length === 0) {
7566
7617
  setColumnOrder(allColumnIds);
7567
7618
  return;
7568
7619
  }
7569
- const missing = allColumnIds.filter((id) => !columnOrder.includes(id));
7570
- if (missing.length > 0) {
7571
- setColumnOrder([...columnOrder, ...missing]);
7620
+ const pruned = columnOrder.filter((id) => allColumnIds.includes(id));
7621
+ const missing = allColumnIds.filter((id) => !pruned.includes(id));
7622
+ const next = [...pruned, ...missing];
7623
+ const changed = next.length !== columnOrder.length || next.some((id, i) => id !== columnOrder[i]);
7624
+ if (changed) {
7625
+ setColumnOrder(next);
7572
7626
  }
7573
7627
  }, [enableColumnDrag, columnOrder, allColumnIds, setColumnOrder]);
7574
7628
  const table = (0, import_react_table.useReactTable)({
@@ -7685,6 +7739,16 @@ function DataTableInner({
7685
7739
  return vars;
7686
7740
  }, [enableColumnResizing, table.getState().columnSizing]);
7687
7741
  const totalSize = enableColumnResizing ? table.getTotalSize() : void 0;
7742
+ const pinnedOffsets = React54.useMemo(() => {
7743
+ const map = /* @__PURE__ */ new Map();
7744
+ for (const col of table.getVisibleLeafColumns()) {
7745
+ const p = col.getIsPinned();
7746
+ if (p === "left") map.set(col.id, { side: "left", offset: col.getStart("left") });
7747
+ else if (p === "right")
7748
+ map.set(col.id, { side: "right", offset: col.getAfter("right") });
7749
+ }
7750
+ return map;
7751
+ }, [table, table.getState().columnPinning, table.getState().columnSizing]);
7688
7752
  const tableContent = /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: cn("w-full", className), style: columnSizeVars, children: [
7689
7753
  /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)(
7690
7754
  Table,
@@ -7760,17 +7824,10 @@ function DataTableInner({
7760
7824
  "data-state": row.getIsSelected() ? "selected" : void 0,
7761
7825
  children: row.getVisibleCells().map((cell) => {
7762
7826
  const cellPinned = cell.column.getIsPinned();
7827
+ const pin = cellPinned ? pinnedOffsets.get(cell.column.id) : void 0;
7763
7828
  const cellStyle = {
7764
7829
  ...enableColumnResizing ? { width: `var(--col-${cell.column.id}-size)` } : {},
7765
- ...cellPinned === "left" ? {
7766
- position: "sticky",
7767
- left: cell.column.getStart("left"),
7768
- zIndex: 2
7769
- } : cellPinned === "right" ? {
7770
- position: "sticky",
7771
- right: cell.column.getAfter("right"),
7772
- zIndex: 2
7773
- } : {}
7830
+ ...pinnedStickyStyle(cellPinned, pin?.offset, 2)
7774
7831
  };
7775
7832
  return /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
7776
7833
  TableCell,
@@ -7781,8 +7838,8 @@ function DataTableInner({
7781
7838
  enableColumnResizing && "truncate",
7782
7839
  // Pinned cells need an opaque background so scrolled
7783
7840
  // content does not show through.
7784
- cellPinned === "left" && "bg-table-row-bg-default group-hover/row:bg-table-row-bg-hover data-[state=selected]:bg-table-row-bg-selected shadow-[1px_0_0_0_var(--core-border-main-primary)]",
7785
- cellPinned === "right" && "bg-table-row-bg-default group-hover/row:bg-table-row-bg-hover data-[state=selected]:bg-table-row-bg-selected shadow-[-1px_0_0_0_var(--core-border-main-primary)]"
7841
+ cellPinned && "bg-table-row-bg-default group-hover/row:bg-table-row-bg-hover data-[state=selected]:bg-table-row-bg-selected",
7842
+ pinnedDividerClass(cellPinned)
7786
7843
  ),
7787
7844
  children: (0, import_react_table.flexRender)(
7788
7845
  cell.column.columnDef.cell,