aplance-ui 1.0.1 → 1.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aplance-ui",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "The aplance-ui CLI - scaffold the design system and copy components into your project as source.",
5
5
  "keywords": [
6
6
  "react",
@@ -88,7 +88,7 @@ const releaseScrollLock = () => {
88
88
 
89
89
  // ─── Shared button passthrough ────────────────────────────────────────────────
90
90
 
91
- type ButtonPassthroughProps = {
91
+ export type ButtonPassthroughProps = {
92
92
  variant?: ButtonVariant;
93
93
  size?: ButtonSize;
94
94
  layout?: ButtonLayout;
@@ -101,7 +101,7 @@ type ButtonPassthroughProps = {
101
101
 
102
102
  // ─── Modal ────────────────────────────────────────────────────────────────────
103
103
 
104
- type ModalProps = {
104
+ export type ModalProps = {
105
105
  /**
106
106
  * Dialog content plus an optional <ModalTrigger>. ModalTrigger is hoisted
107
107
  * out of the dialog panel so it stays visible while the modal is closed.
@@ -378,7 +378,7 @@ export const Modal: FC<ModalProps> = ({
378
378
  // the trigger stays visible while the modal is closed. For fully controlled
379
379
  // modals the trigger can live elsewhere; call open() from useModal() instead.
380
380
 
381
- type ModalTriggerProps = ButtonPassthroughProps & {
381
+ export type ModalTriggerProps = ButtonPassthroughProps & {
382
382
  asChild?: boolean;
383
383
  };
384
384
 
@@ -438,7 +438,7 @@ export const ModalTrigger: FC<ModalTriggerProps> = ({
438
438
  // Explicit close button inside footer/body (e.g. "Cancel").
439
439
  // The automatic X button is controlled by showCloseButton prop on Modal.
440
440
 
441
- type ModalCloseProps = ButtonPassthroughProps & {
441
+ export type ModalCloseProps = ButtonPassthroughProps & {
442
442
  asChild?: boolean;
443
443
  };
444
444
 
@@ -26,7 +26,7 @@ export type Coords = {
26
26
  };
27
27
 
28
28
  // Popover Component
29
- type PopoverProps = {
29
+ export type PopoverProps = {
30
30
  children?: ReactNode;
31
31
  className?: string;
32
32
  style?: CSSProperties;
@@ -279,7 +279,7 @@ export const Popover: FC<PopoverProps> = ({
279
279
  };
280
280
 
281
281
  // Trigger
282
- type PopoverTriggerProps = ComponentProps<"button"> & {
282
+ export type PopoverTriggerProps = ComponentProps<"button"> & {
283
283
  children?: ReactNode;
284
284
  className?: string;
285
285
  style?: CSSProperties;
@@ -355,7 +355,7 @@ export const PopoverTrigger: FC<PopoverTriggerProps> = ({
355
355
  };
356
356
 
357
357
  // Menu
358
- type PopoverMenuProps = {
358
+ export type PopoverMenuProps = {
359
359
  children?: ReactNode;
360
360
  className?: string;
361
361
  style?: CSSProperties;
@@ -2,9 +2,9 @@ import clsx from "clsx";
2
2
  import s from "./avatar.module.css";
3
3
  import { getInitials, hashName } from "../../../lib/string";
4
4
 
5
- type AvatarSize = "sm" | "md" | "lg";
5
+ export type AvatarSize = "sm" | "md" | "lg";
6
6
 
7
- type AvatarProps = {
7
+ export type AvatarProps = {
8
8
  name: string;
9
9
  size?: AvatarSize;
10
10
  className?: string;
@@ -19,8 +19,6 @@ const COLORS = [
19
19
  { bg: "oklch(0.92 0.05 330)", fg: "oklch(0.4 0.15 330)" },
20
20
  ];
21
21
 
22
-
23
-
24
22
  export function Avatar({ name, size = "md", className }: AvatarProps) {
25
23
  const color = COLORS[hashName(name) % COLORS.length];
26
24
  const initials = getInitials(name) || "?";
@@ -1,16 +1,27 @@
1
1
  import clsx from "clsx";
2
2
  import s from "./badge.module.css";
3
3
 
4
- type BadgeVariant = "default" | "primary" | "success" | "warning" | "error" | "info";
5
- type BadgeSize = "default" | "sm";
4
+ export type BadgeVariant =
5
+ | "default"
6
+ | "primary"
7
+ | "success"
8
+ | "warning"
9
+ | "error"
10
+ | "info";
11
+ export type BadgeSize = "default" | "sm";
6
12
 
7
- type BadgeProps = {
13
+ export type BadgeProps = {
8
14
  variant?: BadgeVariant;
9
15
  size?: BadgeSize;
10
16
  className?: string;
11
17
  children: React.ReactNode;
12
18
  };
13
19
 
20
+ export type CountBadgeProps = {
21
+ count: number;
22
+ className?: string;
23
+ };
24
+
14
25
  export function Badge({ variant = "default", size = "default", className, children }: BadgeProps) {
15
26
  return (
16
27
  <span className={clsx(s.base, s[variant], size === "sm" && s.sm, className)}>
@@ -19,7 +30,7 @@ export function Badge({ variant = "default", size = "default", className, childr
19
30
  );
20
31
  }
21
32
 
22
- export function CountBadge({ count, className }: { count: number; className?: string }) {
33
+ export function CountBadge({ count, className }: CountBadgeProps) {
23
34
  if (count <= 0) return null;
24
35
  return (
25
36
  <span className={clsx(s.count, className)} aria-label={`${count} notifications`}>
@@ -45,7 +45,7 @@ type ElementModeProps<T extends "button" | "div" | "span"> = BaseProps & {
45
45
  href?: never;
46
46
  } & ComponentProps<T>;
47
47
 
48
- type ButtonProps =
48
+ export type ButtonProps =
49
49
  | LinkModeProps
50
50
  | ElementModeProps<"button">
51
51
  | ElementModeProps<"div">
@@ -114,12 +114,10 @@
114
114
  overflow-x: auto;
115
115
  overflow-y: auto;
116
116
  border: var(--border-1) solid var(--color-border);
117
- border-radius: var(--radius-md);
118
- background: var(--color-background);
119
- position: relative;
120
- /* Add this smooth transition */
121
- transition: opacity 0.2s ease;
122
- }
117
+ border-radius: var(--radius-md);
118
+ background: var(--color-background);
119
+ position: relative;
120
+ }
123
121
 
124
122
  /* ─────────────────────────────────────────────
125
123
  Shared <table> - header wrap + body wrap.
@@ -12,11 +12,12 @@ import {
12
12
  type Updater,
13
13
  } from "@tanstack/react-table";
14
14
  import {
15
- useCallback,
16
- useEffect,
17
- useMemo,
18
- useRef,
19
- useState,
15
+ useCallback,
16
+ useEffect,
17
+ useLayoutEffect,
18
+ useMemo,
19
+ useRef,
20
+ useState,
20
21
  type CSSProperties,
21
22
  type DragEvent as ReactDragEvent,
22
23
  type ReactNode,
@@ -73,11 +74,13 @@ const EMPTY_FILTER_VALUES: Record<string, string> = {};
73
74
  const ICON_SIZE = 15;
74
75
  const SORT_ICON_SIZE = 13;
75
76
  const ICON_STROKE = 1.6;
76
- const EMPTY_COLUMN_EXTRA_WIDTHS: Record<string, number> = {};
77
- // Grace period after `isLoading` flips false before committing to the empty
78
- // state. Eliminates the flash when consumers set isLoading=false a render
79
- // before populating rows, without making genuinely-empty results feel sluggish.
80
- const EMPTY_STATE_GRACE_MS = 150;
77
+ const EMPTY_COLUMN_EXTRA_WIDTHS: Record<string, number> = {};
78
+ // Grace period after `isLoading` flips false before committing to the empty
79
+ // state. Eliminates the flash when consumers set isLoading=false a render
80
+ // before populating rows, without making genuinely-empty results feel sluggish.
81
+ const EMPTY_STATE_GRACE_MS = 150;
82
+ const useIsomorphicLayoutEffect =
83
+ typeof window === "undefined" ? useEffect : useLayoutEffect;
81
84
 
82
85
  export const DataTable = <TData extends { id: string }>({
83
86
  data,
@@ -349,12 +352,19 @@ export const DataTable = <TData extends { id: string }>({
349
352
  const tableColumnPinning = tableState.columnPinning;
350
353
  const tableColumnSizing = tableState.columnSizing;
351
354
  const tableColumnOrder = tableState.columnOrder;
352
- const tableColumnVisibility = tableState.columnVisibility;
353
- const tableGrouping = tableState.grouping;
354
- const [bodyViewportWidth, setBodyViewportWidth] = useState(0);
355
-
356
- const syncHeaderMetrics = useCallback(() => {
357
- const body = bodyRef.current;
355
+ const tableColumnVisibility = tableState.columnVisibility;
356
+ const tableGrouping = tableState.grouping;
357
+ const [bodyViewportWidth, setBodyViewportWidth] = useState(0);
358
+ const bodyViewportWidthRef = useRef(0);
359
+
360
+ const commitBodyViewportWidth = useCallback((nextWidth: number) => {
361
+ if (bodyViewportWidthRef.current === nextWidth) return;
362
+ bodyViewportWidthRef.current = nextWidth;
363
+ setBodyViewportWidth(nextWidth);
364
+ }, []);
365
+
366
+ const syncHeaderMetrics = useCallback(() => {
367
+ const body = bodyRef.current;
358
368
  const header = headerRef.current;
359
369
  if (!body || !header) return;
360
370
 
@@ -369,32 +379,33 @@ export const DataTable = <TData extends { id: string }>({
369
379
  );
370
380
  header.parentElement?.setAttribute("data-has-scroll", hasScroll.toString());
371
381
 
372
- // Re-sync horizontal scroll position after width changes so pinned
373
- // columns stay perfectly aligned with their body counterparts.
374
- header.scrollLeft = body.scrollLeft;
375
- setBodyViewportWidth(body.clientWidth);
376
- }, []);
377
-
378
- useEffect(() => {
379
- syncHeaderMetrics();
380
- }, [
381
- rows.length,
382
+ // Re-sync horizontal scroll position after width changes so pinned
383
+ // columns stay perfectly aligned with their body counterparts.
384
+ header.scrollLeft = body.scrollLeft;
385
+ commitBodyViewportWidth(body.clientWidth);
386
+ }, [commitBodyViewportWidth]);
387
+
388
+ useIsomorphicLayoutEffect(() => {
389
+ syncHeaderMetrics();
390
+ }, [
391
+ rows.length,
382
392
  headerCount,
383
393
  totalWidth,
384
394
  tableColumnPinning,
385
395
  tableColumnSizing,
386
396
  tableColumnOrder,
387
397
  tableColumnVisibility,
388
- tableGrouping,
389
- syncHeaderMetrics,
390
- ]);
391
-
392
- useEffect(() => {
393
- const body = bodyRef.current;
394
- if (!body) return;
395
-
396
- const observer = new ResizeObserver(syncHeaderMetrics);
397
- observer.observe(body);
398
+ tableGrouping,
399
+ syncHeaderMetrics,
400
+ ]);
401
+
402
+ useIsomorphicLayoutEffect(() => {
403
+ const body = bodyRef.current;
404
+ if (!body) return;
405
+ if (typeof ResizeObserver === "undefined") return;
406
+
407
+ const observer = new ResizeObserver(syncHeaderMetrics);
408
+ observer.observe(body);
398
409
 
399
410
  return () => {
400
411
  observer.disconnect();
@@ -599,13 +610,16 @@ export const DataTable = <TData extends { id: string }>({
599
610
  isPinned === "right" && s.col_pinned_right,
600
611
  isLastPinnedCol && s.col_pinned_last,
601
612
  )}
602
- style={baseStyle}
603
- >
604
- <div className={s.td_inner}>
605
- <Loader variant="skeleton" style={{height: "var(--spacing-9)"}}/>
606
- </div>
607
- </td>
608
- );
613
+ style={baseStyle}
614
+ >
615
+ <div className={s.td_inner}>
616
+ <Loader
617
+ variant="skeleton"
618
+ style={{ height: "var(--spacing-9)" }}
619
+ />
620
+ </div>
621
+ </td>
622
+ );
609
623
  })}
610
624
  </tr>
611
625
  ),
@@ -1,33 +1,33 @@
1
- import { Fragment } from 'react'
2
- import type { FilterBarProps } from './types'
3
- import s from './data-table.module.css'
1
+ import { Fragment } from "react";
2
+ import type { FilterBarProps } from "./types";
3
+ import s from "./data-table.module.css";
4
4
 
5
5
  /**
6
6
  * Headless single-row filter bar.
7
7
  * Each field owns its rendering via a render-prop and sets its own grid
8
8
  * column width. The bar never wraps - consumers pick widths that fit.
9
9
  *
10
- * Width defaults: input fields → `1fr`, actions → `auto`.
10
+ * Width defaults: input fields -> `1fr`, actions -> `auto`.
11
11
  */
12
12
  export const FilterBar = ({ fields, values, onChange }: FilterBarProps) => {
13
13
  const gridCols = fields
14
- .map((f) => f.width ?? (f.type === 'action' ? 'auto' : '1fr'))
15
- .join(' ')
14
+ .map((f) => f.width ?? (f.type === "action" ? "auto" : "1fr"))
15
+ .join(" ");
16
16
 
17
17
  return (
18
18
  <div className={s.filter_bar} style={{ gridTemplateColumns: gridCols }}>
19
19
  {fields.map((field, i) => (
20
20
  <Fragment
21
- key={field.type === 'action' ? `act:${i}` : `k:${field.key}`}
21
+ key={field.type === "action" ? `act:${i}` : `k:${field.key}`}
22
22
  >
23
- {field.type === 'action'
23
+ {field.type === "action"
24
24
  ? field.render()
25
25
  : field.render({
26
- value: values[field.key] ?? '',
26
+ value: values[field.key] ?? "",
27
27
  onChange: (v) => onChange(field.key, v),
28
28
  })}
29
29
  </Fragment>
30
30
  ))}
31
31
  </div>
32
- )
33
- }
32
+ );
33
+ };
@@ -1,13 +1,13 @@
1
- import { Button } from '../button/button'
2
- import { CustomSelect } from '../input-fields/custom-select/custom-select'
3
- import type { PaginationBarProps } from './types'
4
- import s from './data-table.module.css'
5
1
  import {
6
2
  IconChevronLeft,
7
3
  IconChevronRight,
8
4
  IconChevronsLeft,
9
5
  IconChevronsRight,
10
- } from '@tabler/icons-react'
6
+ } from "@tabler/icons-react";
7
+ import { Button } from "../button/button";
8
+ import { CustomSelect } from "../input-fields/custom-select/custom-select";
9
+ import type { PaginationBarProps } from "./types";
10
+ import s from "./data-table.module.css";
11
11
 
12
12
  export const PaginationBar = <TData,>({
13
13
  table,
@@ -21,22 +21,22 @@ export const PaginationBar = <TData,>({
21
21
  selectedCount,
22
22
  extra,
23
23
  }: PaginationBarProps<TData>) => {
24
- const pageCount = table.getPageCount()
25
- const total = totalCount ?? table.getRowCount()
26
- const canPrev = table.getCanPreviousPage()
27
- const canNext = table.getCanNextPage()
24
+ const pageCount = table.getPageCount();
25
+ const total = totalCount ?? table.getRowCount();
26
+ const canPrev = table.getCanPreviousPage();
27
+ const canNext = table.getCanNextPage();
28
28
 
29
29
  const goTo = (i: number) =>
30
- onPaginationChange({ ...pagination, pageIndex: i })
30
+ onPaginationChange({ ...pagination, pageIndex: i });
31
31
  const setSize = (size: number) =>
32
- onPaginationChange({ pageIndex: 0, pageSize: size })
32
+ onPaginationChange({ pageIndex: 0, pageSize: size });
33
33
 
34
34
  // Always show all page size options - current pageSize is guaranteed in the list.
35
35
  const sizeOptions = Array.from(
36
36
  new Set([...pageSizeOptions, pagination.pageSize]),
37
37
  )
38
38
  .sort((a, b) => a - b)
39
- .map((n) => ({ value: String(n), label: String(n) }))
39
+ .map((n) => ({ value: String(n), label: String(n) }));
40
40
 
41
41
  return (
42
42
  <div className={s.pagination_bar}>
@@ -66,7 +66,7 @@ export const PaginationBar = <TData,>({
66
66
  <CustomSelect
67
67
  options={sizeOptions}
68
68
  value={String(pagination.pageSize)}
69
- popover={{position: "top"}}
69
+ popover={{ position: "top" }}
70
70
  onChange={(val) =>
71
71
  setSize(Number(Array.isArray(val) ? val[0] : val))
72
72
  }
@@ -76,7 +76,7 @@ export const PaginationBar = <TData,>({
76
76
  )}
77
77
 
78
78
  <p className={s.page_indicator}>
79
- Page <span>{pagination.pageIndex + 1}</span> of{' '}
79
+ Page <span>{pagination.pageIndex + 1}</span> of{" "}
80
80
  <span>{pageCount}</span>
81
81
  </p>
82
82
 
@@ -121,5 +121,5 @@ export const PaginationBar = <TData,>({
121
121
  </div>
122
122
  )}
123
123
  </div>
124
- )
125
- }
124
+ );
125
+ };
@@ -2,7 +2,7 @@ import type { ElementType } from "react";
2
2
  import { Icon } from "../../primitives/icon/icon";
3
3
  import s from "./empty-state.module.css";
4
4
 
5
- type EmptyStateProps = {
5
+ export type EmptyStateProps = {
6
6
  icon?: ElementType;
7
7
  title: string;
8
8
  description?: string;
@@ -22,7 +22,7 @@ export type TabItem = {
22
22
  disabled?: boolean;
23
23
  };
24
24
 
25
- type TabsProps = {
25
+ export type TabsProps = {
26
26
  tabs: TabItem[];
27
27
  defaultTab?: string;
28
28
  activeTab?: string;
@@ -8,7 +8,7 @@ import type { ComponentProps } from "react";
8
8
  import s from "./tooltip.module.css";
9
9
  import clsx from "clsx";
10
10
 
11
- type TooltipProps = {
11
+ export type TooltipProps = {
12
12
  content: ReactNode;
13
13
  children: ReactNode;
14
14
  showArrow?: boolean;