@djangocfg/ui-core 2.1.319 → 2.1.321

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.
@@ -23,6 +23,8 @@ components/select/
23
23
 
24
24
  Low-level primitives with icon and badge support.
25
25
 
26
+ > **Empty-string values are allowed.** Radix forbids `<Select.Item value="" />` (it reserves `''` for "cleared selection"). Our `Select` wrapper substitutes a sentinel (`'__empty__'`) internally for both the controlled value and item values, so consumers can use `''` as a normal enum value (e.g. for "none"). The sentinel never leaks into `onValueChange` — it's an implementation detail.
27
+
26
28
  ```tsx
27
29
  import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from '@djangocfg/ui-core';
28
30
 
@@ -8,7 +8,42 @@ import * as SelectPrimitive from '@radix-ui/react-select';
8
8
  import { cn } from '../../lib/utils';
9
9
  import { Badge } from '../data/badge';
10
10
 
11
- const Select = SelectPrimitive.Root
11
+ /**
12
+ * Radix `<Select.Item value="" />` is reserved (it's how Radix represents
13
+ * "cleared selection"), so passing an empty string at the consumer level
14
+ * throws. This wrapper transparently substitutes empty strings with a
15
+ * sentinel for the Radix layer and unwraps it again on `onValueChange`,
16
+ * letting consumers use `''` for "none" without inventing their own marker.
17
+ *
18
+ * Applies to `Select` (controlled value/defaultValue/onValueChange) and
19
+ * `SelectItem` (item value).
20
+ */
21
+ const EMPTY_SENTINEL = '__empty__';
22
+ const toRadix = (raw: string | undefined): string | undefined => {
23
+ if (raw === undefined) return undefined;
24
+ return raw === '' ? EMPTY_SENTINEL : raw;
25
+ };
26
+ const fromRadix = (raw: string): string => (raw === EMPTY_SENTINEL ? '' : raw);
27
+
28
+ function Select(
29
+ props: React.ComponentPropsWithoutRef<typeof SelectPrimitive.Root>,
30
+ ): React.ReactElement {
31
+ const { value, defaultValue, onValueChange, ...rest } = props;
32
+ const handleChange = React.useCallback(
33
+ (next: string) => {
34
+ onValueChange?.(fromRadix(next));
35
+ },
36
+ [onValueChange],
37
+ );
38
+ return (
39
+ <SelectPrimitive.Root
40
+ value={toRadix(value)}
41
+ defaultValue={toRadix(defaultValue)}
42
+ onValueChange={onValueChange ? handleChange : undefined}
43
+ {...rest}
44
+ />
45
+ );
46
+ }
12
47
 
13
48
  const SelectGroup = SelectPrimitive.Group
14
49
 
@@ -120,14 +155,15 @@ SelectLabel.displayName = SelectPrimitive.Label.displayName
120
155
 
121
156
  const SelectItem = React.forwardRef<
122
157
  React.ElementRef<typeof SelectPrimitive.Item>,
123
- React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item> & {
158
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item> & {
124
159
  key?: React.Key;
125
160
  icon?: React.ComponentType<{ className?: string }>;
126
161
  badge?: string | React.ReactNode;
127
162
  }
128
- >(({ className, children, icon: Icon, badge, ...props }, ref) => (
163
+ >(({ className, children, icon: Icon, badge, value, ...props }, ref) => (
129
164
  <SelectPrimitive.Item
130
165
  ref={ref}
166
+ value={value === '' ? EMPTY_SENTINEL : value}
131
167
  className={cn(
132
168
  "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
133
169
  className
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Shared types for schema-driven configurator UIs in the djangocfg monorepo.
3
+ *
4
+ * This is **our** mini-spec — a Draft 7-shaped subset that any package can use
5
+ * to describe a configurator without taking a runtime dependency on RJSF or
6
+ * any other form framework. `@djangocfg/ui-tools`'s `<JsonSchemaForm>` accepts
7
+ * these types directly (it casts internally to RJSF's own types).
8
+ *
9
+ * Use this when you ship a configurator schema from a non-form package
10
+ * (e.g. `@djangocfg/layouts/configurator`) so the package stays decoupled
11
+ * from any concrete form framework.
12
+ *
13
+ * Naming choice: not `JsonSchema` (would clash with the broader Draft 7 spec
14
+ * and other community types). `CustomJsonSchema7` makes the scope explicit:
15
+ * this is for sidebar / settings configurators, not arbitrary JSON validation.
16
+ */
17
+
18
+ /** Primitive JSON Schema types we cover. */
19
+ export type CustomJsonSchema7Type =
20
+ | 'object'
21
+ | 'array'
22
+ | 'string'
23
+ | 'number'
24
+ | 'integer'
25
+ | 'boolean'
26
+ | 'null';
27
+
28
+ /** A Draft 7-shaped JSON Schema descriptor — sufficient for configurator UIs. */
29
+ export interface CustomJsonSchema7 {
30
+ type?: CustomJsonSchema7Type;
31
+ title?: string;
32
+ description?: string;
33
+ default?: unknown;
34
+ enum?: readonly (string | number | boolean | null)[];
35
+ /** Whitelist of property keys that must be set (objects only). */
36
+ required?: readonly string[];
37
+ /** Object child schemas. */
38
+ properties?: Record<string, CustomJsonSchema7>;
39
+ /** Array item schema. */
40
+ items?: CustomJsonSchema7;
41
+ /** Numeric bounds. */
42
+ minimum?: number;
43
+ maximum?: number;
44
+ /** String / array bounds. */
45
+ minLength?: number;
46
+ maxLength?: number;
47
+ /** String format (e.g. `'email'`, `'uri'`). */
48
+ format?: string;
49
+ /** Free-form extras kept as-is — let downstream form frameworks read them. */
50
+ [key: string]: unknown;
51
+ }
52
+
53
+ /**
54
+ * Conditional-disable rule attached to a field via `'ui:disabledWhen'` in
55
+ * `CustomJsonUiSchema7`. Evaluated against the form's current data by the
56
+ * runtime (`@djangocfg/ui-tools`'s `evaluateDisabledWhen`).
57
+ */
58
+ export type CustomJsonUiDisabledWhenRule =
59
+ | { path: string; eq: unknown }
60
+ | { path: string; notEq: unknown }
61
+ | { path: string; in: readonly unknown[] }
62
+ | { path: string; notIn: readonly unknown[] }
63
+ | { path: string; truthy: true }
64
+ | { path: string; falsy: true };
65
+
66
+ /**
67
+ * Sub-group descriptor used by `'ui:groups'` on an object's uiSchema. Splits
68
+ * the object into collapsible sub-sections without restructuring the schema.
69
+ */
70
+ export interface CustomJsonUiGroup {
71
+ /** Section title shown on the trigger row. */
72
+ title: string;
73
+ /** Property keys to include in this group. */
74
+ fields: readonly string[];
75
+ /** Initial open state. Default `true`. */
76
+ defaultOpen?: boolean;
77
+ }
78
+
79
+ /**
80
+ * Per-field UI hints that travel alongside `CustomJsonSchema7`. Mirrors the
81
+ * subset of RJSF `uiSchema` we use, plus our own `ui:disabledWhen`
82
+ * / `ui:groups` extensions.
83
+ */
84
+ export interface CustomJsonUiSchema7 {
85
+ /** Override the widget for this field (`'select'`, `'switch'`, `'slider'`, `'textarea'`, …). */
86
+ 'ui:widget'?: string;
87
+ /** Free-form widget options (e.g. `{ unit: 'px', step: 2 }` for slider). */
88
+ 'ui:options'?: Record<string, unknown>;
89
+ /** Description override; in compact density the value moves into the label tooltip. */
90
+ 'ui:description'?: string;
91
+ /** Wrap this object body in a single Collapsible card. */
92
+ 'ui:collapsible'?: boolean;
93
+ /** Initial collapsed state when `ui:collapsible`. Default `false` (open). */
94
+ 'ui:collapsed'?: boolean;
95
+ /** Grid column count for an object body (1, 2, 3 …). */
96
+ 'ui:grid'?: number;
97
+ /** Extra wrapper class. */
98
+ 'ui:className'?: string;
99
+ /** Split an object into collapsible sub-sections. */
100
+ 'ui:groups'?: readonly CustomJsonUiGroup[];
101
+ /** Declarative conditional disable. */
102
+ 'ui:disabledWhen'?: CustomJsonUiDisabledWhenRule;
103
+ /** Per-property nested uiSchema (keys mirror `properties` keys on the schema). */
104
+ [key: string]: unknown;
105
+ }
package/src/lib/index.ts CHANGED
@@ -3,3 +3,10 @@ export * from "./logger";
3
3
  export * from "./dialog-service";
4
4
  export * from "./env";
5
5
  export * from "./persist";
6
+ export type {
7
+ CustomJsonSchema7,
8
+ CustomJsonSchema7Type,
9
+ CustomJsonUiSchema7,
10
+ CustomJsonUiGroup,
11
+ CustomJsonUiDisabledWhenRule,
12
+ } from "./configurator-schema";