@page-speed/forms 0.1.0 → 0.1.2

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/dist/inputs.js CHANGED
@@ -1,4 +1,4 @@
1
- import * as React5 from 'react';
1
+ import * as React6 from 'react';
2
2
 
3
3
  function TextInput({
4
4
  name,
@@ -22,7 +22,7 @@ function TextInput({
22
22
  const baseClassName = "text-input";
23
23
  const errorClassName = error ? "text-input--error" : "";
24
24
  const combinedClassName = `${baseClassName} ${errorClassName} ${className}`.trim();
25
- return /* @__PURE__ */ React5.createElement(
25
+ return /* @__PURE__ */ React6.createElement(
26
26
  "input",
27
27
  {
28
28
  type,
@@ -68,7 +68,7 @@ function TextArea({
68
68
  const baseClassName = "textarea";
69
69
  const errorClassName = error ? "textarea--error" : "";
70
70
  const combinedClassName = `${baseClassName} ${errorClassName} ${className}`.trim();
71
- return /* @__PURE__ */ React5.createElement(
71
+ return /* @__PURE__ */ React6.createElement(
72
72
  "textarea",
73
73
  {
74
74
  name,
@@ -105,8 +105,8 @@ function Checkbox({
105
105
  label,
106
106
  ...props
107
107
  }) {
108
- const inputRef = React5.useRef(null);
109
- React5.useEffect(() => {
108
+ const inputRef = React6.useRef(null);
109
+ React6.useEffect(() => {
110
110
  if (inputRef.current) {
111
111
  inputRef.current.indeterminate = indeterminate;
112
112
  }
@@ -120,7 +120,7 @@ function Checkbox({
120
120
  const baseClassName = "checkbox";
121
121
  const errorClassName = error ? "checkbox--error" : "";
122
122
  const combinedClassName = `${baseClassName} ${errorClassName} ${className}`.trim();
123
- const checkbox = /* @__PURE__ */ React5.createElement(
123
+ const checkbox = /* @__PURE__ */ React6.createElement(
124
124
  "input",
125
125
  {
126
126
  ref: inputRef,
@@ -139,11 +139,134 @@ function Checkbox({
139
139
  }
140
140
  );
141
141
  if (label) {
142
- return /* @__PURE__ */ React5.createElement("label", { className: "checkbox-label" }, checkbox, /* @__PURE__ */ React5.createElement("span", { className: "checkbox-label-text" }, label));
142
+ return /* @__PURE__ */ React6.createElement("label", { className: "checkbox-label" }, checkbox, /* @__PURE__ */ React6.createElement("span", { className: "checkbox-label-text" }, label));
143
143
  }
144
144
  return checkbox;
145
145
  }
146
146
  Checkbox.displayName = "Checkbox";
147
+ function CheckboxGroup({
148
+ name,
149
+ value = [],
150
+ onChange,
151
+ onBlur,
152
+ disabled = false,
153
+ required = false,
154
+ error = false,
155
+ className = "",
156
+ layout = "stacked",
157
+ label,
158
+ description,
159
+ options,
160
+ showSelectAll = false,
161
+ selectAllLabel = "Select all",
162
+ minSelections,
163
+ maxSelections,
164
+ renderOption,
165
+ gridColumns = 2,
166
+ ...props
167
+ }) {
168
+ const enabledOptions = options.filter((opt) => !opt.disabled);
169
+ const enabledValues = enabledOptions.map((opt) => opt.value);
170
+ const selectedEnabledCount = value.filter(
171
+ (v) => enabledValues.includes(v)
172
+ ).length;
173
+ const allSelected = selectedEnabledCount === enabledOptions.length;
174
+ const someSelected = selectedEnabledCount > 0 && !allSelected;
175
+ const handleChange = (optionValue, checked) => {
176
+ const newValues = checked ? [...value, optionValue] : value.filter((v) => v !== optionValue);
177
+ if (maxSelections && checked && newValues.length > maxSelections) {
178
+ return;
179
+ }
180
+ onChange(newValues);
181
+ };
182
+ const handleSelectAll = (checked) => {
183
+ if (checked) {
184
+ const allValues = enabledOptions.map((opt) => opt.value);
185
+ onChange(allValues);
186
+ } else {
187
+ onChange([]);
188
+ }
189
+ };
190
+ const handleBlur = () => {
191
+ onBlur?.();
192
+ };
193
+ const baseClassName = "checkbox-group";
194
+ const errorClassName = error ? "checkbox-group--error" : "";
195
+ const layoutClassName = `checkbox-group--${layout}`;
196
+ const combinedClassName = `${baseClassName} ${errorClassName} ${layoutClassName} ${className}`.trim();
197
+ const maxReached = Boolean(maxSelections && value.length >= maxSelections);
198
+ return /* @__PURE__ */ React6.createElement(
199
+ "div",
200
+ {
201
+ className: combinedClassName,
202
+ role: "group",
203
+ "aria-invalid": error || props["aria-invalid"],
204
+ "aria-describedby": props["aria-describedby"],
205
+ "aria-required": required || props["aria-required"],
206
+ "aria-label": typeof label === "string" ? label : props["aria-label"],
207
+ style: layout === "grid" ? {
208
+ gridTemplateColumns: `repeat(${gridColumns}, 1fr)`
209
+ } : void 0
210
+ },
211
+ label && /* @__PURE__ */ React6.createElement("div", { className: "checkbox-group-label" }, label),
212
+ description && /* @__PURE__ */ React6.createElement("div", { className: "checkbox-group-description" }, description),
213
+ /* @__PURE__ */ React6.createElement("div", { className: "checkbox-options" }, showSelectAll && enabledOptions.length > 0 && /* @__PURE__ */ React6.createElement("label", { className: "checkbox-option checkbox-option--select-all" }, /* @__PURE__ */ React6.createElement(
214
+ "input",
215
+ {
216
+ type: "checkbox",
217
+ checked: allSelected,
218
+ ref: (input) => {
219
+ if (input) {
220
+ input.indeterminate = someSelected;
221
+ }
222
+ },
223
+ onChange: (e) => handleSelectAll(e.target.checked),
224
+ onBlur: handleBlur,
225
+ disabled,
226
+ className: "checkbox-input",
227
+ "aria-label": selectAllLabel
228
+ }
229
+ ), /* @__PURE__ */ React6.createElement("div", { className: "checkbox-content" }, /* @__PURE__ */ React6.createElement("span", { className: "checkbox-label" }, selectAllLabel))), options.map((option) => {
230
+ const isChecked = value.includes(option.value);
231
+ const isDisabled = disabled || option.disabled || maxReached && !isChecked;
232
+ const checkboxId = `${name}-${option.value}`;
233
+ return /* @__PURE__ */ React6.createElement(
234
+ "label",
235
+ {
236
+ key: option.value,
237
+ className: `checkbox-option ${isDisabled ? "checkbox-option--disabled" : ""}`,
238
+ htmlFor: checkboxId
239
+ },
240
+ /* @__PURE__ */ React6.createElement(
241
+ "input",
242
+ {
243
+ type: "checkbox",
244
+ id: checkboxId,
245
+ name,
246
+ value: option.value,
247
+ checked: isChecked,
248
+ onChange: (e) => handleChange(option.value, e.target.checked),
249
+ onBlur: handleBlur,
250
+ disabled: isDisabled,
251
+ required: required && minSelections ? value.length < minSelections : false,
252
+ className: "checkbox-input",
253
+ "aria-describedby": option.description ? `${checkboxId}-description` : props["aria-describedby"]
254
+ }
255
+ ),
256
+ /* @__PURE__ */ React6.createElement("div", { className: "checkbox-content" }, renderOption ? renderOption(option) : /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement("span", { className: "checkbox-label" }, option.label), option.description && /* @__PURE__ */ React6.createElement(
257
+ "span",
258
+ {
259
+ className: "checkbox-description",
260
+ id: `${checkboxId}-description`
261
+ },
262
+ option.description
263
+ )))
264
+ );
265
+ })),
266
+ (minSelections || maxSelections) && /* @__PURE__ */ React6.createElement("div", { className: "checkbox-group-feedback", "aria-live": "polite" }, minSelections && value.length < minSelections && /* @__PURE__ */ React6.createElement("span", { className: "checkbox-group-feedback-min" }, "Select at least ", minSelections, " option", minSelections !== 1 ? "s" : ""), maxSelections && /* @__PURE__ */ React6.createElement("span", { className: "checkbox-group-feedback-max" }, value.length, "/", maxSelections, " selected"))
267
+ );
268
+ }
269
+ CheckboxGroup.displayName = "CheckboxGroup";
147
270
  function Radio({
148
271
  name,
149
272
  value,
@@ -193,7 +316,7 @@ function Radio({
193
316
  const errorClassName = error ? "radio-group--error" : "";
194
317
  const layoutClassName = `radio-group--${layout}`;
195
318
  const combinedClassName = `${baseClassName} ${errorClassName} ${layoutClassName} ${className}`.trim();
196
- return /* @__PURE__ */ React5.createElement(
319
+ return /* @__PURE__ */ React6.createElement(
197
320
  "div",
198
321
  {
199
322
  className: combinedClassName,
@@ -203,19 +326,19 @@ function Radio({
203
326
  "aria-required": required || props["aria-required"],
204
327
  "aria-label": typeof label === "string" ? label : props["aria-label"]
205
328
  },
206
- label && /* @__PURE__ */ React5.createElement("div", { className: "radio-group-label" }, label),
207
- /* @__PURE__ */ React5.createElement("div", { className: "radio-options" }, options.map((option, index) => {
329
+ label && /* @__PURE__ */ React6.createElement("div", { className: "radio-group-label" }, label),
330
+ /* @__PURE__ */ React6.createElement("div", { className: "radio-options" }, options.map((option, index) => {
208
331
  const isChecked = value === option.value;
209
332
  const isDisabled = disabled || option.disabled;
210
333
  const radioId = `${name}-${option.value}`;
211
- return /* @__PURE__ */ React5.createElement(
334
+ return /* @__PURE__ */ React6.createElement(
212
335
  "label",
213
336
  {
214
337
  key: option.value,
215
338
  className: `radio-option ${isDisabled ? "radio-option--disabled" : ""}`,
216
339
  htmlFor: radioId
217
340
  },
218
- /* @__PURE__ */ React5.createElement(
341
+ /* @__PURE__ */ React6.createElement(
219
342
  "input",
220
343
  {
221
344
  type: "radio",
@@ -232,7 +355,7 @@ function Radio({
232
355
  "aria-describedby": option.description ? `${radioId}-description` : props["aria-describedby"]
233
356
  }
234
357
  ),
235
- /* @__PURE__ */ React5.createElement("div", { className: "radio-content" }, /* @__PURE__ */ React5.createElement("span", { className: "radio-label" }, option.label), option.description && /* @__PURE__ */ React5.createElement(
358
+ /* @__PURE__ */ React6.createElement("div", { className: "radio-content" }, /* @__PURE__ */ React6.createElement("span", { className: "radio-label" }, option.label), option.description && /* @__PURE__ */ React6.createElement(
236
359
  "span",
237
360
  {
238
361
  className: "radio-description",
@@ -264,19 +387,19 @@ function Select({
264
387
  renderOption,
265
388
  ...props
266
389
  }) {
267
- const [isOpen, setIsOpen] = React5.useState(false);
268
- const [searchQuery, setSearchQuery] = React5.useState("");
269
- const [focusedIndex, setFocusedIndex] = React5.useState(-1);
270
- const selectRef = React5.useRef(null);
271
- const searchInputRef = React5.useRef(null);
390
+ const [isOpen, setIsOpen] = React6.useState(false);
391
+ const [searchQuery, setSearchQuery] = React6.useState("");
392
+ const [focusedIndex, setFocusedIndex] = React6.useState(-1);
393
+ const selectRef = React6.useRef(null);
394
+ const searchInputRef = React6.useRef(null);
272
395
  const dropdownId = `${name}-dropdown`;
273
- const allOptions = React5.useMemo(() => {
396
+ const allOptions = React6.useMemo(() => {
274
397
  if (optionGroups.length > 0) {
275
398
  return optionGroups.flatMap((group) => group.options);
276
399
  }
277
400
  return options;
278
401
  }, [options, optionGroups]);
279
- const filteredOptions = React5.useMemo(() => {
402
+ const filteredOptions = React6.useMemo(() => {
280
403
  if (!searchQuery.trim()) {
281
404
  return allOptions;
282
405
  }
@@ -286,7 +409,7 @@ function Select({
286
409
  return label.toLowerCase().includes(query);
287
410
  });
288
411
  }, [allOptions, searchQuery]);
289
- const selectedOption = React5.useMemo(() => {
412
+ const selectedOption = React6.useMemo(() => {
290
413
  return allOptions.find((opt) => opt.value === value);
291
414
  }, [allOptions, value]);
292
415
  const handleSelect = (optionValue) => {
@@ -390,7 +513,7 @@ function Select({
390
513
  const handleBlur = () => {
391
514
  onBlur?.();
392
515
  };
393
- React5.useEffect(() => {
516
+ React6.useEffect(() => {
394
517
  const handleClickOutside = (event) => {
395
518
  if (selectRef.current && !selectRef.current.contains(event.target)) {
396
519
  setIsOpen(false);
@@ -411,7 +534,7 @@ function Select({
411
534
  const disabledClassName = disabled ? "select--disabled" : "";
412
535
  const openClassName = isOpen ? "select--open" : "";
413
536
  const combinedClassName = `${baseClassName} ${errorClassName} ${disabledClassName} ${openClassName} ${className}`.trim();
414
- return /* @__PURE__ */ React5.createElement(
537
+ return /* @__PURE__ */ React6.createElement(
415
538
  "div",
416
539
  {
417
540
  ref: selectRef,
@@ -419,7 +542,7 @@ function Select({
419
542
  onKeyDown: handleKeyDown,
420
543
  onBlur: handleBlur
421
544
  },
422
- /* @__PURE__ */ React5.createElement(
545
+ /* @__PURE__ */ React6.createElement(
423
546
  "select",
424
547
  {
425
548
  name,
@@ -432,10 +555,10 @@ function Select({
432
555
  tabIndex: -1,
433
556
  style: { display: "none" }
434
557
  },
435
- /* @__PURE__ */ React5.createElement("option", { value: "" }, "Select..."),
436
- allOptions.map((option) => /* @__PURE__ */ React5.createElement("option", { key: option.value, value: option.value }, typeof option.label === "string" ? option.label : option.value))
558
+ /* @__PURE__ */ React6.createElement("option", { value: "" }, "Select..."),
559
+ allOptions.map((option) => /* @__PURE__ */ React6.createElement("option", { key: option.value, value: option.value }, typeof option.label === "string" ? option.label : option.value))
437
560
  ),
438
- /* @__PURE__ */ React5.createElement(
561
+ /* @__PURE__ */ React6.createElement(
439
562
  "div",
440
563
  {
441
564
  className: "select-trigger",
@@ -449,8 +572,8 @@ function Select({
449
572
  "aria-disabled": disabled,
450
573
  tabIndex: disabled ? -1 : 0
451
574
  },
452
- /* @__PURE__ */ React5.createElement("span", { className: "select-value" }, selectedOption ? renderOption ? renderOption(selectedOption) : selectedOption.label : /* @__PURE__ */ React5.createElement("span", { className: "select-placeholder" }, placeholder)),
453
- /* @__PURE__ */ React5.createElement("div", { className: "select-icons" }, loading && /* @__PURE__ */ React5.createElement("span", { className: "select-loading" }, "\u23F3"), clearable && value && !disabled && !loading && /* @__PURE__ */ React5.createElement(
575
+ /* @__PURE__ */ React6.createElement("span", { className: "select-value" }, selectedOption ? renderOption ? renderOption(selectedOption) : selectedOption.label : /* @__PURE__ */ React6.createElement("span", { className: "select-placeholder" }, placeholder)),
576
+ /* @__PURE__ */ React6.createElement("div", { className: "select-icons" }, loading && /* @__PURE__ */ React6.createElement("span", { className: "select-loading" }, "\u23F3"), clearable && value && !disabled && !loading && /* @__PURE__ */ React6.createElement(
454
577
  "button",
455
578
  {
456
579
  type: "button",
@@ -460,9 +583,9 @@ function Select({
460
583
  tabIndex: -1
461
584
  },
462
585
  "\u2715"
463
- ), /* @__PURE__ */ React5.createElement("span", { className: "select-arrow", "aria-hidden": "true" }, isOpen ? "\u25B2" : "\u25BC"))
586
+ ), /* @__PURE__ */ React6.createElement("span", { className: "select-arrow", "aria-hidden": "true" }, isOpen ? "\u25B2" : "\u25BC"))
464
587
  ),
465
- isOpen && /* @__PURE__ */ React5.createElement("div", { id: dropdownId, className: "select-dropdown", role: "listbox" }, searchable && /* @__PURE__ */ React5.createElement("div", { className: "select-search" }, /* @__PURE__ */ React5.createElement(
588
+ isOpen && /* @__PURE__ */ React6.createElement("div", { id: dropdownId, className: "select-dropdown", role: "listbox" }, searchable && /* @__PURE__ */ React6.createElement("div", { className: "select-search" }, /* @__PURE__ */ React6.createElement(
466
589
  "input",
467
590
  {
468
591
  ref: searchInputRef,
@@ -474,19 +597,19 @@ function Select({
474
597
  onClick: (e) => e.stopPropagation(),
475
598
  "aria-label": "Search options"
476
599
  }
477
- )), /* @__PURE__ */ React5.createElement("div", { className: "select-options" }, filteredOptions.length === 0 ? /* @__PURE__ */ React5.createElement("div", { className: "select-no-options" }, "No options found") : optionGroups.length > 0 ? (
600
+ )), /* @__PURE__ */ React6.createElement("div", { className: "select-options" }, filteredOptions.length === 0 ? /* @__PURE__ */ React6.createElement("div", { className: "select-no-options" }, "No options found") : optionGroups.length > 0 ? (
478
601
  // Render grouped options
479
602
  optionGroups.map((group, groupIndex) => {
480
603
  const groupOptions = group.options.filter(
481
604
  (opt) => filteredOptions.includes(opt)
482
605
  );
483
606
  if (groupOptions.length === 0) return null;
484
- return /* @__PURE__ */ React5.createElement("div", { key: groupIndex, className: "select-optgroup" }, /* @__PURE__ */ React5.createElement("div", { className: "select-optgroup-label" }, group.label), groupOptions.map((option) => {
607
+ return /* @__PURE__ */ React6.createElement("div", { key: groupIndex, className: "select-optgroup" }, /* @__PURE__ */ React6.createElement("div", { className: "select-optgroup-label" }, group.label), groupOptions.map((option) => {
485
608
  const globalIndex = filteredOptions.indexOf(option);
486
609
  const isSelected = value === option.value;
487
610
  const isFocused = globalIndex === focusedIndex;
488
611
  const isDisabled = option.disabled;
489
- return /* @__PURE__ */ React5.createElement(
612
+ return /* @__PURE__ */ React6.createElement(
490
613
  "div",
491
614
  {
492
615
  key: option.value,
@@ -506,7 +629,7 @@ function Select({
506
629
  const isSelected = value === option.value;
507
630
  const isFocused = index === focusedIndex;
508
631
  const isDisabled = option.disabled;
509
- return /* @__PURE__ */ React5.createElement(
632
+ return /* @__PURE__ */ React6.createElement(
510
633
  "div",
511
634
  {
512
635
  key: option.value,
@@ -524,6 +647,6 @@ function Select({
524
647
  }
525
648
  Select.displayName = "Select";
526
649
 
527
- export { Checkbox, Radio, Select, TextArea, TextInput };
650
+ export { Checkbox, CheckboxGroup, Radio, Select, TextArea, TextInput };
528
651
  //# sourceMappingURL=inputs.js.map
529
652
  //# sourceMappingURL=inputs.js.map