playbook_ui 15.0.0.pre.alpha.PLAY2419dialogaccessibility10324 → 15.0.0.pre.alpha.PLAY2420atfirstcolumnborderfix10439
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.
- checksums.yaml +4 -4
- data/app/pb_kits/playbook/pb_advanced_table/_advanced_table.scss +45 -6
- data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_headers_vertical_border.html.erb +4 -2
- data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_headers_vertical_border.jsx +12 -8
- data/app/pb_kits/playbook/pb_circle_icon_button/_circle_icon_button.tsx +2 -0
- data/app/pb_kits/playbook/pb_circle_icon_button/circle_icon_button.html.erb +1 -0
- data/app/pb_kits/playbook/pb_date_picker/date_picker_helper.ts +48 -4
- data/app/pb_kits/playbook/pb_dialog/_dialog.tsx +5 -7
- data/app/pb_kits/playbook/pb_loading_inline/_loading_inline.tsx +3 -1
- data/app/pb_kits/playbook/pb_loading_inline/docs/_loading_inline_variant.html.erb +5 -0
- data/app/pb_kits/playbook/pb_loading_inline/docs/_loading_inline_variant.jsx +24 -0
- data/app/pb_kits/playbook/pb_loading_inline/docs/example.yml +2 -0
- data/app/pb_kits/playbook/pb_loading_inline/docs/index.js +1 -0
- data/app/pb_kits/playbook/pb_loading_inline/loading_inline.html.erb +1 -1
- data/app/pb_kits/playbook/pb_loading_inline/loading_inline.rb +11 -0
- data/app/pb_kits/playbook/pb_text_input/_text_input.tsx +3 -0
- data/app/pb_kits/playbook/pb_text_input/docs/_text_input_autocomplete.html.erb +41 -0
- data/app/pb_kits/playbook/pb_text_input/docs/_text_input_autocomplete.jsx +80 -0
- data/app/pb_kits/playbook/pb_text_input/docs/_text_input_autocomplete.md +1 -0
- data/app/pb_kits/playbook/pb_text_input/docs/example.yml +3 -0
- data/app/pb_kits/playbook/pb_text_input/docs/index.js +1 -0
- data/app/pb_kits/playbook/pb_text_input/text_input.rb +2 -3
- data/app/pb_kits/playbook/pb_text_input/text_input.test.js +38 -0
- data/dist/chunks/{_line_graph-pjRvO-Xb.js → _line_graph-B-n_nsLR.js} +1 -1
- data/dist/chunks/{_typeahead-CZo22jaG.js → _typeahead-CS9PvM1x.js} +2 -2
- data/dist/chunks/_weekday_stacked-DaFBilv5.js +37 -0
- data/dist/chunks/{lib-CY5ZPzic.js → lib-BTs5acfO.js} +1 -1
- data/dist/chunks/{pb_form_validation-D3b0JKHH.js → pb_form_validation-CKkaQFX3.js} +1 -1
- data/dist/chunks/vendor.js +1 -1
- data/dist/playbook-doc.js +2 -2
- data/dist/playbook-rails-react-bindings.js +1 -1
- data/dist/playbook-rails.js +1 -1
- data/dist/playbook.css +1 -1
- data/lib/playbook/version.rb +1 -1
- metadata +12 -8
- data/app/pb_kits/playbook/pb_dialog/dialogHelper.js +0 -65
- data/dist/chunks/_weekday_stacked-CXuGHKcc.js +0 -37
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 5c0850e82d5491835ef48bbdd5e1c28d4ef6948080093b74dc4aba06cfaf637e
         | 
| 4 | 
            +
              data.tar.gz: b494a2a9849173a0e049360489d2b37418f50dd33d151eafc5f0c588de2dbcca
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: d1c327736134be2fb192d676143d5904179510c97a8fd6560d293efbcc90497c9c41bcb7d4d309bf82844286dd640811749ce7f9384982cbe9aedaa6f029230e
         | 
| 7 | 
            +
              data.tar.gz: 2376428cb935232f12a97496c049bb652e9eef97e5ee39ec1b9aea1b7466f81c10b11f08f71ee36ba33de613fbb44ca22a1842114b2a2d841462f96ca5d8a040
         | 
| @@ -1011,18 +1011,57 @@ | |
| 1011 1011 | 
             
            .pb-advanced-table-popover-option:hover {
         | 
| 1012 1012 | 
             
              background-color: $bg_light;
         | 
| 1013 1013 | 
             
            }
         | 
| 1014 | 
            +
             | 
| 1014 1015 | 
             
            // Removes borders when Wrapped inside the Card Kit
         | 
| 1015 | 
            -
            .pb_card_kit > .pb_advanced_table  | 
| 1016 | 
            -
             | 
| 1017 | 
            -
            . | 
| 1018 | 
            -
            . | 
| 1016 | 
            +
            .pb_card_kit > .pb_advanced_table {
         | 
| 1017 | 
            +
              // Remove right border only from the actual last column
         | 
| 1018 | 
            +
              .pb_advanced_table_body tr td:last-child,
         | 
| 1019 | 
            +
              .pb_advanced_table_header tr th:last-child {
         | 
| 1020 | 
            +
                box-shadow: none !important; // prevents double up of borders on Firefox when in Card Kit
         | 
| 1019 1021 | 
             
                border-right: none !important;
         | 
| 1022 | 
            +
              }
         | 
| 1023 | 
            +
             | 
| 1024 | 
            +
              // Without vertical border, remove all last-cell borders
         | 
| 1025 | 
            +
              .pb_table:not(.vertical-border) {
         | 
| 1026 | 
            +
                .pb_advanced_table_body .last-cell,
         | 
| 1027 | 
            +
                .pb_advanced_table_header .last-header-cell {
         | 
| 1028 | 
            +
                  box-shadow: none !important;
         | 
| 1029 | 
            +
                  border-right: none !important;
         | 
| 1030 | 
            +
                }
         | 
| 1031 | 
            +
              }
         | 
| 1032 | 
            +
             | 
| 1033 | 
            +
              // For tables WITH vertical borders, only remove border from actual last column
         | 
| 1034 | 
            +
              .pb_table.vertical-border {
         | 
| 1035 | 
            +
                .pb_advanced_table_body .last-cell:not(:last-child),
         | 
| 1036 | 
            +
                .pb_advanced_table_header .last-header-cell:not(:last-child) {
         | 
| 1037 | 
            +
                  box-shadow: none !important;
         | 
| 1038 | 
            +
                  border-right: 1px solid $border_light !important;
         | 
| 1039 | 
            +
                }
         | 
| 1040 | 
            +
             | 
| 1041 | 
            +
                // Dark mode
         | 
| 1042 | 
            +
                &.dark {
         | 
| 1043 | 
            +
                  .pb_advanced_table_body .last-cell:not(:last-child),
         | 
| 1044 | 
            +
                  .pb_advanced_table_header .last-header-cell:not(:last-child) {
         | 
| 1045 | 
            +
                    box-shadow: none !important;
         | 
| 1046 | 
            +
                    border-right: 1px solid $border_dark !important;
         | 
| 1047 | 
            +
                  }
         | 
| 1048 | 
            +
                }
         | 
| 1049 | 
            +
             | 
| 1050 | 
            +
                // Support border colors
         | 
| 1051 | 
            +
                &.pb_advanced_table[class*="column-group-border-"] {
         | 
| 1052 | 
            +
                  .pb_advanced_table_body .last-cell:not(:last-child),
         | 
| 1053 | 
            +
                  .pb_advanced_table_header .last-header-cell:not(:last-child) {
         | 
| 1054 | 
            +
                    box-shadow: none !important;
         | 
| 1055 | 
            +
                    border-right: 1px solid var(--column-border-color) !important;
         | 
| 1056 | 
            +
                  }
         | 
| 1057 | 
            +
                }
         | 
| 1058 | 
            +
              }
         | 
| 1020 1059 | 
             
            }
         | 
| 1021 1060 |  | 
| 1022 1061 | 
             
            //  Removes borders when Wrapped inside the Card Kit
         | 
| 1023 | 
            -
            .pb_card_kit > .pb_advanced_table tr:last-child, | 
| 1062 | 
            +
            .pb_card_kit > .pb_advanced_table tr:last-child,
         | 
| 1024 1063 | 
             
            .pb_card_kit > .pb_advanced_table .last-row-cell {
         | 
| 1025 1064 | 
             
              td {
         | 
| 1026 | 
            -
                border-bottom: none !important; | 
| 1065 | 
            +
                border-bottom: none !important;
         | 
| 1027 1066 | 
             
              }
         | 
| 1028 1067 | 
             
            }
         | 
| @@ -39,5 +39,7 @@ | |
| 39 39 | 
             
                ],
         | 
| 40 40 | 
             
              },
         | 
| 41 41 | 
             
            ] %>
         | 
| 42 | 
            -
             | 
| 43 | 
            -
            <%= pb_rails(" | 
| 42 | 
            +
            <%# Adjusting for test purposes. Will revert prior to commit. %>
         | 
| 43 | 
            +
            <%= pb_rails("card", props: {})  do %>
         | 
| 44 | 
            +
              <%= pb_rails("advanced_table", props: { id: "table_multi_headers_vertical_borders", table_data: @table_data, column_definitions: column_definitions, table_props: { container: false, vertical_border: true } }) %>
         | 
| 45 | 
            +
            <% end %>
         | 
    
        data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_headers_vertical_border.jsx
    CHANGED
    
    | @@ -1,4 +1,5 @@ | |
| 1 1 | 
             
            import React from "react"
         | 
| 2 | 
            +
            import Card from "../../pb_card/_card"
         | 
| 2 3 | 
             
            import AdvancedTable from '../../pb_advanced_table/_advanced_table'
         | 
| 3 4 | 
             
            import MOCK_DATA from "./advanced_table_mock_data.json"
         | 
| 4 5 |  | 
| @@ -44,19 +45,22 @@ const AdvancedTableColumnHeadersVerticalBorder = (props) => { | |
| 44 45 | 
             
                  ],
         | 
| 45 46 | 
             
                },
         | 
| 46 47 | 
             
              ];
         | 
| 47 | 
            -
             | 
| 48 | 
            +
             | 
| 48 49 | 
             
              const tableProps = {
         | 
| 50 | 
            +
                container: false,
         | 
| 49 51 | 
             
                verticalBorder: true
         | 
| 50 52 | 
             
              }
         | 
| 51 | 
            -
             | 
| 53 | 
            +
            // Adjusted for testing purposes. Will revert prior to commit.
         | 
| 52 54 | 
             
              return (
         | 
| 53 55 | 
             
                <>
         | 
| 54 | 
            -
                  < | 
| 55 | 
            -
             | 
| 56 | 
            -
             | 
| 57 | 
            -
             | 
| 58 | 
            -
             | 
| 59 | 
            -
             | 
| 56 | 
            +
                  <Card>
         | 
| 57 | 
            +
                    <AdvancedTable
         | 
| 58 | 
            +
                        columnDefinitions={columnDefinitions}
         | 
| 59 | 
            +
                        tableData={MOCK_DATA}
         | 
| 60 | 
            +
                        tableProps={tableProps}
         | 
| 61 | 
            +
                        {...props}
         | 
| 62 | 
            +
                    />
         | 
| 63 | 
            +
                  </Card>
         | 
| 60 64 | 
             
                </>
         | 
| 61 65 | 
             
              )
         | 
| 62 66 | 
             
            }
         | 
| @@ -65,6 +65,7 @@ const CircleIconButton = (props: CircleIconButtonProps): React.ReactElement => { | |
| 65 65 | 
             
                    id={id}
         | 
| 66 66 | 
             
                >
         | 
| 67 67 | 
             
                  <Button
         | 
| 68 | 
            +
                      aria={aria}
         | 
| 68 69 | 
             
                      dark={dark}
         | 
| 69 70 | 
             
                      disabled={disabled}
         | 
| 70 71 | 
             
                      htmlType={type}
         | 
| @@ -78,6 +79,7 @@ const CircleIconButton = (props: CircleIconButtonProps): React.ReactElement => { | |
| 78 79 | 
             
                  >
         | 
| 79 80 | 
             
                    <Icon
         | 
| 80 81 | 
             
                        fixedWidth
         | 
| 82 | 
            +
                        htmlOptions={{'aria-hidden': true}}
         | 
| 81 83 | 
             
                        icon={icon}
         | 
| 82 84 | 
             
                    />
         | 
| 83 85 | 
             
                  </Button>
         | 
| @@ -89,6 +89,48 @@ const datePickerHelper = (config: DatePickerConfig, scrollContainer: string | HT | |
| 89 89 | 
             
                }
         | 
| 90 90 | 
             
              }
         | 
| 91 91 |  | 
| 92 | 
            +
              // Helper function to get min/max years based on yearRange. If minDate/maxDate provided, grab year from those values
         | 
| 93 | 
            +
              const getMinMaxYears = () => {
         | 
| 94 | 
            +
                const [minYear, maxYear] = yearRange;
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                const extractYear = (dateOption: typeof minDate | typeof maxDate): number | null => {
         | 
| 97 | 
            +
                  if (!dateOption) return null;
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                  // If it's already a number, assume it's a year
         | 
| 100 | 
            +
                  if (typeof dateOption === 'number') {
         | 
| 101 | 
            +
                    return dateOption;
         | 
| 102 | 
            +
                  }
         | 
| 103 | 
            +
             | 
| 104 | 
            +
                  // If it's a string, extract year with regex
         | 
| 105 | 
            +
                  if (typeof dateOption === 'string') {
         | 
| 106 | 
            +
                    const match = dateOption.match(/\b(19|20)\d{2}\b/);
         | 
| 107 | 
            +
                    return match ? parseInt(match[0], 10) : null;
         | 
| 108 | 
            +
                  }
         | 
| 109 | 
            +
             | 
| 110 | 
            +
                  // If it's a Date object, get the year directly
         | 
| 111 | 
            +
                  if (dateOption instanceof Date) {
         | 
| 112 | 
            +
                    return dateOption.getFullYear();
         | 
| 113 | 
            +
                  }
         | 
| 114 | 
            +
             | 
| 115 | 
            +
                  return null;
         | 
| 116 | 
            +
                };
         | 
| 117 | 
            +
             | 
| 118 | 
            +
                const setMinYear = minDate ? (extractYear(minDate) ?? minYear) : minYear;
         | 
| 119 | 
            +
                const setMaxYear = maxDate ? (extractYear(maxDate) ?? maxYear) : maxYear;
         | 
| 120 | 
            +
             | 
| 121 | 
            +
                return { setMinYear, setMaxYear };
         | 
| 122 | 
            +
              };
         | 
| 123 | 
            +
             | 
| 124 | 
            +
              const { setMinYear, setMaxYear } = getMinMaxYears()
         | 
| 125 | 
            +
             | 
| 126 | 
            +
              // Helper function to get min/max dates based on yearRange
         | 
| 127 | 
            +
              const getMinMaxDates = () => {
         | 
| 128 | 
            +
                const setMinDate = minDate || `01/01/${setMinYear}`
         | 
| 129 | 
            +
                const setMaxDate = maxDate || `12/31/${setMaxYear}`
         | 
| 130 | 
            +
             | 
| 131 | 
            +
                return { setMinDate, setMaxDate }
         | 
| 132 | 
            +
              }
         | 
| 133 | 
            +
             | 
| 92 134 | 
             
              const disabledWeekDays = () => {
         | 
| 93 135 | 
             
                return (
         | 
| 94 136 | 
             
                  [
         | 
| @@ -201,6 +243,8 @@ const datePickerHelper = (config: DatePickerConfig, scrollContainer: string | HT | |
| 201 243 | 
             
              // |             Flatpickr initializer w/ config             |
         | 
| 202 244 | 
             
              // ===========================================================
         | 
| 203 245 |  | 
| 246 | 
            +
              const { setMinDate, setMaxDate } = getMinMaxDates()
         | 
| 247 | 
            +
             | 
| 204 248 | 
             
              flatpickr(`#${pickerId}`, {
         | 
| 205 249 | 
             
                allowInput,
         | 
| 206 250 | 
             
                closeOnSelect,
         | 
| @@ -212,8 +256,8 @@ const datePickerHelper = (config: DatePickerConfig, scrollContainer: string | HT | |
| 212 256 | 
             
                locale: {
         | 
| 213 257 | 
             
                  rangeSeparator: ' to '
         | 
| 214 258 | 
             
                },
         | 
| 215 | 
            -
                maxDate,
         | 
| 216 | 
            -
                minDate,
         | 
| 259 | 
            +
                maxDate: setMaxDate,
         | 
| 260 | 
            +
                minDate: setMinDate,
         | 
| 217 261 | 
             
                mode,
         | 
| 218 262 | 
             
                nextArrow: '<i class="far fa-angle-right"></i>',
         | 
| 219 263 | 
             
                onOpen: [() => {
         | 
| @@ -250,7 +294,7 @@ const datePickerHelper = (config: DatePickerConfig, scrollContainer: string | HT | |
| 250 294 |  | 
| 251 295 | 
             
              // create html option tags for desired years
         | 
| 252 296 | 
             
              let years = ''
         | 
| 253 | 
            -
              for (let year =  | 
| 297 | 
            +
              for (let year = setMaxYear; year >= setMinYear; year--) {
         | 
| 254 298 | 
             
                years += `<option value="${year}">${year}</option>`
         | 
| 255 299 | 
             
              }
         | 
| 256 300 |  | 
| @@ -323,7 +367,7 @@ const datePickerHelper = (config: DatePickerConfig, scrollContainer: string | HT | |
| 323 367 | 
             
              }
         | 
| 324 368 | 
             
            // === End of Automatic Sync Logic ===
         | 
| 325 369 |  | 
| 326 | 
            -
             | 
| 370 | 
            +
             | 
| 327 371 | 
             
              // Adding dropdown icons to year and month select
         | 
| 328 372 | 
             
              dropdown.insertAdjacentHTML('afterend', `<i class="year-dropdown-icon">${angleDown}</i>`)
         | 
| 329 373 | 
             
              if (picker.monthElements[0].parentElement) {
         | 
| @@ -73,8 +73,8 @@ const Dialog = (props: DialogProps): React.ReactElement => { | |
| 73 73 | 
             
              const ariaProps = buildAriaProps(aria);
         | 
| 74 74 | 
             
               const dataProps = buildDataProps(data)
         | 
| 75 75 | 
             
               const htmlProps = buildHtmlProps(htmlOptions);
         | 
| 76 | 
            -
              const  | 
| 77 | 
            -
                base: classnames("pb_dialog", buildCss("pb_dialog", size, placement)),
         | 
| 76 | 
            +
              const classes = {
         | 
| 77 | 
            +
                base: classnames("pb_dialog", buildCss("pb_dialog", size, placement), globalProps(props), className),
         | 
| 78 78 | 
             
                afterOpen: "pb_dialog_after_open",
         | 
| 79 79 | 
             
                beforeClose: "pb_dialog_before_close",
         | 
| 80 80 | 
             
              };
         | 
| @@ -93,10 +93,8 @@ const Dialog = (props: DialogProps): React.ReactElement => { | |
| 93 93 |  | 
| 94 94 | 
             
              const dynamicInlineProps = globalInlineProps(props); 
         | 
| 95 95 |  | 
| 96 | 
            -
              const  | 
| 96 | 
            +
              const wrapperClasses = classnames(
         | 
| 97 97 | 
             
                buildCss("pb_dialog_wrapper"),
         | 
| 98 | 
            -
                globalProps(props),
         | 
| 99 | 
            -
                className
         | 
| 100 98 | 
             
              );
         | 
| 101 99 |  | 
| 102 100 | 
             
              const [triggerOpened, setTriggerOpened] = useState(false),
         | 
| @@ -173,11 +171,11 @@ const Dialog = (props: DialogProps): React.ReactElement => { | |
| 173 171 | 
             
                      {...ariaProps} 
         | 
| 174 172 | 
             
                      {...dataProps}
         | 
| 175 173 | 
             
                      {...htmlProps} 
         | 
| 176 | 
            -
                      className={ | 
| 174 | 
            +
                      className={wrapperClasses}
         | 
| 177 175 | 
             
                  >
         | 
| 178 176 | 
             
                    <Modal
         | 
| 179 177 | 
             
                        ariaHideApp={false}
         | 
| 180 | 
            -
                        className={ | 
| 178 | 
            +
                        className={classes}
         | 
| 181 179 | 
             
                        closeTimeoutMS={200}
         | 
| 182 180 | 
             
                        contentLabel="Dialog"
         | 
| 183 181 | 
             
                        id={id}
         | 
| @@ -16,6 +16,7 @@ type LoadingInlineProps = { | |
| 16 16 | 
             
              htmlOptions?: {[key: string]: string | number | boolean | (() => void)},
         | 
| 17 17 | 
             
              id?: string,
         | 
| 18 18 | 
             
              text?: string,
         | 
| 19 | 
            +
              variant?: "dotted" | "solid" ,
         | 
| 19 20 | 
             
            }
         | 
| 20 21 |  | 
| 21 22 | 
             
            const LoadingInline = (props: LoadingInlineProps) => {
         | 
| @@ -28,6 +29,7 @@ const LoadingInline = (props: LoadingInlineProps) => { | |
| 28 29 | 
             
                htmlOptions = {},
         | 
| 29 30 | 
             
                id,
         | 
| 30 31 | 
             
                text = ' Loading',
         | 
| 32 | 
            +
                variant = 'dotted',
         | 
| 31 33 | 
             
              } = props
         | 
| 32 34 |  | 
| 33 35 | 
             
              const ariaProps = buildAriaProps(aria)
         | 
| @@ -54,7 +56,7 @@ const LoadingInline = (props: LoadingInlineProps) => { | |
| 54 56 | 
             
                    <Icon
         | 
| 55 57 | 
             
                        aria={{ label: 'loading icon' }}
         | 
| 56 58 | 
             
                        fixedWidth
         | 
| 57 | 
            -
                        icon= | 
| 59 | 
            +
                        icon={variant === 'dotted' ? 'spinner' : variant === 'solid' ? 'spinner-solid' : undefined}
         | 
| 58 60 | 
             
                        pulse
         | 
| 59 61 | 
             
                    />
         | 
| 60 62 | 
             
                    {text}
         | 
| @@ -0,0 +1,24 @@ | |
| 1 | 
            +
            import React from 'react'
         | 
| 2 | 
            +
            import { LoadingInline } from 'playbook-ui'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            const LoadingInlineVariant = (props) => {
         | 
| 5 | 
            +
              return (
         | 
| 6 | 
            +
                <div>
         | 
| 7 | 
            +
                  <LoadingInline 
         | 
| 8 | 
            +
                      text=" Dotted Spinner" 
         | 
| 9 | 
            +
                      variant="dotted" 
         | 
| 10 | 
            +
                      {...props}
         | 
| 11 | 
            +
                  />
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  <br />
         | 
| 14 | 
            +
              
         | 
| 15 | 
            +
                  <LoadingInline
         | 
| 16 | 
            +
                      text=" Solid Spinner"
         | 
| 17 | 
            +
                      variant="solid"
         | 
| 18 | 
            +
                      {...props}
         | 
| 19 | 
            +
                  />
         | 
| 20 | 
            +
                </div>
         | 
| 21 | 
            +
              )
         | 
| 22 | 
            +
            }
         | 
| 23 | 
            +
             | 
| 24 | 
            +
            export default LoadingInlineVariant
         | 
| @@ -3,9 +3,11 @@ examples: | |
| 3 3 | 
             
              rails:
         | 
| 4 4 | 
             
              - loading_inline_default: Default
         | 
| 5 5 | 
             
              - loading_inline_custom: Custom Text
         | 
| 6 | 
            +
              - loading_inline_variant: Variant
         | 
| 6 7 |  | 
| 7 8 |  | 
| 8 9 |  | 
| 9 10 | 
             
              react:
         | 
| 10 11 | 
             
              - loading_inline_default: Default
         | 
| 11 12 | 
             
              - loading_inline_custom: Custom Text
         | 
| 13 | 
            +
              - loading_inline_variant: Variant
         | 
| @@ -1,6 +1,6 @@ | |
| 1 1 | 
             
            <%= pb_content_tag do %>
         | 
| 2 2 |  | 
| 3 3 | 
             
              <%= pb_rails("body", props: { color: "light", dark: object.dark }) do %>
         | 
| 4 | 
            -
                <%= pb_rails("icon", props: { aria: { label: "loading icon" }, fixed_width: true, icon:  | 
| 4 | 
            +
                <%= pb_rails("icon", props: { aria: { label: "loading icon" }, fixed_width: true, icon: object.spinner_icon, pulse: true }) %> <%= object.text %>
         | 
| 5 5 | 
             
              <% end %>
         | 
| 6 6 | 
             
            <% end %>
         | 
| @@ -8,10 +8,21 @@ module Playbook | |
| 8 8 | 
             
                               default: "left"
         | 
| 9 9 | 
             
                  prop :dark, type: Playbook::Props::Boolean, default: false
         | 
| 10 10 | 
             
                  prop :text, type: Playbook::Props::String, default: "Loading"
         | 
| 11 | 
            +
                  prop :variant, type: Playbook::Props::Enum,
         | 
| 12 | 
            +
                                 values: %w[dotted solid],
         | 
| 13 | 
            +
                                 default: "dotted"
         | 
| 11 14 |  | 
| 12 15 | 
             
                  def classname
         | 
| 13 16 | 
             
                    generate_classname("pb_loading_inline_kit", align)
         | 
| 14 17 | 
             
                  end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  def spinner_icon
         | 
| 20 | 
            +
                    if variant == "dotted"
         | 
| 21 | 
            +
                      "spinner"
         | 
| 22 | 
            +
                    elsif variant == "solid"
         | 
| 23 | 
            +
                      "spinner-solid"
         | 
| 24 | 
            +
                    end
         | 
| 25 | 
            +
                  end
         | 
| 15 26 | 
             
                end
         | 
| 16 27 | 
             
              end
         | 
| 17 28 | 
             
            end
         | 
| @@ -36,6 +36,7 @@ type TextInputProps = { | |
| 36 36 | 
             
                alignment?: "right" | "left",
         | 
| 37 37 | 
             
                border?: boolean,
         | 
| 38 38 | 
             
              },
         | 
| 39 | 
            +
              autoComplete?: boolean | string,
         | 
| 39 40 | 
             
            } & GlobalProps
         | 
| 40 41 |  | 
| 41 42 | 
             
            const TextInput = (props: TextInputProps, ref: React.LegacyRef<HTMLInputElement>) => {
         | 
| @@ -59,6 +60,7 @@ const TextInput = (props: TextInputProps, ref: React.LegacyRef<HTMLInputElement> | |
| 59 60 | 
             
                type = 'text',
         | 
| 60 61 | 
             
                value = '',
         | 
| 61 62 | 
             
                children = null,
         | 
| 63 | 
            +
                autoComplete = true,
         | 
| 62 64 | 
             
              } = props
         | 
| 63 65 |  | 
| 64 66 | 
             
              const ariaProps = buildAriaProps(aria)
         | 
| @@ -142,6 +144,7 @@ const TextInput = (props: TextInputProps, ref: React.LegacyRef<HTMLInputElement> | |
| 142 144 | 
             
                childInput ? React.cloneElement(children, { className: "text_input" }) :
         | 
| 143 145 | 
             
                (<input
         | 
| 144 146 | 
             
                    {...domSafeProps(props)}
         | 
| 147 | 
            +
                    autoComplete={typeof autoComplete === "string" ? autoComplete : ( autoComplete ? undefined : "off" )}
         | 
| 145 148 | 
             
                    className="text_input"
         | 
| 146 149 | 
             
                    disabled={disabled}
         | 
| 147 150 | 
             
                    id={id}
         | 
| @@ -0,0 +1,41 @@ | |
| 1 | 
            +
            <%= pb_rails("text_input", props: {
         | 
| 2 | 
            +
              autocomplete: false,
         | 
| 3 | 
            +
              label: "autocomplete='off'",
         | 
| 4 | 
            +
              name: "firstName",
         | 
| 5 | 
            +
              placeholder: "Enter first name",
         | 
| 6 | 
            +
            }) %>
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            <%= pb_rails("text_input", props: {
         | 
| 9 | 
            +
              label: "no autocomplete attribute (let browser decide- basically 'on')",
         | 
| 10 | 
            +
              name: "lastName",
         | 
| 11 | 
            +
              placeholder: "Enter last name"
         | 
| 12 | 
            +
            }) %>
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            <%= pb_rails("text_input", props: {
         | 
| 15 | 
            +
                autocomplete: true,
         | 
| 16 | 
            +
                label: "autocomplete='on'",
         | 
| 17 | 
            +
                name: "phone",
         | 
| 18 | 
            +
                type: "phone",
         | 
| 19 | 
            +
                placeholder: "Enter phone number"
         | 
| 20 | 
            +
            }) %>
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            <%= pb_rails("body", props: { margin_bottom: "sm" }) do %>
         | 
| 23 | 
            +
              The following have the same autocomplete attributes (email), but have
         | 
| 24 | 
            +
              different name attributes (email and emailAlt). Many browsers will
         | 
| 25 | 
            +
              open autocomplete based on name attributes instead of autocomplete:
         | 
| 26 | 
            +
            <% end %>
         | 
| 27 | 
            +
             | 
| 28 | 
            +
            <%= pb_rails("text_input", props: {
         | 
| 29 | 
            +
                autocomplete: "email",
         | 
| 30 | 
            +
                label: "autocomplete='email' name='email'",
         | 
| 31 | 
            +
                name: "email",
         | 
| 32 | 
            +
                placeholder: "Enter email address"
         | 
| 33 | 
            +
            }) %>
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            <%= pb_rails("text_input", props: {
         | 
| 36 | 
            +
                autocomplete: "email",
         | 
| 37 | 
            +
                label: "autocomplete='email' name='emailAlt'",
         | 
| 38 | 
            +
                name: "emailAlt",
         | 
| 39 | 
            +
                type: "email",
         | 
| 40 | 
            +
                placeholder: "Enter email address"
         | 
| 41 | 
            +
            }) %>
         | 
| @@ -0,0 +1,80 @@ | |
| 1 | 
            +
            import React, { useState } from 'react'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            import TextInput from '../../pb_text_input/_text_input'
         | 
| 4 | 
            +
            import Body from '../../pb_body/_body'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
             | 
| 7 | 
            +
            const TextInputAutocomplete = (props) => {
         | 
| 8 | 
            +
              const [formFields, setFormFields] = useState({
         | 
| 9 | 
            +
                firstName: "",
         | 
| 10 | 
            +
                lastName: "",
         | 
| 11 | 
            +
                phone: "",
         | 
| 12 | 
            +
                emailTest: "",
         | 
| 13 | 
            +
                email: "",
         | 
| 14 | 
            +
              });
         | 
| 15 | 
            +
             | 
| 16 | 
            +
              const handleOnChangeFormField = ({ target }) => {
         | 
| 17 | 
            +
                const { name, value } = target;
         | 
| 18 | 
            +
                setFormFields({ ...formFields, [name]: value });
         | 
| 19 | 
            +
              };
         | 
| 20 | 
            +
             | 
| 21 | 
            +
              return (
         | 
| 22 | 
            +
                <div>
         | 
| 23 | 
            +
                  <TextInput
         | 
| 24 | 
            +
                      autoComplete={false}
         | 
| 25 | 
            +
                      label="autocomplete='off'"
         | 
| 26 | 
            +
                      name="firstName"
         | 
| 27 | 
            +
                      onChange={handleOnChangeFormField}
         | 
| 28 | 
            +
                      placeholder="Enter first name"
         | 
| 29 | 
            +
                      value={formFields.firstName}
         | 
| 30 | 
            +
                      {...props}
         | 
| 31 | 
            +
                  />
         | 
| 32 | 
            +
                  <TextInput
         | 
| 33 | 
            +
                      label="no autocomplete attribute (let browser decide- basically 'on')"
         | 
| 34 | 
            +
                      name="lastName"
         | 
| 35 | 
            +
                      onChange={handleOnChangeFormField}
         | 
| 36 | 
            +
                      placeholder="Enter last name"
         | 
| 37 | 
            +
                      value={formFields.lastName}
         | 
| 38 | 
            +
                      {...props}
         | 
| 39 | 
            +
                  />
         | 
| 40 | 
            +
                  <TextInput
         | 
| 41 | 
            +
                      autoComplete
         | 
| 42 | 
            +
                      label="autocomplete='on'"
         | 
| 43 | 
            +
                      name="phone"
         | 
| 44 | 
            +
                      onChange={handleOnChangeFormField}
         | 
| 45 | 
            +
                      placeholder="Enter phone number"
         | 
| 46 | 
            +
                      type="phone"
         | 
| 47 | 
            +
                      value={formFields.phone}
         | 
| 48 | 
            +
                      {...props}
         | 
| 49 | 
            +
                  />
         | 
| 50 | 
            +
                  <Body marginBottom="sm">
         | 
| 51 | 
            +
                    The following have the same autocomplete attributes (email), but have
         | 
| 52 | 
            +
                    different name attributes (email and emailAlt). Many browsers will
         | 
| 53 | 
            +
                    open autocomplete based on name attributes instead of autocomplete:
         | 
| 54 | 
            +
                  </Body>
         | 
| 55 | 
            +
                  <TextInput
         | 
| 56 | 
            +
                      autoComplete="email"
         | 
| 57 | 
            +
                      label="autocomplete='email' name='email'"
         | 
| 58 | 
            +
                      name="email"
         | 
| 59 | 
            +
                      onChange={handleOnChangeFormField}
         | 
| 60 | 
            +
                      placeholder="Enter email address"
         | 
| 61 | 
            +
                      type="email"
         | 
| 62 | 
            +
                      value={formFields.email}
         | 
| 63 | 
            +
                      {...props}
         | 
| 64 | 
            +
                  />
         | 
| 65 | 
            +
                  <TextInput
         | 
| 66 | 
            +
                      autoComplete="email"
         | 
| 67 | 
            +
                      label="autocomplete='email' name='emailAlt'"
         | 
| 68 | 
            +
                      marginTop="sm"
         | 
| 69 | 
            +
                      name="emailTest"
         | 
| 70 | 
            +
                      onChange={handleOnChangeFormField}
         | 
| 71 | 
            +
                      placeholder="Enter email address"
         | 
| 72 | 
            +
                      type="email"
         | 
| 73 | 
            +
                      value={formFields.emailTest}
         | 
| 74 | 
            +
                      {...props}
         | 
| 75 | 
            +
                  />
         | 
| 76 | 
            +
                </div>
         | 
| 77 | 
            +
              );
         | 
| 78 | 
            +
            };
         | 
| 79 | 
            +
             | 
| 80 | 
            +
            export default TextInputAutocomplete;
         | 
| @@ -0,0 +1 @@ | |
| 1 | 
            +
            Set this prop to `false` or `"off"` to remove autocomplete from text inputs. You can also set it to a string, but browsers will often defer to other attributes like `name`. 
         | 
| @@ -9,6 +9,8 @@ examples: | |
| 9 9 | 
             
              - text_input_no_label: No Label
         | 
| 10 10 | 
             
              - text_input_options: Input Options
         | 
| 11 11 | 
             
              - text_input_mask: Mask
         | 
| 12 | 
            +
              - text_input_autocomplete: Autocomplete
         | 
| 13 | 
            +
              
         | 
| 12 14 | 
             
              react:
         | 
| 13 15 | 
             
              - text_input_default: Default
         | 
| 14 16 | 
             
              - text_input_error: With Error
         | 
| @@ -19,6 +21,7 @@ examples: | |
| 19 21 | 
             
              - text_input_no_label: No Label
         | 
| 20 22 | 
             
              - text_input_mask: Mask
         | 
| 21 23 | 
             
              - text_input_sanitize: Sanitized Masked Input
         | 
| 24 | 
            +
              - text_input_autocomplete: Autocomplete
         | 
| 22 25 |  | 
| 23 26 |  | 
| 24 27 | 
             
              swift:
         | 
| @@ -7,3 +7,4 @@ export { default as TextInputInline } from './_text_input_inline.jsx' | |
| 7 7 | 
             
            export { default as TextInputNoLabel } from './_text_input_no_label.jsx'
         | 
| 8 8 | 
             
            export { default as TextInputMask } from './_text_input_mask.jsx'
         | 
| 9 9 | 
             
            export { default as TextInputSanitize } from './_text_input_sanitize.jsx'
         | 
| 10 | 
            +
            export { default as TextInputAutocomplete } from './_text_input_autocomplete.jsx'
         | 
| @@ -15,8 +15,7 @@ module Playbook | |
| 15 15 | 
             
                    "cvv" => '\d{3,4}',
         | 
| 16 16 | 
             
                  }.freeze
         | 
| 17 17 |  | 
| 18 | 
            -
                  prop :autocomplete,  | 
| 19 | 
            -
                                      default: true
         | 
| 18 | 
            +
                  prop :autocomplete, default: true
         | 
| 20 19 | 
             
                  prop :disabled, type: Playbook::Props::Boolean,
         | 
| 21 20 | 
             
                                  default: false
         | 
| 22 21 | 
             
                  prop :error
         | 
| @@ -69,7 +68,7 @@ module Playbook | |
| 69 68 |  | 
| 70 69 | 
             
                  def all_input_options
         | 
| 71 70 | 
             
                    {
         | 
| 72 | 
            -
                      autocomplete: autocomplete ? nil : "off",
         | 
| 71 | 
            +
                      autocomplete: autocomplete == true ? nil : (autocomplete.presence || "off"),
         | 
| 73 72 | 
             
                      class: "text_input #{input_options.dig(:classname) || ''}",
         | 
| 74 73 | 
             
                      data: validation_data,
         | 
| 75 74 | 
             
                      disabled: disabled,
         | 
| @@ -306,3 +306,41 @@ test('returns masked CVV value', () => { | |
| 306 306 |  | 
| 307 307 | 
             
              expect(input.value).toBe('')
         | 
| 308 308 | 
             
            })
         | 
| 309 | 
            +
             | 
| 310 | 
            +
            test('adds autocomplete string attribute', () => {
         | 
| 311 | 
            +
              render(
         | 
| 312 | 
            +
                <TextInput
         | 
| 313 | 
            +
                    autoComplete="family-name"
         | 
| 314 | 
            +
                    data={{ testid: testId }}
         | 
| 315 | 
            +
                />
         | 
| 316 | 
            +
              )
         | 
| 317 | 
            +
             | 
| 318 | 
            +
              const kit = screen.getByTestId(testId)
         | 
| 319 | 
            +
              const input = within(kit).getByRole('textbox')
         | 
| 320 | 
            +
              expect(input).toHaveAttribute("autocomplete", "family-name")
         | 
| 321 | 
            +
            })
         | 
| 322 | 
            +
             | 
| 323 | 
            +
            test('adds autocomplete "off" attribute', () => {
         | 
| 324 | 
            +
              render(
         | 
| 325 | 
            +
                <TextInput
         | 
| 326 | 
            +
                    autoComplete={false}
         | 
| 327 | 
            +
                    data={{ testid: testId }}
         | 
| 328 | 
            +
                />
         | 
| 329 | 
            +
              )
         | 
| 330 | 
            +
             | 
| 331 | 
            +
              const kit = screen.getByTestId(testId)
         | 
| 332 | 
            +
              const input = within(kit).getByRole('textbox')
         | 
| 333 | 
            +
              expect(input).toHaveAttribute("autocomplete", "off")
         | 
| 334 | 
            +
            })
         | 
| 335 | 
            +
             | 
| 336 | 
            +
            test('does not add autocomplete attribute otherwise', () => {
         | 
| 337 | 
            +
              render(
         | 
| 338 | 
            +
                <TextInput
         | 
| 339 | 
            +
                    data={{ testid: testId }}
         | 
| 340 | 
            +
                />
         | 
| 341 | 
            +
              )
         | 
| 342 | 
            +
             | 
| 343 | 
            +
              const kit = screen.getByTestId(testId)
         | 
| 344 | 
            +
              const input = within(kit).getByRole('textbox')
         | 
| 345 | 
            +
              expect(input).not.toHaveAttribute("autocomplete")
         | 
| 346 | 
            +
            })
         |