@jinaan/uikit 0.1.42 → 0.1.44

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.
@@ -0,0 +1,24 @@
1
+ import { type InputProps } from "./input";
2
+ export interface InputSelectOption {
3
+ value: string;
4
+ label: string;
5
+ }
6
+ export interface InputSelectProps extends Omit<InputProps, "className" | "containerClassName" | "onClear"> {
7
+ /** Selected value for the right-hand inline dropdown. */
8
+ selectValue: string;
9
+ onSelectChange: (value: string) => void;
10
+ selectOptions: InputSelectOption[];
11
+ selectAriaLabel: string;
12
+ selectPlaceholder?: string;
13
+ /** Outer wrapper class. */
14
+ className?: string;
15
+ inputClassName?: string;
16
+ selectClassName?: string;
17
+ }
18
+ /**
19
+ * Joined text/number input with an inline select on the right. The input keeps
20
+ * its right corners square and the dropdown keeps its left corners square so
21
+ * they read as one control.
22
+ */
23
+ export declare const InputSelect: import("react").ForwardRefExoticComponent<InputSelectProps & import("react").RefAttributes<HTMLInputElement>>;
24
+ //# sourceMappingURL=input-select.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input-select.d.ts","sourceRoot":"","sources":["../src/input-select.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAS,KAAK,UAAU,EAAE,MAAM,SAAS,CAAC;AAUjD,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,gBACf,SAAQ,IAAI,CAAC,UAAU,EAAE,WAAW,GAAG,oBAAoB,GAAG,SAAS,CAAC;IACxE,yDAAyD;IACzD,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,aAAa,EAAE,iBAAiB,EAAE,CAAC;IACnC,eAAe,EAAE,MAAM,CAAC;IACxB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,2BAA2B;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;GAIG;AACH,eAAO,MAAM,WAAW,+GAuDvB,CAAC"}
package/dist/select.d.ts CHANGED
@@ -5,8 +5,8 @@ export declare const SelectValue: import("react").ForwardRefExoticComponent<Sele
5
5
  /** Select root; disabled when there are no items to choose from. */
6
6
  export declare function Select({ children, disabled, ...props }: ComponentPropsWithoutRef<typeof SelectPrimitive.Root>): import("react").JSX.Element;
7
7
  export interface SelectTriggerProps extends ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger> {
8
- /** `field` fills a form; `inline` mimics the compact app dropdown. */
9
- variant?: "field" | "inline";
8
+ /** `field` fills a form; `inline` mimics the compact app dropdown; `ghost` mirrors a ghost button. */
9
+ variant?: "field" | "inline" | "ghost";
10
10
  }
11
11
  export declare const SelectTrigger: import("react").ForwardRefExoticComponent<SelectTriggerProps & import("react").RefAttributes<HTMLButtonElement>>;
12
12
  export declare const SelectContent: import("react").ForwardRefExoticComponent<Omit<SelectPrimitive.SelectContentProps & import("react").RefAttributes<HTMLDivElement>, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
@@ -1 +1 @@
1
- {"version":3,"file":"select.d.ts","sourceRoot":"","sources":["../src/select.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,eAAe,MAAM,wBAAwB,CAAC;AAE1D,OAAO,EAEL,KAAK,wBAAwB,EAE9B,MAAM,OAAO,CAAC;AAKf,eAAO,MAAM,WAAW,6HAAwB,CAAC;AACjD,eAAO,MAAM,WAAW,8HAAwB,CAAC;AAEjD,oEAAoE;AACpE,wBAAgB,MAAM,CAAC,EACrB,QAAQ,EACR,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,wBAAwB,CAAC,OAAO,eAAe,CAAC,IAAI,CAAC,+BAQvD;AAED,MAAM,WAAW,kBAAmB,SAAQ,wBAAwB,CAClE,OAAO,eAAe,CAAC,OAAO,CAC/B;IACC,sEAAsE;IACtE,OAAO,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;CAC9B;AAED,eAAO,MAAM,aAAa,kHAuBxB,CAAC;AAGH,eAAO,MAAM,aAAa,4LA+BzB,CAAC;AAGF,eAAO,MAAM,UAAU,yLAmBrB,CAAC;AAGH,eAAO,MAAM,WAAW,0LAStB,CAAC"}
1
+ {"version":3,"file":"select.d.ts","sourceRoot":"","sources":["../src/select.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,eAAe,MAAM,wBAAwB,CAAC;AAE1D,OAAO,EAEL,KAAK,wBAAwB,EAE9B,MAAM,OAAO,CAAC;AAKf,eAAO,MAAM,WAAW,6HAAwB,CAAC;AACjD,eAAO,MAAM,WAAW,8HAAwB,CAAC;AAEjD,oEAAoE;AACpE,wBAAgB,MAAM,CAAC,EACrB,QAAQ,EACR,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,wBAAwB,CAAC,OAAO,eAAe,CAAC,IAAI,CAAC,+BAQvD;AAED,MAAM,WAAW,kBAAmB,SAAQ,wBAAwB,CAClE,OAAO,eAAe,CAAC,OAAO,CAC/B;IACC,sGAAsG;IACtG,OAAO,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;CACxC;AAED,eAAO,MAAM,aAAa,kHA2BxB,CAAC;AAGH,eAAO,MAAM,aAAa,4LA+BzB,CAAC;AAGF,eAAO,MAAM,UAAU,yLAmBrB,CAAC;AAGH,eAAO,MAAM,WAAW,0LAStB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jinaan/uikit",
3
- "version": "0.1.42",
3
+ "version": "0.1.44",
4
4
  "description": "Reusable React and Tailwind UI components for Jinaan projects.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -141,6 +141,14 @@ interface DataTableProps<T> {
141
141
  onRowClick?: (row: T) => void;
142
142
 
143
143
  defaultSort?: { id: string; dir: "asc" | "desc" };
144
+ /**
145
+ * Controlled sort state. Pass together with `onSortChange`: header caret
146
+ * clicks then report through the callback instead of mutating internal
147
+ * state, so an external control (e.g. a toolbar "Sort by" select) and the
148
+ * headers can drive one shared state.
149
+ */
150
+ sort?: { id: string; dir: "asc" | "desc" };
151
+ onSortChange?: (sort: { id: string; dir: "asc" | "desc" } | null) => void;
144
152
  pageSize?: number;
145
153
  pageSizeOptions?: number[];
146
154
 
@@ -223,6 +231,8 @@ export function DataTable<T>({
223
231
  rowActions,
224
232
  onRowClick,
225
233
  defaultSort,
234
+ sort: sortProp,
235
+ onSortChange,
226
236
  pageSize: initialPageSize = 25,
227
237
  pageSizeOptions = [10, 25, 50, 100],
228
238
  pagination = "auto",
@@ -240,9 +250,15 @@ export function DataTable<T>({
240
250
  // Column order and runtime pins are per-mount, like `hidden`.
241
251
  const [order, setOrder] = useState<string[] | null>(null);
242
252
  const [dragId, setDragId] = useState<string | null>(null);
243
- const [sort, setSort] = useState<{ id: string; dir: "asc" | "desc" } | null>(
253
+ const [internalSort, setInternalSort] = useState<{ id: string; dir: "asc" | "desc" } | null>(
244
254
  defaultSort ?? null,
245
255
  );
256
+ // Controlled when the caller passes onSortChange; uncontrolled otherwise.
257
+ const sort = onSortChange ? sortProp ?? null : internalSort;
258
+ const setSort = (next: { id: string; dir: "asc" | "desc" } | null) => {
259
+ if (onSortChange) onSortChange(next);
260
+ else setInternalSort(next);
261
+ };
246
262
  const [page, setPage] = useState(0);
247
263
  const [pageSize, setPageSize] = useState(initialPageSize);
248
264
  const [selected, setSelected] = useState<Set<string>>(new Set());
@@ -323,7 +339,8 @@ export function DataTable<T>({
323
339
 
324
340
  const toggleSort = (col: Column<T>) => {
325
341
  if (!col.sortable) return;
326
- setSort((prev) =>
342
+ const prev = sort;
343
+ setSort(
327
344
  prev?.id === col.id
328
345
  ? prev.dir === "asc"
329
346
  ? { id: col.id, dir: "desc" }
package/src/index.ts CHANGED
@@ -12,6 +12,7 @@ export * from "./dialog";
12
12
  export * from "./dropdown-menu";
13
13
  export * from "./empty-state";
14
14
  export * from "./input";
15
+ export * from "./input-select";
15
16
  export * from "./kbd";
16
17
  export * from "./label";
17
18
  export * from "./radio-group";
@@ -0,0 +1,93 @@
1
+ import { forwardRef } from "react";
2
+
3
+ import { Input, type InputProps } from "./input";
4
+ import {
5
+ Select,
6
+ SelectContent,
7
+ SelectItem,
8
+ SelectTrigger,
9
+ SelectValue,
10
+ } from "./select";
11
+ import { cn } from "./utils";
12
+
13
+ export interface InputSelectOption {
14
+ value: string;
15
+ label: string;
16
+ }
17
+
18
+ export interface InputSelectProps
19
+ extends Omit<InputProps, "className" | "containerClassName" | "onClear"> {
20
+ /** Selected value for the right-hand inline dropdown. */
21
+ selectValue: string;
22
+ onSelectChange: (value: string) => void;
23
+ selectOptions: InputSelectOption[];
24
+ selectAriaLabel: string;
25
+ selectPlaceholder?: string;
26
+ /** Outer wrapper class. */
27
+ className?: string;
28
+ inputClassName?: string;
29
+ selectClassName?: string;
30
+ }
31
+
32
+ /**
33
+ * Joined text/number input with an inline select on the right. The input keeps
34
+ * its right corners square and the dropdown keeps its left corners square so
35
+ * they read as one control.
36
+ */
37
+ export const InputSelect = forwardRef<HTMLInputElement, InputSelectProps>(
38
+ (
39
+ {
40
+ className,
41
+ inputClassName,
42
+ selectClassName,
43
+ selectValue,
44
+ onSelectChange,
45
+ selectOptions,
46
+ selectAriaLabel,
47
+ selectPlaceholder,
48
+ id,
49
+ size,
50
+ disabled,
51
+ ...inputProps
52
+ },
53
+ ref,
54
+ ) => (
55
+ <div className={cn("flex w-full items-stretch", className)}>
56
+ <Input
57
+ {...inputProps}
58
+ ref={ref}
59
+ id={id}
60
+ size={size}
61
+ disabled={disabled}
62
+ className={cn(
63
+ "min-w-0 rounded-l-(--control-radius) rounded-r-none",
64
+ inputClassName,
65
+ )}
66
+ />
67
+ <Select
68
+ value={selectValue}
69
+ onValueChange={onSelectChange}
70
+ disabled={disabled}
71
+ >
72
+ <SelectTrigger
73
+ aria-label={selectAriaLabel}
74
+ variant="ghost"
75
+ className={cn(
76
+ "min-w-28 shrink-0 rounded-l-none border border-(--input-border) border-l-0 px-3 text-[13px] font-normal",
77
+ selectClassName,
78
+ )}
79
+ >
80
+ <SelectValue placeholder={selectPlaceholder} />
81
+ </SelectTrigger>
82
+ <SelectContent>
83
+ {selectOptions.map((option) => (
84
+ <SelectItem key={option.value} value={option.value}>
85
+ {option.label}
86
+ </SelectItem>
87
+ ))}
88
+ </SelectContent>
89
+ </Select>
90
+ </div>
91
+ ),
92
+ );
93
+ InputSelect.displayName = "InputSelect";
package/src/select.tsx CHANGED
@@ -30,8 +30,8 @@ export function Select({
30
30
  export interface SelectTriggerProps extends ComponentPropsWithoutRef<
31
31
  typeof SelectPrimitive.Trigger
32
32
  > {
33
- /** `field` fills a form; `inline` mimics the compact app dropdown. */
34
- variant?: "field" | "inline";
33
+ /** `field` fills a form; `inline` mimics the compact app dropdown; `ghost` mirrors a ghost button. */
34
+ variant?: "field" | "inline" | "ghost";
35
35
  }
36
36
 
37
37
  export const SelectTrigger = forwardRef<
@@ -48,13 +48,17 @@ export const SelectTrigger = forwardRef<
48
48
  "h-(--control-h-md) w-full rounded-md border border-line bg-surface px-3 focus:border-primary focus:ring-2 focus:ring-focus/30",
49
49
  variant === "inline" &&
50
50
  "btn-fill btn-inner-border btn-interactive h-(--control-h-md) w-auto rounded-(--control-radius) border-0 bg-(--button-secondary-bg) px-(--control-pad-x-sm) font-medium text-fg hover:bg-(--button-surface-hover) active:bg-(--button-surface-active) focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-line-strong",
51
+ variant === "ghost" &&
52
+ "btn-inner-border btn-interactive h-(--control-h-md) w-auto rounded-(--control-radius) border-0 px-(--control-pad-x-sm) text-muted hover:bg-(--button-surface-hover) hover:text-fg active:bg-(--button-surface-active) focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-line-strong",
51
53
  className,
52
54
  )}
53
55
  {...props}
54
56
  >
55
57
  <span className="min-w-0 flex-1 truncate text-left">{children}</span>
56
58
  <SelectPrimitive.Icon asChild>
57
- <CaretDown className="ml-auto size-3.5 shrink-0 text-muted transition-transform duration-200 ease-out group-data-[state=open]:rotate-180 motion-reduce:transition-none" />
59
+ <span className="flex size-4 shrink-0 items-center justify-center">
60
+ <CaretDown className="size-3.5 text-muted transition-transform duration-200 ease-out group-data-[state=open]:rotate-180 motion-reduce:transition-none" />
61
+ </span>
58
62
  </SelectPrimitive.Icon>
59
63
  </SelectPrimitive.Trigger>
60
64
  ));