playbook_ui 15.6.0.pre.alpha.draggableask12907 → 15.6.0.pre.alpha.play263913015

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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/pb_card/docs/_card_header.md +1 -1
  3. data/app/pb_kits/playbook/pb_card/docs/_card_highlight.md +1 -1
  4. data/app/pb_kits/playbook/pb_collapsible/__snapshots__/collapsible.test.js.snap +2 -2
  5. data/app/pb_kits/playbook/pb_collapsible/child_kits/CollapsibleIcon.tsx +10 -8
  6. data/app/pb_kits/playbook/pb_collapsible/docs/_collapsible_icons.jsx +0 -1
  7. data/app/pb_kits/playbook/pb_collapsible/docs/_collapsible_state.jsx +0 -3
  8. data/app/pb_kits/playbook/pb_date_picker/date_picker.test.js +24 -0
  9. data/app/pb_kits/playbook/pb_date_picker/date_picker_helper.ts +181 -3
  10. data/app/pb_kits/playbook/pb_distribution_bar/docs/_distribution_bar_custom_colors.md +1 -1
  11. data/app/pb_kits/playbook/pb_filter/Filter/FilterBackground.tsx +3 -3
  12. data/app/pb_kits/playbook/pb_select/_select.tsx +8 -3
  13. data/app/pb_kits/playbook/pb_select/docs/_select_input_options.html.erb +16 -0
  14. data/app/pb_kits/playbook/pb_select/docs/_select_input_options.jsx +30 -0
  15. data/app/pb_kits/playbook/pb_select/docs/_select_input_options.md +1 -0
  16. data/app/pb_kits/playbook/pb_select/docs/example.yml +2 -0
  17. data/app/pb_kits/playbook/pb_select/docs/index.js +1 -0
  18. data/app/pb_kits/playbook/pb_select/select.html.erb +2 -2
  19. data/app/pb_kits/playbook/pb_select/select.rb +3 -1
  20. data/app/pb_kits/playbook/pb_select/select.test.js +23 -0
  21. data/app/pb_kits/playbook/pb_table/_table.tsx +187 -33
  22. data/app/pb_kits/playbook/pb_table/docs/_table_with_filter_variant.jsx +134 -0
  23. data/app/pb_kits/playbook/pb_table/docs/_table_with_filter_variant.md +34 -0
  24. data/app/pb_kits/playbook/pb_table/docs/_table_with_filter_variant_rails.html.erb +101 -0
  25. data/app/pb_kits/playbook/pb_table/docs/_table_with_filter_variant_rails.md +33 -0
  26. data/app/pb_kits/playbook/pb_table/docs/_table_with_filter_variant_with_pagination.jsx +180 -0
  27. data/app/pb_kits/playbook/pb_table/docs/_table_with_filter_variant_with_pagination.md +3 -0
  28. data/app/pb_kits/playbook/pb_table/docs/_table_with_filter_variant_with_pagination_rails.html.erb +122 -0
  29. data/app/pb_kits/playbook/pb_table/docs/_table_with_filter_variant_with_pagination_rails.md +3 -0
  30. data/app/pb_kits/playbook/pb_table/docs/example.yml +4 -0
  31. data/app/pb_kits/playbook/pb_table/docs/index.js +2 -0
  32. data/app/pb_kits/playbook/pb_table/table.html.erb +68 -12
  33. data/app/pb_kits/playbook/pb_table/table.rb +22 -3
  34. data/app/pb_kits/playbook/pb_table/table.test.js +143 -0
  35. data/app/pb_kits/playbook/tokens/_colors.scss +2 -1
  36. data/dist/chunks/_typeahead-CHwm9MTE.js +6 -0
  37. data/dist/chunks/lib-Cugvy62C.js +29 -0
  38. data/dist/chunks/vendor.js +2 -2
  39. data/dist/playbook-rails-react-bindings.js +1 -1
  40. data/dist/playbook-rails.js +1 -1
  41. data/dist/playbook.css +1 -1
  42. data/lib/playbook/forms/builder/collection_select_field.rb +9 -1
  43. data/lib/playbook/forms/builder/select_field.rb +9 -1
  44. data/lib/playbook/forms/builder/time_zone_select_field.rb +9 -1
  45. data/lib/playbook/version.rb +1 -1
  46. metadata +15 -4
  47. data/dist/chunks/_typeahead-Ch3OzX5L.js +0 -6
  48. data/dist/chunks/lib-CgpqUb6l.js +0 -29
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4b0f188b0fa59810bc278d4f0fd8211ec8488472f38d61f3cf422edacdb4e918
4
- data.tar.gz: 8b4daf06eead8575f94da3c2307c6df40bb49cf17128b765d7a234bbacaa895c
3
+ metadata.gz: 526e2a456a333473009406a753d4a9513b99a33bef1a69e92410d94cdc23c3aa
4
+ data.tar.gz: 0b7ef7af65efedfcf5f2f1da554acd8e9d04ab95361feaf3425d5285171946b8
5
5
  SHA512:
6
- metadata.gz: 4c2cb58e0e5f3891d8c15954fc83e9a0e5904d71600883654d15a9ffd339c399d9f19bb4688401d2f735c3adaa557e57108a43ac6c743df9587e8d2f99032913
7
- data.tar.gz: a939b477c9565c5bdb334fd8bab4cc7abc507ff2f4b6e30a18e422b22233db8598df43811066c443253094e9f8609f0ca3e0058409a1beeed20a9f9d5264c348
6
+ metadata.gz: 1775155366eab8937dfac61095faa4a4281dfe3981a6839c97e7e35fb233716da18b8b1285d1a88ff8529c0a0560858c22808e0ba0df43102ff6cbdb02efc38e
7
+ data.tar.gz: 36ae7bbfcd1182ff97a85c018ecef7232877fabf931d30428fa8ac7b69890fd31e714f0bd9e644698665a644f5d14d2eaf8dcf2f88788050a6665fdec2aad9d4
@@ -1 +1 @@
1
- Card headers pass category, product, status and background colors only. List of all category, product, status and background colors can be viewed <a href="https://playbook.powerapp.cloud/token/colors" target="_blank">here</a>.
1
+ Card headers pass category, product, status and background colors only. List of all category, product, status and background colors can be viewed <a href="https://playbook.powerapp.cloud/tokens/colors" target="_blank">here</a>.
@@ -1 +1 @@
1
- Card highlight can pass status, product, and category colors. List of all colors can be viewed <a href="https://playbook.powerapp.cloud/token/colors" target="_blank">here</a>.
1
+ Card highlight can pass status, product, and category colors. List of all colors can be viewed <a href="https://playbook.powerapp.cloud/tokens/colors" target="_blank">here</a>.
@@ -25,11 +25,11 @@ exports[`html structure is correct 1`] = `
25
25
  >
26
26
  <div
27
27
  class="icon_wrapper"
28
- style="vertical-align: middle; color: rgb(193, 205, 214);"
28
+ style="vertical-align: middle;"
29
29
  >
30
30
  <svg
31
31
  aria-label="chevron-down icon"
32
- class="pb_custom_icon svg-inline--fa svg_lg svg_fw"
32
+ class="pb_custom_icon svg-inline--fa color_text_lt_lighter svg_lg svg_fw"
33
33
  color="currentColor"
34
34
  fill="none"
35
35
  height="auto"
@@ -27,12 +27,12 @@ type colorMap = {
27
27
  };
28
28
 
29
29
  const colorMap = {
30
- default: "#242B42",
31
- light: "#687887",
32
- lighter: "#C1CDD6",
33
- link: "#0056CF",
34
- error: "#FF2229",
35
- success: "#00CA74",
30
+ default:"text_lt_default",
31
+ light: "text_lt_light",
32
+ lighter: "text_lt_lighter",
33
+ link: "primary",
34
+ error: "error",
35
+ success: "text_dk_success_sm",
36
36
  };
37
37
 
38
38
  const CollapsibleIcon = ({
@@ -68,9 +68,10 @@ const CollapsibleIcon = ({
68
68
  className="icon_wrapper"
69
69
  key={icon ? showIcon(icon)[0] : "chevron-down"}
70
70
  onClick={(e) => handleIconClick(e)}
71
- style={{ verticalAlign: "middle", color: color }}
71
+ style={{ verticalAlign: "middle"}}
72
72
  >
73
73
  <Icon
74
+ color={color}
74
75
  icon={icon ? showIcon(icon)[0] : "chevron-down"}
75
76
  size={iconSize}
76
77
  />
@@ -80,9 +81,10 @@ const CollapsibleIcon = ({
80
81
  className="icon_wrapper"
81
82
  key={icon ? showIcon(icon)[1] : "chevron-up"}
82
83
  onClick={(e) => handleIconClick(e)}
83
- style={{ verticalAlign: "middle", color: color }}
84
+ style={{ verticalAlign: "middle" }}
84
85
  >
85
86
  <Icon
87
+ color={color}
86
88
  icon={icon ? showIcon(icon)[1] : "chevron-up"}
87
89
  size={iconSize}
88
90
  />
@@ -7,7 +7,6 @@ const CollapsibleIcons = (props) => {
7
7
  <>
8
8
  <Collapsible
9
9
  icon={['plus','minus']}
10
- iconColor='white'
11
10
  >
12
11
  <Collapsible.Main {...props}>
13
12
  <div>{'Main Section'}</div>
@@ -22,7 +22,6 @@ const CollapsibleState = (props) => {
22
22
  <Collapsible
23
23
  collapsed={isCollapsed}
24
24
  icon={["plus", "minus"]}
25
- iconColor='white'
26
25
  padding="none"
27
26
  >
28
27
  <Collapsible.Main padding="sm"
@@ -41,7 +40,6 @@ const CollapsibleState = (props) => {
41
40
  <Collapsible
42
41
  collapsed={isCollapsed}
43
42
  icon={["plus", "minus"]}
44
- iconColor='white'
45
43
  padding="none"
46
44
  >
47
45
  <Collapsible.Main padding="sm"
@@ -60,7 +58,6 @@ const CollapsibleState = (props) => {
60
58
  <Collapsible
61
59
  collapsed={isCollapsed}
62
60
  icon={["plus", "minus"]}
63
- iconColor='white'
64
61
  padding="none"
65
62
  >
66
63
  <Collapsible.Main padding="sm"
@@ -251,4 +251,28 @@ describe('DatePicker Kit', () => {
251
251
  expect(input).toHaveValue(DateTime.getYearStartDate(new Date()).formatDate() + " to " + new Date().formatDate())
252
252
  })
253
253
  })
254
+
255
+
256
+ test('displays defaultDate when it is later than maxDate', async () => {
257
+ const testId = 'datepicker-out-of-range-after'
258
+ const futureDateString = '01/15/2020'
259
+ const maxDateString = '01/10/2020'
260
+
261
+ render(
262
+ <DatePicker
263
+ data={{ testid: testId }}
264
+ defaultDate={futureDateString}
265
+ format="m/d/Y"
266
+ maxDate={maxDateString}
267
+ pickerId="date-picker-out-of-range-after"
268
+ />
269
+ )
270
+
271
+ const kit = screen.getByTestId(testId)
272
+ const input = within(kit).getByPlaceholderText('Select Date')
273
+
274
+ await waitFor(() => {
275
+ expect(input).toHaveValue('01/15/2020')
276
+ }, { timeout: 5000 })
277
+ })
254
278
  })
@@ -275,6 +275,80 @@ const datePickerHelper = (config: DatePickerConfig, scrollContainer: string | HT
275
275
 
276
276
  const { setMinDate, setMaxDate } = getMinMaxDates()
277
277
 
278
+ // Default Date + Min/Max Date Initialization Helper Functions section ----/
279
+ const toDateObject = (dateValue: any): Date | null => {
280
+ if (!dateValue) return null
281
+ if (dateValue instanceof Date) return dateValue
282
+ if (typeof dateValue === 'string') {
283
+ const parsed = new Date(dateValue)
284
+ return isNaN(parsed.getTime()) ? null : parsed
285
+ }
286
+ if (typeof dateValue === 'number') {
287
+ return new Date(dateValue)
288
+ }
289
+ return null
290
+ }
291
+
292
+ // Formatting Date for Flatpickr
293
+ const formatDateForFlatpickr = (dateValue: any): string | null => {
294
+ const dateObj = toDateObject(dateValue)
295
+ if (!dateObj) return null
296
+
297
+ const year = dateObj.getFullYear()
298
+ const month = String(dateObj.getMonth() + 1).padStart(2, '0')
299
+ const day = String(dateObj.getDate()).padStart(2, '0')
300
+ return `${year}-${month}-${day}`
301
+ }
302
+
303
+ // Helper to check if defaultDate is earlier than minDate
304
+ const isDefaultDateBeforeMinDate = (defaultDateValue: any, minDateValue: any): boolean => {
305
+ if (!defaultDateValue || !minDateValue) return false
306
+
307
+ const defaultDateObj = toDateObject(defaultDateValue)
308
+ const minDateObj = toDateObject(minDateValue)
309
+
310
+ if (!defaultDateObj || !minDateObj) return false
311
+
312
+ const defaultDateOnly = new Date(defaultDateObj.getFullYear(), defaultDateObj.getMonth(), defaultDateObj.getDate())
313
+ const minDateOnly = new Date(minDateObj.getFullYear(), minDateObj.getMonth(), minDateObj.getDate())
314
+
315
+ return defaultDateOnly < minDateOnly
316
+ }
317
+
318
+ // Helper to check if defaultDate is later than maxDate
319
+ const isDefaultDateAfterMaxDate = (defaultDateValue: any, maxDateValue: any): boolean => {
320
+ if (!defaultDateValue || !maxDateValue) return false
321
+
322
+ const defaultDateObj = toDateObject(defaultDateValue)
323
+ const maxDateObj = toDateObject(maxDateValue)
324
+
325
+ if (!defaultDateObj || !maxDateObj) return false
326
+
327
+ const defaultDateOnly = new Date(defaultDateObj.getFullYear(), defaultDateObj.getMonth(), defaultDateObj.getDate())
328
+ const maxDateOnly = new Date(maxDateObj.getFullYear(), maxDateObj.getMonth(), maxDateObj.getDate())
329
+
330
+ return defaultDateOnly > maxDateOnly
331
+ }
332
+
333
+ const defaultDateValue: any = defaultDateGetter()
334
+ // Only check for and out-of-range if user actually provided minDate/maxDate constraints
335
+ const isBeforeMin = minDate && isDefaultDateBeforeMinDate(defaultDateValue, setMinDate)
336
+ const isAfterMax = maxDate && isDefaultDateAfterMaxDate(defaultDateValue, setMaxDate)
337
+
338
+ // Store these values for use in onClose handler
339
+ const hasOutOfRangeDefault = (isBeforeMin || isAfterMax) && defaultDateValue
340
+
341
+ // Temporarily adjust minDate/maxDate to allow defaultDate to render if it's out of range via user provided minDate/maxDate constraints
342
+ const effectiveMinDate = isBeforeMin && defaultDateValue && minDate
343
+ ? formatDateForFlatpickr(defaultDateValue) || setMinDate
344
+ : setMinDate
345
+
346
+ const effectiveMaxDate = isAfterMax && defaultDateValue && maxDate
347
+ ? formatDateForFlatpickr(defaultDateValue) || setMaxDate
348
+ : setMaxDate
349
+
350
+ // End of Default Date + Min/Max Date Initialization Helper Functions section ----/
351
+
278
352
  flatpickr(`#${pickerId}`, {
279
353
  allowInput,
280
354
  closeOnSelect,
@@ -286,11 +360,32 @@ const datePickerHelper = (config: DatePickerConfig, scrollContainer: string | HT
286
360
  locale: {
287
361
  rangeSeparator: ' to '
288
362
  },
289
- maxDate: setMaxDate,
290
- minDate: setMinDate,
363
+ maxDate: effectiveMaxDate,
364
+ minDate: effectiveMinDate,
291
365
  mode,
292
366
  nextArrow: '<i class="far fa-angle-right"></i>',
293
367
  onOpen: [(_selectedDates, _dateStr, fp) => {
368
+ // If defaultDate was out of range of a dev set min/max date, restore it when calendar opens (in situation where the input was manually cleared or the calendar was closed without selection)
369
+ if (hasOutOfRangeDefault) {
370
+ const dateObj = toDateObject(defaultDateValue)
371
+ if (dateObj) {
372
+ const inputIsBlank = !fp.input.value || fp.input.value.trim() === ''
373
+ const noSelection = !fp.selectedDates || fp.selectedDates.length === 0
374
+
375
+ if (inputIsBlank || noSelection) {
376
+ const formattedDate = fp.formatDate(dateObj, getDateFormat())
377
+ if (formattedDate) {
378
+ fp.input.value = formattedDate
379
+ }
380
+ fp.selectedDates = [dateObj]
381
+ fp.jumpToDate(dateObj)
382
+ setTimeout(() => {
383
+ yearChangeHook(fp)
384
+ }, 0)
385
+ }
386
+ }
387
+ }
388
+
294
389
  calendarResizer()
295
390
  if (resizeRepositionHandlerRef) {
296
391
  window.removeEventListener('resize', resizeRepositionHandlerRef)
@@ -303,12 +398,30 @@ const datePickerHelper = (config: DatePickerConfig, scrollContainer: string | HT
303
398
  if (!staticPosition && scrollContainer) attachToScroll(scrollContainer)
304
399
  positionCalendarIfNeeded(fp)
305
400
  }],
306
- onClose: [(selectedDates, dateStr) => {
401
+ onClose: [(selectedDates, dateStr, fp) => {
307
402
  if (resizeRepositionHandlerRef) {
308
403
  window.removeEventListener('resize', resizeRepositionHandlerRef)
309
404
  resizeRepositionHandlerRef = null
310
405
  }
311
406
  if (!staticPosition && scrollContainer) detachFromScroll(scrollContainer as HTMLElement)
407
+
408
+ // If defaultDate was out of range and no date was selected, preserve the default date
409
+ if (hasOutOfRangeDefault && (!selectedDates || selectedDates.length === 0)) {
410
+ const dateObj = toDateObject(defaultDateValue)
411
+ if (dateObj && fp.input) {
412
+ const formattedDate = fp.formatDate(dateObj, getDateFormat())
413
+ if (formattedDate) {
414
+ setTimeout(() => {
415
+ if (fp.input && (!fp.selectedDates || fp.selectedDates.length === 0)) {
416
+ fp.input.value = formattedDate
417
+ fp.selectedDates = [dateObj]
418
+ fp.jumpToDate(dateObj)
419
+ }
420
+ }, 0)
421
+ }
422
+ }
423
+ }
424
+
312
425
  onClose(selectedDates, dateStr)
313
426
  }],
314
427
  onChange: [(selectedDates, dateStr, fp) => {
@@ -330,6 +443,71 @@ const datePickerHelper = (config: DatePickerConfig, scrollContainer: string | HT
330
443
  const picker = document.querySelector<HTMLElement & { [x: string]: any }>(`#${pickerId}`)._flatpickr
331
444
  picker.innerContainer.parentElement.id = `cal-${pickerId}`
332
445
 
446
+ // If defaultDate was out of range, restore the original minDate/maxDate after initialization (defaultDate displayed, still cannot select dates outside the actual range via user provided minDate/maxDate constraints)
447
+ if ((isBeforeMin || isAfterMax) && defaultDateValue) {
448
+ const dateObj = toDateObject(defaultDateValue)
449
+ const formattedDate = dateObj ? picker.formatDate(dateObj, getDateFormat()) : null
450
+
451
+ setTimeout(() => {
452
+ if (!dateObj || !picker.input || !formattedDate) return
453
+
454
+ picker.setDate(dateObj, false)
455
+
456
+ if (isBeforeMin && setMinDate && minDate) {
457
+ picker.set('minDate', setMinDate)
458
+ }
459
+ if (isAfterMax && setMaxDate && maxDate) {
460
+ picker.set('maxDate', setMaxDate)
461
+ }
462
+ picker.input.value = formattedDate
463
+
464
+ picker.selectedDates = [dateObj]
465
+
466
+ setTimeout(() => {
467
+ yearChangeHook(picker)
468
+ }, 0)
469
+
470
+ // Restore function for out-of-range default dates
471
+ const restoreOutOfRangeValue = () => {
472
+ if (!picker.input) return
473
+
474
+ const inputIsBlank = !picker.input.value || picker.input.value.trim() === ''
475
+ const noSelection = !picker.selectedDates || picker.selectedDates.length === 0
476
+
477
+ if (inputIsBlank || noSelection) {
478
+ setTimeout(() => {
479
+ if (picker.input && (!picker.input.value || picker.input.value.trim() === '')) {
480
+ picker.input.value = formattedDate
481
+ }
482
+ if (!picker.selectedDates || picker.selectedDates.length === 0) {
483
+ picker.selectedDates = [dateObj]
484
+ if (picker.isOpen) {
485
+ picker.jumpToDate(dateObj)
486
+ picker.redraw()
487
+ setTimeout(() => {
488
+ yearChangeHook(picker)
489
+ }, 0)
490
+ }
491
+ }
492
+ }, 0)
493
+ }
494
+ }
495
+
496
+ const originalClear = picker.clear.bind(picker)
497
+ picker.clear = function(...args: any[]) {
498
+ const result = originalClear(...args)
499
+ setTimeout(() => restoreOutOfRangeValue(), 0)
500
+ return result
501
+ }
502
+
503
+ picker.input.addEventListener('input', restoreOutOfRangeValue)
504
+
505
+ picker.config.onClose.push(() => {
506
+ restoreOutOfRangeValue()
507
+ })
508
+ }, 10)
509
+ }
510
+
333
511
  // replace year selector with dropdown
334
512
  picker.yearElements[0].parentElement.innerHTML = `<select class="numInput cur-year" type="number" tabIndex="-1" aria-label="Year" id="year-${pickerId}"></select>`
335
513
 
@@ -1 +1 @@
1
- You can customize the order of the colors you would like to use by using the `colors` prop. Only the data and status colors will work for Playbook charts. See the [design page](https://playbook.powerapp.cloud/token/colors) for reference.
1
+ You can customize the order of the colors you would like to use by using the `colors` prop. Only the data and status colors will work for Playbook charts. See the [design page](https://playbook.powerapp.cloud/tokens/colors) for reference.
@@ -6,10 +6,10 @@ import { GlobalProps, globalProps } from '../../utilities/globalProps'
6
6
  import Card from '../../pb_card/_card'
7
7
 
8
8
  export type FilterBackgroundProps = {
9
- background: boolean,
10
- className: string,
9
+ background?: boolean,
10
+ className?: string,
11
11
  children?: React.ReactChild[] | React.ReactChild,
12
- dark: boolean,
12
+ dark?: boolean,
13
13
  } & GlobalProps
14
14
 
15
15
  const FilterBackground = (props: FilterBackgroundProps): React.ReactElement => {
@@ -29,6 +29,7 @@ type SelectProps = {
29
29
  id?: string,
30
30
  includeBlank?: string,
31
31
  inline?: boolean,
32
+ inputOptions?: {[key: string]: string | number | boolean | (() => void)},
32
33
  label?: string,
33
34
  margin: string,
34
35
  marginBottom: string,
@@ -63,6 +64,7 @@ const Select = ({
63
64
  label,
64
65
  htmlOptions = {},
65
66
  inline = false,
67
+ inputOptions = {},
66
68
  multiple = false,
67
69
  name,
68
70
  onChange = () => undefined,
@@ -94,14 +96,17 @@ const Select = ({
94
96
  const angleDown = getAllIcons()["angleDown"].icon as unknown as { [key: string]: SVGElement }
95
97
 
96
98
  const selectWrapperClass = classnames(buildCss('pb_select_kit_wrapper'), { error }, className)
99
+ const selectId = (inputOptions?.id as string) || name
100
+
97
101
  const selectBody =(() =>{
98
102
  if (children) return children
99
103
  return (
100
104
  <select
101
105
  {...htmlOptions}
102
106
  {...domSafeProps(props)}
107
+ {...inputOptions}
103
108
  disabled={disabled}
104
- id={name}
109
+ id={selectId}
105
110
  multiple={multiple}
106
111
  name={name}
107
112
  onChange={onChange}
@@ -125,7 +130,7 @@ const Select = ({
125
130
  {label &&
126
131
  <label
127
132
  className="pb_select_kit_label"
128
- htmlFor={name}
133
+ htmlFor={selectId}
129
134
  >
130
135
  <Caption
131
136
  dark={props.dark}
@@ -135,7 +140,7 @@ const Select = ({
135
140
  }
136
141
  <label
137
142
  className={selectWrapperClass}
138
- htmlFor={name}
143
+ htmlFor={selectId}
139
144
  >
140
145
  {selectBody}
141
146
  { multiple !== true ?
@@ -0,0 +1,16 @@
1
+ <%= pb_rails("select", props: {
2
+ label: "Favorite Food",
3
+ name: "favorite_food",
4
+ options: [
5
+ { value: "pizza", value_text: "Pizza" },
6
+ { value: "tacos", value_text: "Tacos" },
7
+ { value: "sushi", value_text: "Sushi" }
8
+ ],
9
+ input_options: {
10
+ 'aria-label': "Select your favorite food",
11
+ class: "custom-select-class",
12
+ data: { controller: "search", action: "change->search#filter" },
13
+ id: "favorite-food-select"
14
+ }
15
+ }) %>
16
+
@@ -0,0 +1,30 @@
1
+ import React from 'react'
2
+
3
+ import Select from '../_select'
4
+
5
+ const SelectInputOptions = (props) => {
6
+ const options = [
7
+ { value: 'pizza', text: 'Pizza' },
8
+ { value: 'tacos', text: 'Tacos' },
9
+ { value: 'sushi', text: 'Sushi' },
10
+ ]
11
+
12
+ return (
13
+ <>
14
+ <Select
15
+ inputOptions={{
16
+ 'aria-label': 'Select your favorite food',
17
+ className: 'custom-select-class',
18
+ id: 'favorite-food-select',
19
+ }}
20
+ label="Favorite Food"
21
+ name="favorite_food"
22
+ options={options}
23
+ {...props}
24
+ />
25
+ </>
26
+ )
27
+ }
28
+
29
+ export default SelectInputOptions
30
+
@@ -0,0 +1 @@
1
+ Use the `input_options` / `inputOptions` prop to pass additional attributes directly to the underlying `<select>` element instead of the outer wrapper. This is useful for applying data attributes, custom IDs, or other HTML attributes that need to be on the select element itself.
@@ -15,6 +15,7 @@ examples:
15
15
  - select_inline_compact: Select Inline Compact
16
16
  - select_attributes: Select W/ Attributes
17
17
  - select_multiple: Select Multiple
18
+ - select_input_options: Input Options
18
19
 
19
20
 
20
21
 
@@ -33,6 +34,7 @@ examples:
33
34
  - select_inline_compact: Select Inline Compact
34
35
  - select_multiple: Select Multiple
35
36
  - select_react_hook: React Hook
37
+ - select_input_options: Input Options
36
38
 
37
39
  swift:
38
40
  - select_default_swift: Default
@@ -12,3 +12,4 @@ export { default as SelectInlineCompact } from './_select_inline_compact.jsx'
12
12
  export { default as SelectMultiple } from './_select_multiple.jsx'
13
13
  export { default as SelectReactHook } from './_select_react_hook.jsx'
14
14
  export { default as SelectCustomSelectSubheaders } from './_select_custom_select_subheaders.jsx'
15
+ export { default as SelectInputOptions } from './_select_input_options.jsx'
@@ -2,11 +2,11 @@
2
2
  id: nil,
3
3
  class: object.classnames ) do %>
4
4
  <% if object.label %>
5
- <label class="pb_select_kit_label" for="<%= object.name %>">
5
+ <label class="pb_select_kit_label" for="<%= object.input_options[:id] || object.name %>">
6
6
  <%= pb_rails("caption", props: { text: object.label, dark: object.dark }) %>
7
7
  </label>
8
8
  <% end %>
9
- <label class="<%= object.select_wrapper_class %>" for="<%= object.name %>">
9
+ <label class="<%= object.select_wrapper_class %>" for="<%= object.input_options[:id] || object.name %>">
10
10
  <% if content.present? %>
11
11
  <%= content %>
12
12
  <%= pb_rails("body", props: { status: "negative", text: object.error }) %>
@@ -14,6 +14,8 @@ module Playbook
14
14
  prop :error
15
15
  prop :include_blank
16
16
  prop :inline, type: Playbook::Props::Boolean, default: false
17
+ prop :input_options, type: Playbook::Props::HashProp,
18
+ default: {}
17
19
  prop :label
18
20
  prop :multiple, type: Playbook::Props::Boolean, default: false
19
21
  prop :name
@@ -38,7 +40,7 @@ module Playbook
38
40
  multiple: multiple,
39
41
  onchange: onchange,
40
42
  include_blank: include_blank,
41
- }.merge(attributes)
43
+ }.merge(attributes).merge(input_options)
42
44
  end
43
45
 
44
46
  def classname
@@ -65,4 +65,27 @@ test('returns multiple variant', () => {
65
65
  const selectElement = kit.querySelector('select');
66
66
 
67
67
  expect(selectElement).toHaveAttribute('multiple', '');
68
+ });
69
+
70
+ test('inputOptions are passed to select element', () => {
71
+ render(
72
+ <Select
73
+ data={{ testid: testId }}
74
+ inputOptions={{
75
+ id: 'custom-select-id',
76
+ className: 'custom-select-class',
77
+ 'aria-label': 'Custom aria label',
78
+ }}
79
+ label="Favorite Food"
80
+ name="food"
81
+ options={options}
82
+ />
83
+ )
84
+
85
+ const kit = screen.getByTestId(testId)
86
+ const selectElement = kit.querySelector('select')
87
+
88
+ expect(selectElement).toHaveAttribute('id', 'custom-select-id')
89
+ expect(selectElement).toHaveClass('custom-select-class')
90
+ expect(selectElement).toHaveAttribute('aria-label', 'Custom aria label')
68
91
  });