@pattern-stack/frontend-patterns 0.2.0-alpha.7 → 0.2.0-alpha.9

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.js CHANGED
@@ -179,6 +179,141 @@ const RESPONSIVE_CHART_HEIGHTS = {
179
179
  secondary: DASHBOARD_CHART_HEIGHTS.medium
180
180
  }
181
181
  };
182
+ const COLUMN_WIDTHS = {
183
+ // Text
184
+ text: 180,
185
+ password: 120,
186
+ // Numbers
187
+ number: 80,
188
+ money: 100,
189
+ percent: 70,
190
+ // Dates
191
+ date: 100,
192
+ datetime: 140,
193
+ // Contact/Links
194
+ email: 180,
195
+ phone: 120,
196
+ url: 160,
197
+ // Visual indicators
198
+ status: 90,
199
+ badge: 90,
200
+ boolean: 60,
201
+ rating: 80,
202
+ color: 60,
203
+ // Entity references
204
+ entity: 140,
205
+ user: 140,
206
+ // Special
207
+ image: 60,
208
+ file: 100,
209
+ json: 200
210
+ };
211
+ const COMPACT_COLUMN_WIDTHS = {
212
+ text: 120,
213
+ // With truncation
214
+ money: 60,
215
+ // $2.5k format
216
+ date: 55,
217
+ // "Dec 5" or "Tu"
218
+ datetime: 65,
219
+ // "5m ago"
220
+ email: 100,
221
+ // Truncated
222
+ phone: 80,
223
+ url: 80,
224
+ entity: 100,
225
+ user: 80,
226
+ json: 80
227
+ };
228
+ const COMPACT_FORMATTERS = {
229
+ money: "compactCurrency",
230
+ // $2,456.99 → $2.5k
231
+ date: "shortDate",
232
+ // 2024-01-15 → Dec 5
233
+ datetime: "relative",
234
+ // 2024-01-15T10:30 → 5m ago
235
+ text: "truncate",
236
+ // Long customer name → Long cust...
237
+ email: "truncate",
238
+ url: "domain",
239
+ // https://example.com/path → example.com
240
+ number: "compact"
241
+ // 1234567 → 1.2M
242
+ };
243
+ const COMPACT_LABELS = {
244
+ // Common field names → short versions
245
+ customer: "Cust",
246
+ description: "Desc",
247
+ quantity: "Qty",
248
+ amount: "Amt",
249
+ total: "Tot",
250
+ priority: "Pri",
251
+ status: "Stat",
252
+ created_at: "Created",
253
+ updated_at: "Updated",
254
+ email: "Email",
255
+ phone: "Ph",
256
+ category: "Cat",
257
+ // Time fields
258
+ time: "Time",
259
+ timeAgo: "Time",
260
+ timestamp: "Time",
261
+ // ID fields
262
+ order_id: "#",
263
+ id: "#",
264
+ transaction_id: "Txn"
265
+ };
266
+ const TABLE_BREAKPOINTS = {
267
+ /** Below this width (px), always use cards */
268
+ minForTable: 400,
269
+ /** Buffer (px) to add when calculating if columns fit */
270
+ widthBuffer: 50,
271
+ /** Minimum columns to show in compact mode */
272
+ minCompactColumns: 3,
273
+ /** Maximum columns in compact mode */
274
+ maxCompactColumns: 6
275
+ };
276
+ const IMPORTANCE_VISIBILITY = {
277
+ full: ["primary", "secondary", "tertiary", "critical", "high", "medium", "low", "minimal"],
278
+ compact: ["primary", "secondary", "critical", "high", "medium"],
279
+ cards: ["primary", "critical", "high"]
280
+ };
281
+ function getColumnWidth(type, mode = "full") {
282
+ if (mode === "compact" && type in COMPACT_COLUMN_WIDTHS) {
283
+ return COMPACT_COLUMN_WIDTHS[type];
284
+ }
285
+ return COLUMN_WIDTHS[type] ?? COLUMN_WIDTHS.text;
286
+ }
287
+ function calculateTableWidth(columns, mode = "full") {
288
+ return columns.reduce((sum, col) => sum + getColumnWidth(col.type, mode), 0);
289
+ }
290
+ function getCompactLabel(fieldName, originalLabel) {
291
+ const normalized = fieldName.toLowerCase();
292
+ return COMPACT_LABELS[normalized] ?? originalLabel;
293
+ }
294
+ function getCompactFormatter(type) {
295
+ return COMPACT_FORMATTERS[type];
296
+ }
297
+ function determineTableMode(containerWidth, columns) {
298
+ const { minForTable, widthBuffer } = TABLE_BREAKPOINTS;
299
+ if (containerWidth < minForTable) {
300
+ return "cards";
301
+ }
302
+ const fullWidth = calculateTableWidth(columns, "full");
303
+ const compactColumns = columns.filter(
304
+ (col) => IMPORTANCE_VISIBILITY.compact.includes(
305
+ col.importance ?? "medium"
306
+ )
307
+ );
308
+ const compactWidth = calculateTableWidth(compactColumns, "compact");
309
+ if (fullWidth + widthBuffer <= containerWidth) {
310
+ return "full";
311
+ }
312
+ if (compactWidth + widthBuffer <= containerWidth) {
313
+ return "compact";
314
+ }
315
+ return "cards";
316
+ }
182
317
  const breakpoints = {
183
318
  "2xs": 400,
184
319
  xs: 500,
@@ -2052,6 +2187,143 @@ function renderField(value, fieldName, fieldType, breakpoint, item, customRender
2052
2187
  function cn(...inputs) {
2053
2188
  return tailwindMerge.twMerge(clsx.clsx(inputs));
2054
2189
  }
2190
+ const IconBadge = ({
2191
+ children,
2192
+ icon,
2193
+ variant = "category",
2194
+ category = 1,
2195
+ status = "neutral",
2196
+ size = "md",
2197
+ interactive = false,
2198
+ onClick,
2199
+ className,
2200
+ tooltip
2201
+ }) => {
2202
+ const currentSize = useResponsiveValue(size);
2203
+ const sizeConfig = responsiveScales.components.iconBadge[currentSize];
2204
+ const sizeClasses2 = {
2205
+ xs: `${(sizeConfig == null ? void 0 : sizeConfig.size) || "w-6 h-6"} ${(sizeConfig == null ? void 0 : sizeConfig.text) || "text-[10px]"} ${(sizeConfig == null ? void 0 : sizeConfig.rounded) || "rounded-md"}`,
2206
+ sm: "w-8 h-8 text-xs rounded-lg",
2207
+ md: "w-10 h-10 text-sm rounded-xl",
2208
+ lg: "w-12 h-12 text-base rounded-xl"
2209
+ };
2210
+ const baseClasses = cn(
2211
+ "inline-flex items-center justify-center font-bold shadow-md",
2212
+ sizeClasses2[currentSize],
2213
+ interactive && [
2214
+ "cursor-pointer",
2215
+ getAnimationClasses({
2216
+ ...animationPresets.dataBadge,
2217
+ size: currentSize === "lg" ? "lg" : currentSize === "sm" || currentSize === "xs" ? "sm" : "md"
2218
+ })
2219
+ ],
2220
+ className
2221
+ );
2222
+ const gradientClasses = variant === "category" ? `bg-gradient-to-br from-category-${category} to-category-${Math.min(category + 1, 8)}` : variant === "status" ? `bg-gradient-to-br from-status-${status} to-status-${status}` : "bg-gradient-to-br from-category-1 to-category-2";
2223
+ const badge = /* @__PURE__ */ jsxRuntime.jsxs(
2224
+ "div",
2225
+ {
2226
+ className: cn(baseClasses, gradientClasses, "animate-fade-in"),
2227
+ onClick,
2228
+ role: interactive ? "button" : void 0,
2229
+ tabIndex: interactive ? 0 : void 0,
2230
+ onKeyDown: interactive ? (e) => {
2231
+ if (e.key === "Enter" || e.key === " ") {
2232
+ e.preventDefault();
2233
+ onClick == null ? void 0 : onClick();
2234
+ }
2235
+ } : void 0,
2236
+ "data-component-name": "IconBadge",
2237
+ children: [
2238
+ icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-primary-foreground drop-shadow", children: icon }),
2239
+ children && !icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-primary-foreground font-bold", children })
2240
+ ]
2241
+ }
2242
+ );
2243
+ if (tooltip) {
2244
+ return /* @__PURE__ */ jsxRuntime.jsx(Tooltip, { content: tooltip, position: "top", size: "sm", children: badge });
2245
+ }
2246
+ return badge;
2247
+ };
2248
+ const valueColorMap = {
2249
+ success: "text-green-600",
2250
+ error: "text-red-600",
2251
+ warning: "text-yellow-600",
2252
+ info: "text-blue-600",
2253
+ neutral: "text-muted-foreground",
2254
+ default: "text-foreground"
2255
+ };
2256
+ function ListCard({
2257
+ icon,
2258
+ title,
2259
+ subtitle,
2260
+ metadata,
2261
+ value,
2262
+ badge,
2263
+ onClick,
2264
+ className,
2265
+ children
2266
+ }) {
2267
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2268
+ "div",
2269
+ {
2270
+ className: cn(
2271
+ "flex items-start gap-3 p-3 rounded-lg border border-border transition-colors",
2272
+ onClick && "hover:bg-muted/50 cursor-pointer",
2273
+ className
2274
+ ),
2275
+ onClick,
2276
+ children: [
2277
+ icon && /* @__PURE__ */ jsxRuntime.jsx(
2278
+ IconBadge,
2279
+ {
2280
+ variant: icon.variant || "category",
2281
+ category: icon.variant === "category" ? icon.category || 1 : void 0,
2282
+ status: icon.variant === "status" ? icon.status || "neutral" : void 0,
2283
+ size: icon.size || "sm",
2284
+ icon: icon.icon,
2285
+ tooltip: icon.tooltip
2286
+ }
2287
+ ),
2288
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [
2289
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-2", children: [
2290
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1", children: [
2291
+ /* @__PURE__ */ jsxRuntime.jsx("h4", { className: "text-sm font-medium text-foreground truncate", children: title }),
2292
+ subtitle && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground mt-0.5", children: subtitle }),
2293
+ metadata && metadata.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-3 mt-1", children: metadata.map((item, index) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: item }, index)) })
2294
+ ] }),
2295
+ (value || badge) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-right", children: [
2296
+ value && /* @__PURE__ */ jsxRuntime.jsx(
2297
+ "p",
2298
+ {
2299
+ className: cn(
2300
+ "text-sm font-semibold",
2301
+ value.variant && valueColorMap[value.variant],
2302
+ value.className
2303
+ ),
2304
+ children: value.text
2305
+ }
2306
+ ),
2307
+ badge && /* @__PURE__ */ jsxRuntime.jsx(
2308
+ DataBadge,
2309
+ {
2310
+ variant: badge.variant,
2311
+ status: badge.variant === "status" ? badge.status : void 0,
2312
+ category: badge.variant === "category" ? badge.category : void 0,
2313
+ size: badge.size || "sm",
2314
+ icon: badge.icon,
2315
+ className: "mt-1",
2316
+ children: badge.text
2317
+ }
2318
+ )
2319
+ ] })
2320
+ ] }),
2321
+ children
2322
+ ] })
2323
+ ]
2324
+ }
2325
+ );
2326
+ }
2055
2327
  const STATUS_COLOR_MAP = {
2056
2328
  // Success states
2057
2329
  active: "success",
@@ -2390,7 +2662,6 @@ function getCompactWidth(type) {
2390
2662
  }
2391
2663
  }
2392
2664
  function createMobileCardRenderer(columns, mapping, entityType) {
2393
- const { ListCard: ListCard2 } = require("../components/data/ListCard");
2394
2665
  return ({ data, onItemClick }) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-2", children: data.map((item, index) => {
2395
2666
  const cardProps = entityToListCardProps(
2396
2667
  item,
@@ -2399,7 +2670,7 @@ function createMobileCardRenderer(columns, mapping, entityType) {
2399
2670
  entityType
2400
2671
  );
2401
2672
  return /* @__PURE__ */ jsxRuntime.jsx(
2402
- ListCard2,
2673
+ ListCard,
2403
2674
  {
2404
2675
  ...cardProps,
2405
2676
  onClick: onItemClick ? () => onItemClick(item) : void 0
@@ -2573,10 +2844,10 @@ function useApiMutation(mutationFn, options) {
2573
2844
  const queryClient = reactQuery.useQueryClient();
2574
2845
  return reactQuery.useMutation({
2575
2846
  mutationFn,
2576
- onSuccess: (data, variables, context) => {
2847
+ onSuccess: (...args) => {
2577
2848
  var _a;
2578
- queryClient.invalidateQueries();
2579
- (_a = options == null ? void 0 : options.onSuccess) == null ? void 0 : _a.call(options, data, variables, context);
2849
+ void queryClient.invalidateQueries({});
2850
+ (_a = options == null ? void 0 : options.onSuccess) == null ? void 0 : _a.call(options, ...args);
2580
2851
  },
2581
2852
  ...options
2582
2853
  });
@@ -2651,43 +2922,87 @@ function useFieldMetadata({
2651
2922
  }
2652
2923
  function useResponsiveTable(options = {}) {
2653
2924
  const {
2925
+ columns,
2654
2926
  initialView = "full",
2655
2927
  autoSwitch = true,
2656
- minWidthForCompact = 400,
2928
+ minWidthForCompact = TABLE_BREAKPOINTS.minForTable,
2657
2929
  debounceMs = 100,
2658
2930
  onViewChange
2659
2931
  } = options;
2660
2932
  const [viewMode, setViewModeState] = React.useState(initialView);
2661
2933
  const [isAutoSwitching, setIsAutoSwitching] = React.useState(autoSwitch);
2934
+ const [containerWidth, setContainerWidth] = React.useState(0);
2662
2935
  const [overflowState, setOverflowState] = React.useState({
2663
2936
  fullOverflows: false,
2664
2937
  compactOverflows: false
2665
2938
  });
2939
+ const containerRef = React.useRef(null);
2666
2940
  const fullTableRef = React.useRef(null);
2667
2941
  const compactTableRef = React.useRef(null);
2668
2942
  const debounceTimerRef = React.useRef(null);
2669
2943
  const isTransitioningRef = React.useRef(false);
2944
+ const compactColumns = React.useCallback(() => {
2945
+ if (!columns) return null;
2946
+ const validImportance = IMPORTANCE_VISIBILITY.compact;
2947
+ const filtered = columns.filter(
2948
+ (col) => validImportance.includes(col.importance ?? "medium")
2949
+ );
2950
+ const limited = filtered.slice(0, TABLE_BREAKPOINTS.maxCompactColumns);
2951
+ return limited.map((col) => ({
2952
+ ...col,
2953
+ label: getCompactLabel(col.field, col.label)
2954
+ }));
2955
+ }, [columns])();
2956
+ const calculatedWidths = React.useCallback(() => {
2957
+ if (!columns) {
2958
+ return { full: 0, compact: 0, container: containerWidth };
2959
+ }
2960
+ const fullWidth = calculateTableWidth(
2961
+ columns.map((c) => ({ type: c.type })),
2962
+ "full"
2963
+ );
2964
+ const compactCols = compactColumns ?? [];
2965
+ const compactWidth = calculateTableWidth(
2966
+ compactCols.map((c) => ({ type: c.type })),
2967
+ "compact"
2968
+ );
2969
+ return {
2970
+ full: fullWidth,
2971
+ compact: compactWidth,
2972
+ container: containerWidth
2973
+ };
2974
+ }, [columns, compactColumns, containerWidth])();
2670
2975
  const hasOverflow = React.useCallback((element) => {
2671
2976
  if (!element) return false;
2672
2977
  return element.scrollWidth > element.clientWidth + 2;
2673
2978
  }, []);
2674
2979
  const determineView = React.useCallback(() => {
2675
- var _a, _b, _c, _d;
2676
- const containerWidth = ((_b = (_a = fullTableRef.current) == null ? void 0 : _a.parentElement) == null ? void 0 : _b.clientWidth) || ((_d = (_c = compactTableRef.current) == null ? void 0 : _c.parentElement) == null ? void 0 : _d.clientWidth) || window.innerWidth;
2677
- if (containerWidth < minWidthForCompact) {
2980
+ var _a, _b, _c, _d, _e;
2981
+ if (columns && containerWidth > 0) {
2982
+ const { full, compact } = calculatedWidths;
2983
+ const buffer = TABLE_BREAKPOINTS.widthBuffer;
2984
+ if (containerWidth < minWidthForCompact) {
2985
+ return "cards";
2986
+ }
2987
+ if (full + buffer <= containerWidth) {
2988
+ return "full";
2989
+ }
2990
+ if (compact + buffer <= containerWidth) {
2991
+ return "compact";
2992
+ }
2993
+ return "cards";
2994
+ }
2995
+ const width = ((_a = containerRef.current) == null ? void 0 : _a.clientWidth) || ((_c = (_b = fullTableRef.current) == null ? void 0 : _b.parentElement) == null ? void 0 : _c.clientWidth) || ((_e = (_d = compactTableRef.current) == null ? void 0 : _d.parentElement) == null ? void 0 : _e.clientWidth) || window.innerWidth;
2996
+ if (width < minWidthForCompact) {
2678
2997
  return "cards";
2679
2998
  }
2680
2999
  const fullOverflows = hasOverflow(fullTableRef.current);
2681
3000
  const compactOverflows = hasOverflow(compactTableRef.current);
2682
3001
  setOverflowState({ fullOverflows, compactOverflows });
2683
- if (!fullOverflows) {
2684
- return "full";
2685
- }
2686
- if (!compactOverflows) {
2687
- return "compact";
2688
- }
3002
+ if (!fullOverflows) return "full";
3003
+ if (!compactOverflows) return "compact";
2689
3004
  return "cards";
2690
- }, [hasOverflow, minWidthForCompact]);
3005
+ }, [columns, containerWidth, calculatedWidths, minWidthForCompact, hasOverflow]);
2691
3006
  const checkAndUpdate = React.useCallback(() => {
2692
3007
  if (!isAutoSwitching || isTransitioningRef.current) return;
2693
3008
  if (debounceTimerRef.current) {
@@ -2719,42 +3034,49 @@ function useResponsiveTable(options = {}) {
2719
3034
  React.useEffect(() => {
2720
3035
  var _a, _b;
2721
3036
  if (!isAutoSwitching) return;
2722
- const initialCheckTimer = setTimeout(checkAndUpdate, 50);
3037
+ const updateContainerWidth = () => {
3038
+ var _a2;
3039
+ const ref = containerRef.current || ((_a2 = fullTableRef.current) == null ? void 0 : _a2.parentElement);
3040
+ if (ref) {
3041
+ setContainerWidth(ref.clientWidth);
3042
+ }
3043
+ };
3044
+ const initialTimer = setTimeout(updateContainerWidth, 50);
2723
3045
  const resizeObserver = new ResizeObserver(() => {
3046
+ updateContainerWidth();
2724
3047
  checkAndUpdate();
2725
3048
  });
2726
- if (fullTableRef.current) {
2727
- resizeObserver.observe(fullTableRef.current);
2728
- }
2729
- if (compactTableRef.current) {
2730
- resizeObserver.observe(compactTableRef.current);
3049
+ const observeTarget = containerRef.current || ((_a = fullTableRef.current) == null ? void 0 : _a.parentElement) || ((_b = compactTableRef.current) == null ? void 0 : _b.parentElement);
3050
+ if (observeTarget) {
3051
+ resizeObserver.observe(observeTarget);
2731
3052
  }
2732
- const parent = ((_a = fullTableRef.current) == null ? void 0 : _a.parentElement) || ((_b = compactTableRef.current) == null ? void 0 : _b.parentElement);
2733
- if (parent) {
2734
- resizeObserver.observe(parent);
3053
+ if (!columns) {
3054
+ if (fullTableRef.current) resizeObserver.observe(fullTableRef.current);
3055
+ if (compactTableRef.current) resizeObserver.observe(compactTableRef.current);
2735
3056
  }
2736
3057
  window.addEventListener("resize", checkAndUpdate);
2737
3058
  return () => {
2738
- clearTimeout(initialCheckTimer);
2739
- if (debounceTimerRef.current) {
2740
- clearTimeout(debounceTimerRef.current);
2741
- }
3059
+ clearTimeout(initialTimer);
3060
+ if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current);
2742
3061
  resizeObserver.disconnect();
2743
3062
  window.removeEventListener("resize", checkAndUpdate);
2744
3063
  };
2745
- }, [isAutoSwitching, checkAndUpdate]);
3064
+ }, [isAutoSwitching, columns, checkAndUpdate]);
2746
3065
  React.useEffect(() => {
2747
3066
  if (!isAutoSwitching) return;
2748
3067
  const timer = setTimeout(checkAndUpdate, 100);
2749
3068
  return () => clearTimeout(timer);
2750
- }, [viewMode, isAutoSwitching, checkAndUpdate]);
3069
+ }, [viewMode, containerWidth, isAutoSwitching, checkAndUpdate]);
2751
3070
  return {
2752
3071
  viewMode,
2753
3072
  setViewMode,
2754
3073
  enableAutoSwitch,
2755
3074
  isAutoSwitching,
3075
+ containerRef,
2756
3076
  fullTableRef,
2757
3077
  compactTableRef,
3078
+ compactColumns,
3079
+ calculatedWidths,
2758
3080
  overflowState
2759
3081
  };
2760
3082
  }
@@ -2773,6 +3095,154 @@ function useOverflowDetection(ref) {
2773
3095
  }, [ref]);
2774
3096
  return hasOverflow;
2775
3097
  }
3098
+ const IMPORTANCE_PRIORITY = {
3099
+ minimal: 0,
3100
+ low: 1,
3101
+ tertiary: 2,
3102
+ medium: 3,
3103
+ secondary: 4,
3104
+ high: 5,
3105
+ critical: 6,
3106
+ primary: 7
3107
+ };
3108
+ function getColumnModeWidth(col, mode) {
3109
+ if (mode === "hidden") return 0;
3110
+ return getColumnWidth(col.type, mode === "compact" ? "compact" : "full");
3111
+ }
3112
+ function calculateAdaptiveWidth(columns, modes) {
3113
+ return columns.reduce((sum, col) => sum + getColumnModeWidth(col, modes[col.field] ?? "full"), 0);
3114
+ }
3115
+ function calculateColumnModes(columns, containerWidth, protectedColumns, minColumnsVisible) {
3116
+ const buffer = TABLE_BREAKPOINTS.widthBuffer;
3117
+ const sortedByImportance = [...columns].sort((a, b) => {
3118
+ const priorityA = IMPORTANCE_PRIORITY[a.importance ?? "tertiary"];
3119
+ const priorityB = IMPORTANCE_PRIORITY[b.importance ?? "tertiary"];
3120
+ return priorityA - priorityB;
3121
+ });
3122
+ const modes = {};
3123
+ columns.forEach((col) => modes[col.field] = "full");
3124
+ let currentWidth = calculateAdaptiveWidth(columns, modes);
3125
+ if (currentWidth + buffer <= containerWidth) {
3126
+ return { modes, degradationLevel: 0 };
3127
+ }
3128
+ let degradationLevel = 1;
3129
+ for (const col of sortedByImportance) {
3130
+ if (protectedColumns.has(col.field)) continue;
3131
+ const priority = IMPORTANCE_PRIORITY[col.importance ?? "tertiary"];
3132
+ if (priority > 2) break;
3133
+ modes[col.field] = "compact";
3134
+ currentWidth = calculateAdaptiveWidth(columns, modes);
3135
+ if (currentWidth + buffer <= containerWidth) return { modes, degradationLevel };
3136
+ }
3137
+ degradationLevel = 2;
3138
+ for (const col of sortedByImportance) {
3139
+ if (protectedColumns.has(col.field)) continue;
3140
+ const priority = IMPORTANCE_PRIORITY[col.importance ?? "tertiary"];
3141
+ if (priority <= 2) continue;
3142
+ if (priority > 4) break;
3143
+ modes[col.field] = "compact";
3144
+ currentWidth = calculateAdaptiveWidth(columns, modes);
3145
+ if (currentWidth + buffer <= containerWidth) return { modes, degradationLevel };
3146
+ }
3147
+ degradationLevel = 3;
3148
+ let visibleCount = columns.length;
3149
+ for (const col of sortedByImportance) {
3150
+ const priority = IMPORTANCE_PRIORITY[col.importance ?? "tertiary"];
3151
+ if (priority > 2) break;
3152
+ if (visibleCount <= minColumnsVisible) break;
3153
+ modes[col.field] = "hidden";
3154
+ visibleCount--;
3155
+ currentWidth = calculateAdaptiveWidth(columns, modes);
3156
+ if (currentWidth + buffer <= containerWidth) return { modes, degradationLevel };
3157
+ }
3158
+ degradationLevel = 4;
3159
+ for (const col of sortedByImportance) {
3160
+ const priority = IMPORTANCE_PRIORITY[col.importance ?? "tertiary"];
3161
+ if (priority <= 2) continue;
3162
+ if (priority > 4) break;
3163
+ if (visibleCount <= minColumnsVisible) break;
3164
+ modes[col.field] = "hidden";
3165
+ visibleCount--;
3166
+ currentWidth = calculateAdaptiveWidth(columns, modes);
3167
+ if (currentWidth + buffer <= containerWidth) return { modes, degradationLevel };
3168
+ }
3169
+ degradationLevel = 5;
3170
+ for (const col of sortedByImportance) {
3171
+ if (modes[col.field] === "hidden") continue;
3172
+ modes[col.field] = "compact";
3173
+ currentWidth = calculateAdaptiveWidth(columns, modes);
3174
+ if (currentWidth + buffer <= containerWidth) return { modes, degradationLevel };
3175
+ }
3176
+ return { modes, degradationLevel: 6 };
3177
+ }
3178
+ function useAdaptiveTable(options) {
3179
+ const {
3180
+ columns,
3181
+ protectedColumns = [],
3182
+ minColumnsVisible = 2,
3183
+ minWidthForTable = TABLE_BREAKPOINTS.minForTable,
3184
+ debounceMs = 100,
3185
+ onModeChange
3186
+ } = options;
3187
+ const containerRef = React.useRef(null);
3188
+ const [containerWidth, setContainerWidth] = React.useState(0);
3189
+ const debounceTimerRef = React.useRef(null);
3190
+ const protectedSet = React.useMemo(() => new Set(protectedColumns), [protectedColumns]);
3191
+ const { modes: columnModes, degradationLevel } = React.useMemo(() => {
3192
+ if (containerWidth === 0) {
3193
+ const modes = {};
3194
+ columns.forEach((col) => modes[col.field] = "full");
3195
+ return { modes, degradationLevel: 0 };
3196
+ }
3197
+ return calculateColumnModes(columns, containerWidth, protectedSet, minColumnsVisible);
3198
+ }, [columns, containerWidth, protectedSet, minColumnsVisible]);
3199
+ const visibleColumns = React.useMemo(() => {
3200
+ return columns.filter((col) => columnModes[col.field] !== "hidden").map(
3201
+ (col) => columnModes[col.field] === "compact" ? { ...col, label: getCompactLabel(col.field, col.label) } : col
3202
+ );
3203
+ }, [columns, columnModes]);
3204
+ const calculatedWidth = React.useMemo(
3205
+ () => calculateAdaptiveWidth(columns, columnModes),
3206
+ [columns, columnModes]
3207
+ );
3208
+ const viewMode = React.useMemo(() => {
3209
+ if (containerWidth > 0 && containerWidth < minWidthForTable) return "cards";
3210
+ if (visibleColumns.length < minColumnsVisible) return "cards";
3211
+ return "table";
3212
+ }, [containerWidth, minWidthForTable, visibleColumns.length, minColumnsVisible]);
3213
+ React.useEffect(() => {
3214
+ const updateWidth = () => {
3215
+ if (containerRef.current) setContainerWidth(containerRef.current.clientWidth);
3216
+ };
3217
+ const initialTimer = setTimeout(updateWidth, 50);
3218
+ const resizeObserver = new ResizeObserver(() => {
3219
+ if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current);
3220
+ debounceTimerRef.current = setTimeout(updateWidth, debounceMs);
3221
+ });
3222
+ if (containerRef.current) resizeObserver.observe(containerRef.current);
3223
+ return () => {
3224
+ clearTimeout(initialTimer);
3225
+ if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current);
3226
+ resizeObserver.disconnect();
3227
+ };
3228
+ }, [debounceMs]);
3229
+ const prevModesRef = React.useRef(columnModes);
3230
+ React.useEffect(() => {
3231
+ if (onModeChange && columnModes !== prevModesRef.current) {
3232
+ onModeChange(columnModes);
3233
+ prevModesRef.current = columnModes;
3234
+ }
3235
+ }, [columnModes, onModeChange]);
3236
+ return {
3237
+ containerRef,
3238
+ columnModes,
3239
+ visibleColumns,
3240
+ viewMode,
3241
+ calculatedWidth,
3242
+ containerWidth,
3243
+ degradationLevel
3244
+ };
3245
+ }
2776
3246
  function useEntityData(entity, options = {}) {
2777
3247
  var _a, _b, _c;
2778
3248
  const {
@@ -4655,8 +5125,9 @@ function normalizeColumn(col) {
4655
5125
  return col;
4656
5126
  }
4657
5127
  function DataTable({
4658
- data,
4659
- columns,
5128
+ data: dataProp,
5129
+ columns: columnsProp,
5130
+ query,
4660
5131
  searchPlaceholder = "Search...",
4661
5132
  pageSize = 10,
4662
5133
  showPagination = true,
@@ -4665,12 +5136,12 @@ function DataTable({
4665
5136
  emptyMessage = "No data available",
4666
5137
  className = "",
4667
5138
  hover = false,
4668
- isLoading = false,
5139
+ isLoading: isLoadingProp = false,
4669
5140
  loadingItemCount = 5,
4670
5141
  responsive = true,
4671
5142
  renderMobileCard,
4672
5143
  ui,
4673
- error = null,
5144
+ error: errorProp = null,
4674
5145
  errorTitle,
4675
5146
  errorRetry,
4676
5147
  selectable = false,
@@ -4685,6 +5156,10 @@ function DataTable({
4685
5156
  const [currentPage, setCurrentPage] = React.useState(1);
4686
5157
  const currentBreakpoint = useBreakpoint();
4687
5158
  const isMobile = currentBreakpoint === "xs" || currentBreakpoint === "2xs" || currentBreakpoint === "sm";
5159
+ const data = dataProp ?? (query == null ? void 0 : query.data) ?? [];
5160
+ const columns = columnsProp ?? (query == null ? void 0 : query.columns) ?? [];
5161
+ const isLoading = isLoadingProp || (query == null ? void 0 : query.isLoading) || (query == null ? void 0 : query.isLoadingMetadata) || false;
5162
+ const error = errorProp ?? (query == null ? void 0 : query.error) ?? (query == null ? void 0 : query.metadataError) ?? null;
4688
5163
  const normalizedColumns = React.useMemo(
4689
5164
  () => columns.map((col) => normalizeColumn(col)),
4690
5165
  [columns]
@@ -5071,64 +5546,6 @@ const TableHead = TableHead$1;
5071
5546
  const TableHeader = TableHeader$1;
5072
5547
  const TableRow = TableRow$1;
5073
5548
  const TableFooter = TableFooter$1;
5074
- const IconBadge = ({
5075
- children,
5076
- icon,
5077
- variant = "category",
5078
- category = 1,
5079
- status = "neutral",
5080
- size = "md",
5081
- interactive = false,
5082
- onClick,
5083
- className,
5084
- tooltip
5085
- }) => {
5086
- const currentSize = useResponsiveValue(size);
5087
- const sizeConfig = responsiveScales.components.iconBadge[currentSize];
5088
- const sizeClasses2 = {
5089
- xs: `${(sizeConfig == null ? void 0 : sizeConfig.size) || "w-6 h-6"} ${(sizeConfig == null ? void 0 : sizeConfig.text) || "text-[10px]"} ${(sizeConfig == null ? void 0 : sizeConfig.rounded) || "rounded-md"}`,
5090
- sm: "w-8 h-8 text-xs rounded-lg",
5091
- md: "w-10 h-10 text-sm rounded-xl",
5092
- lg: "w-12 h-12 text-base rounded-xl"
5093
- };
5094
- const baseClasses = cn(
5095
- "inline-flex items-center justify-center font-bold shadow-md",
5096
- sizeClasses2[currentSize],
5097
- interactive && [
5098
- "cursor-pointer",
5099
- getAnimationClasses({
5100
- ...animationPresets.dataBadge,
5101
- size: currentSize === "lg" ? "lg" : currentSize === "sm" || currentSize === "xs" ? "sm" : "md"
5102
- })
5103
- ],
5104
- className
5105
- );
5106
- const gradientClasses = variant === "category" ? `bg-gradient-to-br from-category-${category} to-category-${Math.min(category + 1, 8)}` : variant === "status" ? `bg-gradient-to-br from-status-${status} to-status-${status}` : "bg-gradient-to-br from-category-1 to-category-2";
5107
- const badge = /* @__PURE__ */ jsxRuntime.jsxs(
5108
- "div",
5109
- {
5110
- className: cn(baseClasses, gradientClasses, "animate-fade-in"),
5111
- onClick,
5112
- role: interactive ? "button" : void 0,
5113
- tabIndex: interactive ? 0 : void 0,
5114
- onKeyDown: interactive ? (e) => {
5115
- if (e.key === "Enter" || e.key === " ") {
5116
- e.preventDefault();
5117
- onClick == null ? void 0 : onClick();
5118
- }
5119
- } : void 0,
5120
- "data-component-name": "IconBadge",
5121
- children: [
5122
- icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-primary-foreground drop-shadow", children: icon }),
5123
- children && !icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-primary-foreground font-bold", children })
5124
- ]
5125
- }
5126
- );
5127
- if (tooltip) {
5128
- return /* @__PURE__ */ jsxRuntime.jsx(Tooltip, { content: tooltip, position: "top", size: "sm", children: badge });
5129
- }
5130
- return badge;
5131
- };
5132
5549
  const Chart = ({
5133
5550
  title,
5134
5551
  subtitle,
@@ -5891,85 +6308,6 @@ const ProgressBar = ({
5891
6308
  )
5892
6309
  ] });
5893
6310
  };
5894
- const valueColorMap = {
5895
- success: "text-green-600",
5896
- error: "text-red-600",
5897
- warning: "text-yellow-600",
5898
- info: "text-blue-600",
5899
- neutral: "text-muted-foreground",
5900
- default: "text-foreground"
5901
- };
5902
- function ListCard({
5903
- icon,
5904
- title,
5905
- subtitle,
5906
- metadata,
5907
- value,
5908
- badge,
5909
- onClick,
5910
- className,
5911
- children
5912
- }) {
5913
- return /* @__PURE__ */ jsxRuntime.jsxs(
5914
- "div",
5915
- {
5916
- className: cn(
5917
- "flex items-start gap-3 p-3 rounded-lg border border-border transition-colors",
5918
- onClick && "hover:bg-muted/50 cursor-pointer",
5919
- className
5920
- ),
5921
- onClick,
5922
- children: [
5923
- icon && /* @__PURE__ */ jsxRuntime.jsx(
5924
- IconBadge,
5925
- {
5926
- variant: icon.variant || "category",
5927
- category: icon.variant === "category" ? icon.category || 1 : void 0,
5928
- status: icon.variant === "status" ? icon.status || "neutral" : void 0,
5929
- size: icon.size || "sm",
5930
- icon: icon.icon,
5931
- tooltip: icon.tooltip
5932
- }
5933
- ),
5934
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [
5935
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-2", children: [
5936
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1", children: [
5937
- /* @__PURE__ */ jsxRuntime.jsx("h4", { className: "text-sm font-medium text-foreground truncate", children: title }),
5938
- subtitle && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground mt-0.5", children: subtitle }),
5939
- metadata && metadata.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-3 mt-1", children: metadata.map((item, index) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: item }, index)) })
5940
- ] }),
5941
- (value || badge) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-right", children: [
5942
- value && /* @__PURE__ */ jsxRuntime.jsx(
5943
- "p",
5944
- {
5945
- className: cn(
5946
- "text-sm font-semibold",
5947
- value.variant && valueColorMap[value.variant],
5948
- value.className
5949
- ),
5950
- children: value.text
5951
- }
5952
- ),
5953
- badge && /* @__PURE__ */ jsxRuntime.jsx(
5954
- DataBadge,
5955
- {
5956
- variant: badge.variant,
5957
- status: badge.variant === "status" ? badge.status : void 0,
5958
- category: badge.variant === "category" ? badge.category : void 0,
5959
- size: badge.size || "sm",
5960
- icon: badge.icon,
5961
- className: "mt-1",
5962
- children: badge.text
5963
- }
5964
- )
5965
- ] })
5966
- ] }),
5967
- children
5968
- ] })
5969
- ]
5970
- }
5971
- );
5972
- }
5973
6311
  const TruncatedText = ({
5974
6312
  text,
5975
6313
  className,
@@ -9290,6 +9628,13 @@ const defaultShowcaseNavigation = [
9290
9628
  path: "/admin/dashboard",
9291
9629
  category: 2
9292
9630
  },
9631
+ {
9632
+ value: "admin-dashboard-v2",
9633
+ label: "Admin Dashboard V2",
9634
+ icon: "Shield",
9635
+ path: "/admin/dashboard-v2",
9636
+ category: 2
9637
+ },
9293
9638
  {
9294
9639
  value: "admin-users",
9295
9640
  label: "User Management",
@@ -9560,7 +9905,6 @@ const Sidebar = ({ className }) => {
9560
9905
  );
9561
9906
  };
9562
9907
  const AppHeader = ({ className }) => {
9563
- const isTrialMode = false;
9564
9908
  return /* @__PURE__ */ jsxRuntime.jsx(
9565
9909
  "header",
9566
9910
  {
@@ -9573,7 +9917,7 @@ const AppHeader = ({ className }) => {
9573
9917
  children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-auto max-w-7xl px-4 sm:px-6 lg:px-8", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-16 items-center justify-between", children: [
9574
9918
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
9575
9919
  /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-xl font-bold text-foreground", children: "Frontend Template" }),
9576
- isTrialMode
9920
+ /* @__PURE__ */ jsxRuntime.jsx(DataBadge, { variant: "status", status: "info", display: "icon-text", children: "Trial Mode" })
9577
9921
  ] }),
9578
9922
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 max-w-2xl mx-8", children: /* @__PURE__ */ jsxRuntime.jsx(
9579
9923
  GlobalSearch,
@@ -14357,18 +14701,41 @@ function createReactApp(config) {
14357
14701
  enableRouting = true,
14358
14702
  auth,
14359
14703
  navigation,
14360
- customProviders = []
14704
+ customProviders = [],
14705
+ persistQueryCache = false,
14706
+ queryCacheKey = "PATTERN_STACK_QUERY_CACHE"
14361
14707
  } = appConfig;
14362
14708
  const queryClient = new reactQuery.QueryClient({
14363
14709
  defaultOptions: {
14364
14710
  queries: {
14365
14711
  retry: 1,
14366
14712
  refetchOnWindowFocus: false,
14367
- staleTime: 5 * 60 * 1e3
14713
+ staleTime: 5 * 60 * 1e3,
14368
14714
  // 5 minutes
14715
+ // Keep data longer when persistence is enabled
14716
+ gcTime: persistQueryCache ? 1e3 * 60 * 60 * 24 : 1e3 * 60 * 5
14369
14717
  }
14370
14718
  }
14371
14719
  });
14720
+ if (persistQueryCache && typeof window !== "undefined") {
14721
+ import("@tanstack/react-query-persist-client").then(({ persistQueryClient }) => {
14722
+ import("@tanstack/query-sync-storage-persister").then(({ createSyncStoragePersister }) => {
14723
+ const persister = createSyncStoragePersister({
14724
+ storage: window.localStorage,
14725
+ key: queryCacheKey
14726
+ });
14727
+ persistQueryClient({
14728
+ queryClient,
14729
+ persister,
14730
+ maxAge: 1e3 * 60 * 60 * 24
14731
+ // 24 hours
14732
+ });
14733
+ console.info("[app] Query cache persistence enabled");
14734
+ });
14735
+ }).catch((err) => {
14736
+ console.warn("[app] Query persistence not available:", err.message);
14737
+ });
14738
+ }
14372
14739
  const routes = [];
14373
14740
  const DefaultHome = () => /* @__PURE__ */ jsxRuntime.jsx(DashboardTemplate, { title, description, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center justify-center min-h-[400px] text-center", children: [
14374
14741
  /* @__PURE__ */ jsxRuntime.jsxs("h1", { className: "text-4xl font-bold text-foreground mb-4", children: [
@@ -14387,7 +14754,7 @@ function createReactApp(config) {
14387
14754
  ] }) });
14388
14755
  const createProviderTree = (children) => {
14389
14756
  let tree = children;
14390
- customProviders.reverse().forEach((Provider) => {
14757
+ [...customProviders].reverse().forEach((Provider) => {
14391
14758
  tree = /* @__PURE__ */ jsxRuntime.jsx(Provider, { children: tree }, Provider.name);
14392
14759
  });
14393
14760
  if (enableRouting) {
@@ -14430,10 +14797,10 @@ function createReactApp(config) {
14430
14797
  }
14431
14798
  return component;
14432
14799
  };
14433
- const AppContent = () => /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Routes, { children: /* @__PURE__ */ jsxRuntime.jsxs(reactRouterDom.Route, { path: "/", element: /* @__PURE__ */ jsxRuntime.jsx(AppLayout, {}), children: [
14800
+ const AppContent = () => /* @__PURE__ */ jsxRuntime.jsx(React.Suspense, { fallback: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center min-h-screen", children: "Loading..." }), children: /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Routes, { children: /* @__PURE__ */ jsxRuntime.jsxs(reactRouterDom.Route, { path: "/", element: /* @__PURE__ */ jsxRuntime.jsx(AppLayout, {}), children: [
14434
14801
  /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Route, { index: true, element: wrapWithProtection("/", /* @__PURE__ */ jsxRuntime.jsx(DefaultHome, {})) }),
14435
14802
  routes.map(({ path, component }, index) => /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Route, { path, element: wrapWithProtection(path, component) }, index))
14436
- ] }) });
14803
+ ] }) }) });
14437
14804
  return createProviderTree(/* @__PURE__ */ jsxRuntime.jsx(AppContent, {}));
14438
14805
  },
14439
14806
  mount(elementId = "root") {
@@ -17628,6 +17995,10 @@ exports.Badge = Badge;
17628
17995
  exports.Breadcrumb = Breadcrumb;
17629
17996
  exports.BulkSelectionBar = BulkSelectionBar;
17630
17997
  exports.Button = Button;
17998
+ exports.COLUMN_WIDTHS = COLUMN_WIDTHS;
17999
+ exports.COMPACT_COLUMN_WIDTHS = COMPACT_COLUMN_WIDTHS;
18000
+ exports.COMPACT_FORMATTERS = COMPACT_FORMATTERS;
18001
+ exports.COMPACT_LABELS = COMPACT_LABELS;
17631
18002
  exports.Card = Card;
17632
18003
  exports.CardContent = CardContent;
17633
18004
  exports.CardDescription = CardDescription;
@@ -17686,6 +18057,7 @@ exports.FileUpload = FileUpload;
17686
18057
  exports.FormField = FormField;
17687
18058
  exports.FormGroup = FormGroup;
17688
18059
  exports.GlobalSearch = GlobalSearch;
18060
+ exports.IMPORTANCE_VISIBILITY = IMPORTANCE_VISIBILITY;
17689
18061
  exports.Icon = Icon;
17690
18062
  exports.IconBadge = IconBadge;
17691
18063
  exports.Input = Input;
@@ -17741,6 +18113,7 @@ exports.Spinner = Spinner;
17741
18113
  exports.StatCard = StatCard;
17742
18114
  exports.StyleGuide = StyleGuide;
17743
18115
  exports.Switch = Switch;
18116
+ exports.TABLE_BREAKPOINTS = TABLE_BREAKPOINTS;
17744
18117
  exports.Table = Table;
17745
18118
  exports.TableBody = TableBody;
17746
18119
  exports.TableCaption = TableCaption;
@@ -17763,6 +18136,7 @@ exports.UserMenu = UserMenu;
17763
18136
  exports.animationPresets = animationPresets;
17764
18137
  exports.apiClient = apiClient;
17765
18138
  exports.breakpoints = breakpoints;
18139
+ exports.calculateTableWidth = calculateTableWidth;
17766
18140
  exports.cn = cn;
17767
18141
  exports.createAPIDataTemplate = createAPIDataTemplate;
17768
18142
  exports.createApiClient = createApiClient;
@@ -17772,6 +18146,7 @@ exports.createSimpleApp = createSimpleApp;
17772
18146
  exports.defaultFieldRenderers = defaultFieldRenderers;
17773
18147
  exports.detectBulkOperationType = detectBulkOperationType;
17774
18148
  exports.detectUIConfig = detectUIConfig;
18149
+ exports.determineTableMode = determineTableMode;
17775
18150
  exports.entityToListCardProps = entityToListCardProps;
17776
18151
  exports.env = env;
17777
18152
  exports.formatNumberWithTooltip = formatNumberWithTooltip;
@@ -17779,6 +18154,9 @@ exports.generateBulkOperationName = generateBulkOperationName;
17779
18154
  exports.generateCompactColumns = generateCompactColumns;
17780
18155
  exports.getAnimationClasses = getAnimationClasses;
17781
18156
  exports.getChartHeight = getChartHeight;
18157
+ exports.getColumnWidth = getColumnWidth;
18158
+ exports.getCompactFormatter = getCompactFormatter;
18159
+ exports.getCompactLabel = getCompactLabel;
17782
18160
  exports.getContainerHeightClass = getContainerHeightClass;
17783
18161
  exports.getCurrentBreakpoint = getCurrentBreakpoint;
17784
18162
  exports.getFieldType = getFieldType;
@@ -17797,6 +18175,7 @@ exports.renderField = renderField;
17797
18175
  exports.responsiveScales = responsiveScales;
17798
18176
  exports.setGlobalAuthService = setGlobalAuthService;
17799
18177
  exports.tooltipContent = tooltipContent;
18178
+ exports.useAdaptiveTable = useAdaptiveTable;
17800
18179
  exports.useApiMutation = useApiMutation;
17801
18180
  exports.useApiQuery = useApiQuery;
17802
18181
  exports.useAuth = useAuth;