@cosxai/ui 0.7.0 → 0.8.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": "@cosxai/ui",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "COSX design system — React 19 component primitives shared across product-meta and other consumers",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -568,12 +568,19 @@ const overlayStyle: React.CSSProperties = {
568
568
  zIndex: 0,
569
569
  };
570
570
 
571
+ // chipStyle must NOT add any horizontal or vertical space (no
572
+ // padding, no margin, no border) — the textarea's underlying
573
+ // `@Ben Zhang` glyphs occupy the natural text width, and any extra
574
+ // width on the overlay chip would shift every character after it
575
+ // out of alignment with the textarea's caret. Instead we lean on
576
+ // background + color + a tight border-radius to convey "chip"
577
+ // without adding layout. `font-weight` stays at the inherited
578
+ // value for the same reason: heavier weight changes glyph advance
579
+ // widths and drifts the trailing text by a sub-pixel per character.
571
580
  const chipStyle: React.CSSProperties = {
572
- padding: "0 4px",
573
- borderRadius: 4,
581
+ borderRadius: 3,
574
582
  background: "var(--ck-accent-soft, rgba(37, 99, 235, 0.12))",
575
583
  color: "var(--ck-accent, #2563eb)",
576
- fontWeight: 500,
577
584
  };
578
585
 
579
586
  const listboxStyle: React.CSSProperties = {
@@ -0,0 +1,127 @@
1
+ import { cn } from "../lib/cn";
2
+
3
+ // Custom-styled radio — accent-bordered circle that becomes filled
4
+ // accent + white dot when checked. Paired with Checkbox: same
5
+ // disabled/label/suffix contract, same 16px hit target size. Use
6
+ // this anywhere you'd reach for a `<input type="radio">` — the
7
+ // native control renders as the OS accent (blue on macOS by
8
+ // default) which doesn't respect --ck-accent per workspace, and the
9
+ // unstyled defaults look inconsistent alongside our Checkbox.
10
+ //
11
+ // Radio is a single option in a group; the caller owns the group's
12
+ // selection state and passes `checked` + `onChange` per option.
13
+ // Radios with the same `name` still get the browser's native "one
14
+ // selected at a time" behaviour if you need form-post semantics; we
15
+ // forward it to a hidden native input so aria-pressed on the visual
16
+ // button doesn't drift from the actual form value.
17
+
18
+ export interface RadioProps {
19
+ checked: boolean;
20
+ onChange: (next: boolean) => void;
21
+ label?: React.ReactNode;
22
+ suffix?: React.ReactNode;
23
+ disabled?: boolean;
24
+ /**
25
+ * Native radio-group name. Optional but recommended: browsers
26
+ * enforce "only one checked at a time" for same-name radios, so
27
+ * passing a shared name is a belt-and-braces guard against a
28
+ * stale onChange handler leaving two visually-checked radios.
29
+ */
30
+ name?: string;
31
+ /**
32
+ * Value that lands in the form-post payload when this radio is
33
+ * checked. Only meaningful when `name` is also set.
34
+ */
35
+ value?: string;
36
+ className?: string;
37
+ }
38
+
39
+ export function Radio({
40
+ checked,
41
+ onChange,
42
+ label,
43
+ suffix,
44
+ disabled,
45
+ name,
46
+ value,
47
+ className,
48
+ }: RadioProps) {
49
+ return (
50
+ <label
51
+ className={cn("ck-radio", className)}
52
+ style={{
53
+ display: "inline-flex",
54
+ alignItems: "center",
55
+ gap: 8,
56
+ font: "400 13px/1 var(--ck-font-sans)",
57
+ color: "var(--ck-text-secondary)",
58
+ cursor: disabled ? "not-allowed" : "pointer",
59
+ opacity: disabled ? 0.5 : 1,
60
+ userSelect: "none",
61
+ }}
62
+ >
63
+ <button
64
+ type="button"
65
+ role="radio"
66
+ aria-checked={checked}
67
+ disabled={disabled}
68
+ onClick={() => !disabled && !checked && onChange(true)}
69
+ style={{
70
+ width: 16,
71
+ height: 16,
72
+ borderRadius: "50%",
73
+ border: "1.5px solid var(--ck-accent)",
74
+ background: "transparent",
75
+ display: "inline-flex",
76
+ alignItems: "center",
77
+ justifyContent: "center",
78
+ padding: 0,
79
+ cursor: disabled ? "not-allowed" : "pointer",
80
+ flexShrink: 0,
81
+ }}
82
+ >
83
+ {checked && (
84
+ <span
85
+ aria-hidden
86
+ style={{
87
+ width: 8,
88
+ height: 8,
89
+ borderRadius: "50%",
90
+ background: "var(--ck-accent)",
91
+ display: "inline-block",
92
+ }}
93
+ />
94
+ )}
95
+ </button>
96
+ {/* Hidden native input keeps the browser's same-name enforcement
97
+ + lets consumers use the surrounding <form> for post payload. */}
98
+ {name && (
99
+ <input
100
+ type="radio"
101
+ name={name}
102
+ value={value}
103
+ checked={checked}
104
+ onChange={(e) => !disabled && onChange(e.target.checked)}
105
+ disabled={disabled}
106
+ tabIndex={-1}
107
+ aria-hidden
108
+ style={{
109
+ position: "absolute",
110
+ width: 1,
111
+ height: 1,
112
+ padding: 0,
113
+ margin: -1,
114
+ overflow: "hidden",
115
+ clip: "rect(0,0,0,0)",
116
+ whiteSpace: "nowrap",
117
+ border: 0,
118
+ }}
119
+ />
120
+ )}
121
+ {label}
122
+ {suffix && (
123
+ <span style={{ color: "var(--ck-text-tertiary)" }}>{suffix}</span>
124
+ )}
125
+ </label>
126
+ );
127
+ }
@@ -18,6 +18,8 @@ export { Textarea } from "./Textarea";
18
18
  export type { TextareaProps } from "./Textarea";
19
19
  export { Checkbox } from "./Checkbox";
20
20
  export type { CheckboxProps } from "./Checkbox";
21
+ export { Radio } from "./Radio";
22
+ export type { RadioProps } from "./Radio";
21
23
  export { ToggleSwitch } from "./ToggleSwitch";
22
24
  export type { ToggleSwitchProps } from "./ToggleSwitch";
23
25
  export { Tooltip } from "./Tooltip";