@jinaan/uikit 0.1.7 → 0.1.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.
@@ -0,0 +1,4 @@
1
+ import { type ReactNode } from "react";
2
+ /** Count matching elements anywhere in a static React children tree. */
3
+ export declare function countDescendants(children: ReactNode, types: readonly unknown[]): number;
4
+ //# sourceMappingURL=item-count.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"item-count.d.ts","sourceRoot":"","sources":["../src/item-count.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAEjE,wEAAwE;AACxE,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,SAAS,EACnB,KAAK,EAAE,SAAS,OAAO,EAAE,GACxB,MAAM,CAcR"}
package/dist/select.d.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  import * as SelectPrimitive from "@radix-ui/react-select";
2
2
  import { type ComponentPropsWithoutRef } from "react";
3
- export declare const Select: import("react").FC<SelectPrimitive.SelectProps>;
4
3
  export declare const SelectGroup: import("react").ForwardRefExoticComponent<SelectPrimitive.SelectGroupProps & import("react").RefAttributes<HTMLDivElement>>;
5
4
  export declare const SelectValue: import("react").ForwardRefExoticComponent<SelectPrimitive.SelectValueProps & import("react").RefAttributes<HTMLSpanElement>>;
5
+ /** Select root; disabled when there are no items to choose from. */
6
+ export declare function Select({ children, disabled, ...props }: ComponentPropsWithoutRef<typeof SelectPrimitive.Root>): import("react").JSX.Element;
6
7
  export interface SelectTriggerProps extends ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger> {
7
8
  /** `field` fills a form; `inline` mimics the compact app dropdown. */
8
9
  variant?: "field" | "inline";
@@ -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;AAIf,eAAO,MAAM,MAAM,iDAAuB,CAAC;AAC3C,eAAO,MAAM,WAAW,6HAAwB,CAAC;AACjD,eAAO,MAAM,WAAW,8HAAwB,CAAC;AAEjD,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,sEAAsE;IACtE,OAAO,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;CAC9B;AAED,eAAO,MAAM,aAAa,kHAwBxB,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.7",
3
+ "version": "0.1.9",
4
4
  "description": "Reusable React and Tailwind UI components for Jinaan projects.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
package/src/callout.tsx CHANGED
@@ -58,6 +58,7 @@ export function Callout({
58
58
  role={variant === "error" ? "alert" : undefined}
59
59
  className={cn(
60
60
  "flex items-start gap-2.5 rounded-lg border px-3.5 py-3 text-sm",
61
+ "animate-in fade-in-0 slide-in-from-top-1 duration-200 motion-reduce:animate-none",
61
62
  tone,
62
63
  className,
63
64
  )}
@@ -1,15 +1,52 @@
1
1
  import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
2
2
  import { Check } from "@phosphor-icons/react";
3
3
  import {
4
+ createContext,
4
5
  forwardRef,
6
+ useContext,
5
7
  type ComponentPropsWithoutRef,
6
8
  type ElementRef,
7
9
  } from "react";
8
10
 
11
+ import { countDescendants } from "./item-count";
9
12
  import { cn } from "./utils";
10
13
 
11
- export const DropdownMenu = DropdownMenuPrimitive.Root;
12
- export const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
14
+ const DropdownMenuEmptyContext = createContext(false);
15
+
16
+ /** Menu root; disables its trigger when there are no items. */
17
+ export function DropdownMenu({
18
+ children,
19
+ ...props
20
+ }: ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Root>) {
21
+ const empty =
22
+ countDescendants(children, [
23
+ DropdownMenuItem,
24
+ DropdownMenuCheckboxItem,
25
+ ]) === 0;
26
+
27
+ return (
28
+ <DropdownMenuEmptyContext.Provider value={empty}>
29
+ <DropdownMenuPrimitive.Root {...props}>{children}</DropdownMenuPrimitive.Root>
30
+ </DropdownMenuEmptyContext.Provider>
31
+ );
32
+ }
33
+
34
+ export const DropdownMenuTrigger = forwardRef<
35
+ ElementRef<typeof DropdownMenuPrimitive.Trigger>,
36
+ ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Trigger>
37
+ >(({ disabled, ...props }, ref) => {
38
+ const empty = useContext(DropdownMenuEmptyContext);
39
+
40
+ return (
41
+ <DropdownMenuPrimitive.Trigger
42
+ ref={ref}
43
+ disabled={disabled || empty}
44
+ {...props}
45
+ />
46
+ );
47
+ });
48
+ DropdownMenuTrigger.displayName = "DropdownMenuTrigger";
49
+
13
50
  export const DropdownMenuGroup = DropdownMenuPrimitive.Group;
14
51
  export const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
15
52
 
@@ -22,7 +59,7 @@ export const DropdownMenuContent = forwardRef<
22
59
  ref={ref}
23
60
  sideOffset={sideOffset}
24
61
  className={cn(
25
- "menu-surface btn-inner-border z-50 min-w-48 overflow-hidden rounded-lg border border-line bg-[var(--button-secondary-bg)] p-1",
62
+ "menu-surface btn-inner-border z-50 min-w-48 overflow-hidden rounded-lg border border-line bg-(--button-secondary-bg) p-1",
26
63
  "data-[state=open]:animate-in data-[state=closed]:animate-out",
27
64
  "data-[state=open]:fade-in-0 data-[state=closed]:fade-out-0",
28
65
  "data-[state=open]:zoom-in-95 data-[state=closed]:zoom-out-95",
@@ -47,7 +84,7 @@ export const DropdownMenuItem = forwardRef<
47
84
  ref={ref}
48
85
  className={cn(
49
86
  "relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm text-fg outline-none transition-colors",
50
- "focus:bg-[var(--menu-highlight)] data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
87
+ "focus:bg-(--menu-highlight) data-disabled:pointer-events-none data-disabled:opacity-50",
51
88
  "[&_svg]:size-4 [&_svg]:text-muted",
52
89
  inset && "pl-8",
53
90
  className,
@@ -64,14 +101,14 @@ export const DropdownMenuCheckboxItem = forwardRef<
64
101
  <DropdownMenuPrimitive.CheckboxItem
65
102
  ref={ref}
66
103
  className={cn(
67
- "group relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm text-fg outline-none transition-colors focus:bg-[var(--menu-highlight)]",
104
+ "group relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm text-fg outline-none transition-colors focus:bg-(--menu-highlight)",
68
105
  className,
69
106
  )}
70
107
  checked={checked}
71
108
  {...props}
72
109
  >
73
110
  <span className="absolute left-2 flex size-4 shrink-0 items-center justify-center rounded border border-line-strong bg-transparent transition-colors group-data-[state=checked]:border-primary">
74
- <DropdownMenuPrimitive.ItemIndicator className="btn-fill btn-inner-border flex size-full items-center justify-center rounded-[3px] border-0 bg-[var(--button-primary-bg)] text-[var(--button-primary-fg)] [--button-inner:var(--button-primary-inner)]">
111
+ <DropdownMenuPrimitive.ItemIndicator className="btn-fill btn-inner-border flex size-full items-center justify-center rounded-[3px] border-0 bg-(--button-primary-bg) text-(--button-primary-fg) [--button-inner:var(--button-primary-inner)]">
75
112
  <Check className="size-3" weight="bold" />
76
113
  </DropdownMenuPrimitive.ItemIndicator>
77
114
  </span>
@@ -0,0 +1,21 @@
1
+ import { Children, isValidElement, type ReactNode } from "react";
2
+
3
+ /** Count matching elements anywhere in a static React children tree. */
4
+ export function countDescendants(
5
+ children: ReactNode,
6
+ types: readonly unknown[],
7
+ ): number {
8
+ let count = 0;
9
+
10
+ Children.forEach(children, (child) => {
11
+ if (!isValidElement(child)) return;
12
+ if (types.some((type) => type === child.type)) {
13
+ count += 1;
14
+ return;
15
+ }
16
+ const nested = (child.props as { children?: ReactNode }).children;
17
+ if (nested) count += countDescendants(nested, types);
18
+ });
19
+
20
+ return count;
21
+ }
package/src/select.tsx CHANGED
@@ -6,12 +6,27 @@ import {
6
6
  type ElementRef,
7
7
  } from "react";
8
8
 
9
+ import { countDescendants } from "./item-count";
9
10
  import { cn } from "./utils";
10
11
 
11
- export const Select = SelectPrimitive.Root;
12
12
  export const SelectGroup = SelectPrimitive.Group;
13
13
  export const SelectValue = SelectPrimitive.Value;
14
14
 
15
+ /** Select root; disabled when there are no items to choose from. */
16
+ export function Select({
17
+ children,
18
+ disabled,
19
+ ...props
20
+ }: ComponentPropsWithoutRef<typeof SelectPrimitive.Root>) {
21
+ const empty = countDescendants(children, [SelectItem]) === 0;
22
+
23
+ return (
24
+ <SelectPrimitive.Root disabled={disabled || empty} {...props}>
25
+ {children}
26
+ </SelectPrimitive.Root>
27
+ );
28
+ }
29
+
15
30
  export interface SelectTriggerProps extends ComponentPropsWithoutRef<
16
31
  typeof SelectPrimitive.Trigger
17
32
  > {
@@ -28,6 +43,7 @@ export const SelectTrigger = forwardRef<
28
43
  className={cn(
29
44
  "flex items-center justify-between gap-2 text-sm text-fg",
30
45
  "focus:outline-none disabled:cursor-not-allowed disabled:opacity-50",
46
+ "data-disabled:cursor-not-allowed data-disabled:opacity-50",
31
47
  "[&>span]:truncate",
32
48
  variant === "field" &&
33
49
  "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",