playbook_ui 15.6.0 → 15.7.0.pre.alpha.PLAY2678emojimask13284
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/_playbook.scss +1 -1
- data/app/pb_kits/playbook/pb_advanced_table/Components/RegularTableView.tsx +3 -2
- data/app/pb_kits/playbook/pb_advanced_table/Components/TableHeaderCell.tsx +4 -0
- data/app/pb_kits/playbook/pb_advanced_table/advanced_table.test.jsx +95 -0
- data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_background_colors_rails.html.erb +43 -0
- data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_background_colors_rails.md +1 -0
- data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_background_control_rails.html.erb +11 -5
- data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_background_control_rails.md +7 -1
- data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_styling_background.jsx +54 -0
- data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_styling_background.md +9 -0
- data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_styling_background_multi.jsx +80 -0
- data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_styling_background_multi.md +3 -0
- data/app/pb_kits/playbook/pb_advanced_table/docs/example.yml +4 -1
- data/app/pb_kits/playbook/pb_advanced_table/docs/index.js +3 -1
- data/app/pb_kits/playbook/pb_advanced_table/table_header.html.erb +2 -2
- data/app/pb_kits/playbook/pb_advanced_table/table_header.rb +57 -0
- data/app/pb_kits/playbook/pb_bar_graph/_bar_graph.tsx +6 -0
- data/app/pb_kits/playbook/pb_card/docs/_card_header.md +1 -1
- data/app/pb_kits/playbook/pb_card/docs/_card_highlight.md +1 -1
- data/app/pb_kits/playbook/pb_circle_chart/_circle_chart.tsx +6 -0
- data/app/pb_kits/playbook/pb_collapsible/__snapshots__/collapsible.test.js.snap +2 -2
- data/app/pb_kits/playbook/pb_collapsible/child_kits/CollapsibleIcon.tsx +10 -8
- data/app/pb_kits/playbook/pb_collapsible/docs/_collapsible_icons.jsx +0 -1
- data/app/pb_kits/playbook/pb_collapsible/docs/_collapsible_state.jsx +0 -3
- data/app/pb_kits/playbook/pb_contact/_contact.tsx +51 -24
- data/app/pb_kits/playbook/pb_contact/contact.html.erb +53 -19
- data/app/pb_kits/playbook/pb_contact/contact.rb +11 -1
- data/app/pb_kits/playbook/pb_contact/contact.test.js +76 -0
- data/app/pb_kits/playbook/pb_contact/docs/_contact_unstyled.html.erb +33 -0
- data/app/pb_kits/playbook/pb_contact/docs/_contact_unstyled.jsx +46 -0
- data/app/pb_kits/playbook/pb_contact/docs/_contact_unstyled_rails.md +2 -0
- data/app/pb_kits/playbook/pb_contact/docs/_contact_unstyled_react.md +2 -0
- data/app/pb_kits/playbook/pb_contact/docs/example.yml +2 -0
- data/app/pb_kits/playbook/pb_contact/docs/index.js +1 -0
- data/app/pb_kits/playbook/pb_date_picker/date_picker.test.js +24 -0
- data/app/pb_kits/playbook/pb_date_picker/date_picker_helper.ts +197 -7
- data/app/pb_kits/playbook/pb_date_picker/docs/_date_picker_range_pattern_rails.html.erb +23 -14
- data/app/pb_kits/playbook/pb_date_picker/docs/_date_picker_range_pattern_rails.md +1 -1
- data/app/pb_kits/playbook/pb_dialog/_dialog.tsx +2 -1
- data/app/pb_kits/playbook/pb_dialog/dialog.html.erb +1 -1
- data/app/pb_kits/playbook/pb_dialog/dialog.rb +1 -0
- data/app/pb_kits/playbook/pb_dialog/dialog.test.jsx +14 -0
- data/app/pb_kits/playbook/pb_dialog/dialog_header.html.erb +5 -4
- data/app/pb_kits/playbook/pb_dialog/dialog_header.rb +2 -0
- data/app/pb_kits/playbook/pb_dialog/docs/_dialog_closeable.html.erb +24 -0
- data/app/pb_kits/playbook/pb_dialog/docs/_dialog_closeable.jsx +60 -0
- data/app/pb_kits/playbook/pb_dialog/docs/_dialog_closeable.md +3 -0
- data/app/pb_kits/playbook/pb_dialog/docs/_dialog_overflow_visible.html.erb +71 -0
- data/app/pb_kits/playbook/pb_dialog/docs/_dialog_overflow_visible.jsx +57 -0
- data/app/pb_kits/playbook/pb_dialog/docs/_dialog_overflow_visible_rails.md +1 -0
- data/app/pb_kits/playbook/pb_dialog/docs/_dialog_overflow_visible_react.md +1 -0
- data/app/pb_kits/playbook/pb_dialog/docs/example.yml +4 -0
- data/app/pb_kits/playbook/pb_dialog/docs/index.js +3 -1
- data/app/pb_kits/playbook/pb_distribution_bar/docs/_distribution_bar_custom_colors.md +1 -1
- data/app/pb_kits/playbook/pb_draggable/context/index.tsx +316 -15
- data/app/pb_kits/playbook/pb_draggable/context/types.ts +1 -1
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_default_rails.html.erb +7 -5
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_quickpick_default_dates.html.erb +19 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_quickpick_rails.html.erb +12 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_quickpick_rails.md +26 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_quickpick_range_end_rails.html.erb +19 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_quickpick_range_end_rails.md +1 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_quickpick_with_date_pickers_default_rails.html.erb +30 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_quickpick_with_date_pickers_default_rails.md +3 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_quickpick_with_date_pickers_rails.html.erb +29 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_quickpick_with_date_pickers_rails.md +13 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_display_rails.html.erb +3 -1
- data/app/pb_kits/playbook/pb_dropdown/docs/example.yml +5 -0
- data/app/pb_kits/playbook/pb_dropdown/dropdown.html.erb +4 -0
- data/app/pb_kits/playbook/pb_dropdown/dropdown.rb +39 -5
- data/app/pb_kits/playbook/pb_dropdown/index.js +171 -3
- data/app/pb_kits/playbook/pb_dropdown/quickpick_helper.rb +75 -0
- data/app/pb_kits/playbook/pb_filter/Filter/FilterBackground.tsx +3 -3
- data/app/pb_kits/playbook/pb_form/docs/_form_form_with.html.erb +3 -3
- data/app/pb_kits/playbook/pb_form/docs/_form_form_with_validate.html.erb +2 -1
- data/app/pb_kits/playbook/pb_form/docs/_form_with_required_indicator.html.erb +14 -0
- data/app/pb_kits/playbook/pb_form/docs/_form_with_required_indicator.md +3 -0
- data/app/pb_kits/playbook/pb_form/docs/example.yml +1 -0
- data/app/pb_kits/playbook/pb_gauge/_gauge.tsx +6 -0
- data/app/pb_kits/playbook/pb_line_graph/_line_graph.tsx +6 -0
- data/app/pb_kits/playbook/pb_popover/docs/_popover_append_to.html.erb +2 -2
- data/app/pb_kits/playbook/pb_popover/docs/_popover_append_to.jsx +3 -2
- data/app/pb_kits/playbook/pb_radio/docs/_radio_error.md +1 -1
- data/app/pb_kits/playbook/pb_select/_select.tsx +8 -3
- data/app/pb_kits/playbook/pb_select/docs/_select_error.md +1 -1
- data/app/pb_kits/playbook/pb_select/docs/_select_input_options.html.erb +16 -0
- data/app/pb_kits/playbook/pb_select/docs/_select_input_options.jsx +30 -0
- data/app/pb_kits/playbook/pb_select/docs/_select_input_options.md +1 -0
- data/app/pb_kits/playbook/pb_select/docs/example.yml +2 -0
- data/app/pb_kits/playbook/pb_select/docs/index.js +1 -0
- data/app/pb_kits/playbook/pb_select/select.html.erb +2 -2
- data/app/pb_kits/playbook/pb_select/select.rb +3 -1
- data/app/pb_kits/playbook/pb_select/select.test.js +23 -0
- data/app/pb_kits/playbook/pb_table/_table.tsx +187 -33
- data/app/pb_kits/playbook/pb_table/docs/_table_with_filter_variant.jsx +134 -0
- data/app/pb_kits/playbook/pb_table/docs/_table_with_filter_variant.md +34 -0
- data/app/pb_kits/playbook/pb_table/docs/_table_with_filter_variant_rails.html.erb +101 -0
- data/app/pb_kits/playbook/pb_table/docs/_table_with_filter_variant_rails.md +33 -0
- data/app/pb_kits/playbook/pb_table/docs/_table_with_filter_variant_with_pagination.jsx +180 -0
- data/app/pb_kits/playbook/pb_table/docs/_table_with_filter_variant_with_pagination.md +3 -0
- data/app/pb_kits/playbook/pb_table/docs/_table_with_filter_variant_with_pagination_rails.html.erb +122 -0
- data/app/pb_kits/playbook/pb_table/docs/_table_with_filter_variant_with_pagination_rails.md +3 -0
- data/app/pb_kits/playbook/pb_table/docs/example.yml +4 -0
- data/app/pb_kits/playbook/pb_table/docs/index.js +2 -0
- data/app/pb_kits/playbook/pb_table/table.html.erb +68 -12
- data/app/pb_kits/playbook/pb_table/table.rb +22 -3
- data/app/pb_kits/playbook/pb_table/table.test.js +143 -0
- data/app/pb_kits/playbook/pb_text_input/_text_input.tsx +56 -6
- data/app/pb_kits/playbook/pb_text_input/docs/_text_input_emoji_mask.html.erb +7 -0
- data/app/pb_kits/playbook/pb_text_input/docs/_text_input_emoji_mask.jsx +24 -0
- data/app/pb_kits/playbook/pb_text_input/docs/_text_input_emoji_mask.md +2 -0
- data/app/pb_kits/playbook/pb_text_input/docs/_text_input_error.md +1 -1
- data/app/pb_kits/playbook/pb_text_input/docs/_text_input_required_indicator.html.erb +6 -0
- data/app/pb_kits/playbook/pb_text_input/docs/_text_input_required_indicator.jsx +25 -0
- data/app/pb_kits/playbook/pb_text_input/docs/_text_input_required_indicator.md +3 -0
- data/app/pb_kits/playbook/pb_text_input/docs/example.yml +5 -0
- data/app/pb_kits/playbook/pb_text_input/docs/index.js +2 -0
- data/app/pb_kits/playbook/pb_text_input/index.js +49 -8
- data/app/pb_kits/playbook/pb_text_input/text_input.html.erb +6 -0
- data/app/pb_kits/playbook/pb_text_input/text_input.rb +7 -1
- data/app/pb_kits/playbook/pb_text_input/text_input.test.js +69 -0
- data/app/pb_kits/playbook/pb_textarea/_textarea.tsx +38 -2
- data/app/pb_kits/playbook/pb_textarea/docs/_textarea_emoji_mask.html.erb +5 -0
- data/app/pb_kits/playbook/pb_textarea/docs/_textarea_emoji_mask.jsx +24 -0
- data/app/pb_kits/playbook/pb_textarea/docs/_textarea_emoji_mask.md +1 -0
- data/app/pb_kits/playbook/pb_textarea/docs/_textarea_error.md +1 -1
- data/app/pb_kits/playbook/pb_textarea/docs/example.yml +2 -0
- data/app/pb_kits/playbook/pb_textarea/docs/index.js +1 -0
- data/app/pb_kits/playbook/pb_textarea/index.ts +62 -5
- data/app/pb_kits/playbook/pb_textarea/textarea.html.erb +1 -0
- data/app/pb_kits/playbook/pb_textarea/textarea.rb +8 -0
- data/app/pb_kits/playbook/pb_textarea/textarea.test.js +57 -2
- data/app/pb_kits/playbook/pb_time_picker/_time_picker.scss +296 -0
- data/app/pb_kits/playbook/pb_time_picker/_time_picker.tsx +822 -0
- data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_24_hour.html.erb +2 -0
- data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_24_hour.jsx +16 -0
- data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_24_hour.md +1 -0
- data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_default.html.erb +1 -0
- data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_default.jsx +13 -0
- data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_default.md +1 -0
- data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_default_time.html.erb +4 -0
- data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_default_time.jsx +29 -0
- data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_default_time.md +1 -0
- data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_disabled.html.erb +13 -0
- data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_disabled.jsx +23 -0
- data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_error.html.erb +5 -0
- data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_error.jsx +15 -0
- data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_input_options.html.erb +14 -0
- data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_label.html.erb +2 -0
- data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_label.jsx +15 -0
- data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_min_max_time.html.erb +42 -0
- data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_min_max_time.jsx +52 -0
- data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_min_max_time.md +1 -0
- data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_on_handler.jsx +45 -0
- data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_on_handler.md +1 -0
- data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_timezone.html.erb +3 -0
- data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_timezone.jsx +21 -0
- data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_timezone.md +1 -0
- data/app/pb_kits/playbook/pb_time_picker/docs/example.yml +24 -0
- data/app/pb_kits/playbook/pb_time_picker/docs/index.js +9 -0
- data/app/pb_kits/playbook/pb_time_picker/index.ts +40 -0
- data/app/pb_kits/playbook/pb_time_picker/time_picker.html.erb +1 -0
- data/app/pb_kits/playbook/pb_time_picker/time_picker.rb +80 -0
- data/app/pb_kits/playbook/pb_time_picker/time_picker.test.jsx +114 -0
- data/app/pb_kits/playbook/pb_time_picker/time_picker_helper.ts +662 -0
- data/app/pb_kits/playbook/pb_timeline/_item.tsx +3 -0
- data/app/pb_kits/playbook/pb_timeline/docs/_timeline_show_current_year.html.erb +60 -0
- data/app/pb_kits/playbook/pb_timeline/docs/_timeline_show_current_year.jsx +118 -0
- data/app/pb_kits/playbook/pb_timeline/docs/_timeline_show_current_year.md +1 -0
- data/app/pb_kits/playbook/pb_timeline/docs/_timeline_with_date.md +1 -1
- data/app/pb_kits/playbook/pb_timeline/docs/example.yml +2 -0
- data/app/pb_kits/playbook/pb_timeline/docs/index.js +1 -0
- data/app/pb_kits/playbook/pb_timeline/item.html.erb +1 -1
- data/app/pb_kits/playbook/pb_timeline/item.rb +2 -0
- data/app/pb_kits/playbook/pb_timeline/label.html.erb +2 -1
- data/app/pb_kits/playbook/pb_timeline/label.rb +2 -0
- data/app/pb_kits/playbook/pb_timeline/subcomponents/Label.tsx +3 -0
- data/app/pb_kits/playbook/pb_timeline/timeline.test.js +51 -0
- data/app/pb_kits/playbook/tokens/_colors.scss +2 -1
- data/app/pb_kits/playbook/utilities/deprecated.ts +73 -0
- data/app/pb_kits/playbook/utilities/emojiMask.ts +42 -0
- data/app/pb_kits/playbook/utilities/globalProps.ts +1 -0
- data/dist/chunks/_typeahead-CSCNg6cp.js +6 -0
- data/dist/chunks/lib-DxCgrqqG.js +29 -0
- data/dist/chunks/vendor.js +3 -3
- data/dist/menu.yml +16 -9
- 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/forms/builder/collection_select_field.rb +9 -1
- data/lib/playbook/forms/builder/form_field_builder.rb +15 -2
- data/lib/playbook/forms/builder/select_field.rb +9 -1
- data/lib/playbook/forms/builder/time_picker_field.rb +24 -0
- data/lib/playbook/forms/builder/time_zone_select_field.rb +9 -1
- data/lib/playbook/forms/builder.rb +1 -0
- data/lib/playbook/pb_doc_helper.rb +3 -0
- data/lib/playbook/pb_kit_helper.rb +35 -0
- data/lib/playbook/version.rb +2 -2
- metadata +92 -4
- data/dist/chunks/_typeahead-DecTL7bt.js +0 -6
- data/dist/chunks/lib-Dk4GKPut.js +0 -29
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { forwardRef, ChangeEvent } from 'react'
|
|
1
|
+
import React, { forwardRef, ChangeEvent, ClipboardEvent } from 'react'
|
|
2
2
|
import classnames from 'classnames'
|
|
3
3
|
|
|
4
4
|
import { globalProps, GlobalProps, domSafeProps } from '../utilities/globalProps'
|
|
@@ -9,8 +9,10 @@ import Card from '../pb_card/_card'
|
|
|
9
9
|
import Caption from '../pb_caption/_caption'
|
|
10
10
|
import Body from '../pb_body/_body'
|
|
11
11
|
import Icon from '../pb_icon/_icon'
|
|
12
|
+
import colors from '../tokens/exports/_colors.module.scss'
|
|
12
13
|
|
|
13
14
|
import { INPUTMASKS } from './inputMask'
|
|
15
|
+
import { stripEmojisForPaste, applyEmojiMask } from '../utilities/emojiMask'
|
|
14
16
|
|
|
15
17
|
type TextInputProps = {
|
|
16
18
|
aria?: { [key: string]: string },
|
|
@@ -18,6 +20,7 @@ type TextInputProps = {
|
|
|
18
20
|
data?: { [key: string]: string },
|
|
19
21
|
dark?: boolean,
|
|
20
22
|
disabled?: boolean,
|
|
23
|
+
emojiMask?: boolean,
|
|
21
24
|
error?: string,
|
|
22
25
|
htmlOptions?: {[key: string]: string | number | boolean | (() => void)},
|
|
23
26
|
id?: string,
|
|
@@ -28,6 +31,7 @@ type TextInputProps = {
|
|
|
28
31
|
onChange: (e: React.FormEvent<HTMLInputElement>, sanitizedValue?: string) => void,
|
|
29
32
|
placeholder: string,
|
|
30
33
|
required?: boolean,
|
|
34
|
+
requiredIndicator?: boolean,
|
|
31
35
|
type: string,
|
|
32
36
|
value: string | number,
|
|
33
37
|
children: React.ReactElement,
|
|
@@ -47,6 +51,7 @@ const TextInput = (props: TextInputProps, ref: React.LegacyRef<HTMLInputElement>
|
|
|
47
51
|
dark = false,
|
|
48
52
|
data = {},
|
|
49
53
|
disabled,
|
|
54
|
+
emojiMask = false,
|
|
50
55
|
error,
|
|
51
56
|
htmlOptions = {},
|
|
52
57
|
id,
|
|
@@ -60,6 +65,7 @@ const TextInput = (props: TextInputProps, ref: React.LegacyRef<HTMLInputElement>
|
|
|
60
65
|
type = 'text',
|
|
61
66
|
value = '',
|
|
62
67
|
children = null,
|
|
68
|
+
requiredIndicator = false,
|
|
63
69
|
autoComplete = true,
|
|
64
70
|
} = props
|
|
65
71
|
|
|
@@ -99,6 +105,11 @@ const TextInput = (props: TextInputProps, ref: React.LegacyRef<HTMLInputElement>
|
|
|
99
105
|
const isMaskedInput = mask && mask in INPUTMASKS
|
|
100
106
|
|
|
101
107
|
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
|
108
|
+
// Apply emoji mask if enabled using centralized helper
|
|
109
|
+
if (emojiMask) {
|
|
110
|
+
applyEmojiMask(e.target)
|
|
111
|
+
}
|
|
112
|
+
|
|
102
113
|
if (isMaskedInput) {
|
|
103
114
|
const inputValue = e.target.value
|
|
104
115
|
|
|
@@ -131,6 +142,29 @@ const TextInput = (props: TextInputProps, ref: React.LegacyRef<HTMLInputElement>
|
|
|
131
142
|
}
|
|
132
143
|
}
|
|
133
144
|
|
|
145
|
+
// Handle paste event for emoji mask - updates input value, cursor position, and calls onChange
|
|
146
|
+
const handlePaste = (e: ClipboardEvent<HTMLInputElement>) => {
|
|
147
|
+
if (emojiMask) {
|
|
148
|
+
const pastedText = e.clipboardData.getData('text')
|
|
149
|
+
const filteredText = stripEmojisForPaste(pastedText)
|
|
150
|
+
|
|
151
|
+
if (pastedText !== filteredText) {
|
|
152
|
+
e.preventDefault()
|
|
153
|
+
const input = e.currentTarget
|
|
154
|
+
const start = input.selectionStart || 0
|
|
155
|
+
const end = input.selectionEnd || 0
|
|
156
|
+
const currentValue = input.value
|
|
157
|
+
const newValue = currentValue.slice(0, start) + filteredText + currentValue.slice(end)
|
|
158
|
+
const newCursorPosition = start + filteredText.length
|
|
159
|
+
|
|
160
|
+
input.value = newValue
|
|
161
|
+
input.selectionStart = input.selectionEnd = newCursorPosition
|
|
162
|
+
|
|
163
|
+
onChange({ ...e, target: input, currentTarget: input } as unknown as ChangeEvent<HTMLInputElement>)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
134
168
|
const childInput = children ? children.type === "input" : undefined
|
|
135
169
|
|
|
136
170
|
let formattedValue;
|
|
@@ -142,10 +176,16 @@ const TextInput = (props: TextInputProps, ref: React.LegacyRef<HTMLInputElement>
|
|
|
142
176
|
|
|
143
177
|
const errorId = error ? `${id}-error` : undefined
|
|
144
178
|
|
|
179
|
+
// Set custom handler between emoji mask and input mask
|
|
180
|
+
const shouldUseCustomHandler = isMaskedInput || emojiMask
|
|
181
|
+
|
|
182
|
+
// Filter out emojiMask from props passed to DOM element
|
|
183
|
+
const { emojiMask: _emojiMask, ...domProps } = props
|
|
184
|
+
|
|
145
185
|
const textInput = (
|
|
146
186
|
childInput ? React.cloneElement(children, { className: "text_input" }) :
|
|
147
187
|
(<input
|
|
148
|
-
{...domSafeProps(
|
|
188
|
+
{...domSafeProps(domProps)}
|
|
149
189
|
aria-describedby={errorId}
|
|
150
190
|
aria-invalid={!!error}
|
|
151
191
|
autoComplete={typeof autoComplete === "string" ? autoComplete : ( autoComplete ? undefined : "off" )}
|
|
@@ -154,7 +194,8 @@ const TextInput = (props: TextInputProps, ref: React.LegacyRef<HTMLInputElement>
|
|
|
154
194
|
id={id}
|
|
155
195
|
key={id}
|
|
156
196
|
name={name}
|
|
157
|
-
onChange={
|
|
197
|
+
onChange={shouldUseCustomHandler ? handleChange : onChange}
|
|
198
|
+
onPaste={emojiMask ? handlePaste : undefined}
|
|
158
199
|
pattern={isMaskedInput ? INPUTMASKS[mask]?.pattern : undefined}
|
|
159
200
|
placeholder={placeholder || (isMaskedInput ? INPUTMASKS[mask]?.placeholder : undefined)}
|
|
160
201
|
ref={ref}
|
|
@@ -208,9 +249,18 @@ const TextInput = (props: TextInputProps, ref: React.LegacyRef<HTMLInputElement>
|
|
|
208
249
|
>
|
|
209
250
|
{label && (
|
|
210
251
|
<label htmlFor={id}>
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
252
|
+
{
|
|
253
|
+
requiredIndicator ? (
|
|
254
|
+
<Caption className="pb_text_input_kit_label">
|
|
255
|
+
{label} <span style={{ color: `${colors.error}` }}>*</span>
|
|
256
|
+
</Caption>
|
|
257
|
+
) : (
|
|
258
|
+
<Caption className="pb_text_input_kit_label"
|
|
259
|
+
text={label}
|
|
260
|
+
/>
|
|
261
|
+
)
|
|
262
|
+
}
|
|
263
|
+
|
|
214
264
|
</label>
|
|
215
265
|
)}
|
|
216
266
|
<div className={`${addOnCss} text_input_wrapper`}>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
|
+
|
|
3
|
+
import TextInput from '../../pb_text_input/_text_input'
|
|
4
|
+
|
|
5
|
+
const TextInputEmojiMask = (props) => {
|
|
6
|
+
const [basicValue, setBasicValue] = useState('')
|
|
7
|
+
|
|
8
|
+
return (
|
|
9
|
+
<div>
|
|
10
|
+
<TextInput
|
|
11
|
+
emojiMask
|
|
12
|
+
label="Emoji Mask"
|
|
13
|
+
onChange={({ target }) => setBasicValue(target.value)}
|
|
14
|
+
placeholder="Try typing or pasting emojis..."
|
|
15
|
+
value={basicValue}
|
|
16
|
+
{...props}
|
|
17
|
+
/>
|
|
18
|
+
</div>
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export default TextInputEmojiMask
|
|
23
|
+
|
|
24
|
+
|
|
@@ -1 +1 @@
|
|
|
1
|
-
Text Input w/ Error shows that the
|
|
1
|
+
Text Input w/ Error shows that the input must be filled out (i.e. when used in a form it signals a user to fix an error).
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
|
+
|
|
3
|
+
import TextInput from '../../pb_text_input/_text_input'
|
|
4
|
+
|
|
5
|
+
const TextInputDefault = (props) => {
|
|
6
|
+
const [firstName, setFirstName] = useState('')
|
|
7
|
+
const handleOnChangeFirstName = ({ target }) => {
|
|
8
|
+
setFirstName(target.value)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<TextInput
|
|
13
|
+
id="text_input_required_indicator"
|
|
14
|
+
label="First Name"
|
|
15
|
+
name="firstName"
|
|
16
|
+
onChange={handleOnChangeFirstName}
|
|
17
|
+
placeholder="Enter first name"
|
|
18
|
+
requiredIndicator
|
|
19
|
+
value={firstName}
|
|
20
|
+
{...props}
|
|
21
|
+
/>
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default TextInputDefault
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
The `requiredIndicator`/`required_indicator` prop displays a red asterisk (*) next to the label, visually indicating that the field is required. This is purely visual and does not enforce validation.
|
|
2
|
+
|
|
3
|
+
You can use `requiredIndicator`/`required_indicator` with any validation approach: HTML5 validation via the `required` prop, client-side validation, or backend validation. For this reason, it works independently and doesn't need to be paired with the `required` prop.
|
|
@@ -10,6 +10,9 @@ examples:
|
|
|
10
10
|
- text_input_options: Input Options
|
|
11
11
|
- text_input_mask: Mask
|
|
12
12
|
- text_input_autocomplete: Autocomplete
|
|
13
|
+
- text_input_required_indicator: Required Indicator
|
|
14
|
+
- text_input_emoji_mask: Emoji Mask
|
|
15
|
+
|
|
13
16
|
|
|
14
17
|
react:
|
|
15
18
|
- text_input_default: Default
|
|
@@ -22,6 +25,8 @@ examples:
|
|
|
22
25
|
- text_input_mask: Mask
|
|
23
26
|
- text_input_sanitize: Sanitized Masked Input
|
|
24
27
|
- text_input_autocomplete: Autocomplete
|
|
28
|
+
- text_input_required_indicator: Required Indicator
|
|
29
|
+
- text_input_emoji_mask: Emoji Mask
|
|
25
30
|
|
|
26
31
|
|
|
27
32
|
swift:
|
|
@@ -8,3 +8,5 @@ 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
10
|
export { default as TextInputAutocomplete } from './_text_input_autocomplete.jsx'
|
|
11
|
+
export { default as TextInputRequiredIndicator } from './_text_input_required_indicator.jsx'
|
|
12
|
+
export { default as TextInputEmojiMask } from './_text_input_emoji_mask.jsx'
|
|
@@ -1,26 +1,64 @@
|
|
|
1
1
|
import PbEnhancedElement from "../pb_enhanced_element"
|
|
2
2
|
import { INPUTMASKS } from "./inputMask"
|
|
3
|
+
import { stripEmojisForPaste, applyEmojiMask } from "../utilities/emojiMask"
|
|
3
4
|
|
|
4
5
|
export default class PbTextInput extends PbEnhancedElement {
|
|
5
6
|
static get selector() {
|
|
6
|
-
return '[data-pb-input-mask="true"]';
|
|
7
|
+
return '[data-pb-input-mask="true"], [data-pb-emoji-mask="true"]';
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
connect() {
|
|
10
11
|
this.handleInput = this.handleInput.bind(this);
|
|
12
|
+
this.handlePaste = this.handlePaste.bind(this);
|
|
11
13
|
this.element.addEventListener("input", this.handleInput);
|
|
14
|
+
this.element.addEventListener("paste", this.handlePaste);
|
|
12
15
|
this.handleInput();
|
|
13
16
|
}
|
|
14
17
|
|
|
15
18
|
disconnect() {
|
|
16
19
|
this.element.removeEventListener("input", this.handleInput);
|
|
20
|
+
this.element.removeEventListener("paste", this.handlePaste);
|
|
17
21
|
}
|
|
18
22
|
|
|
19
|
-
|
|
20
|
-
|
|
23
|
+
hasEmojiMask() {
|
|
24
|
+
return this.element.dataset.pbEmojiMask === "true";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
handlePaste(event) {
|
|
28
|
+
if (!this.hasEmojiMask()) return;
|
|
29
|
+
|
|
30
|
+
const pastedText = event.clipboardData.getData('text');
|
|
31
|
+
const filteredText = stripEmojisForPaste(pastedText);
|
|
32
|
+
|
|
33
|
+
if (pastedText !== filteredText) {
|
|
34
|
+
event.preventDefault();
|
|
35
|
+
const input = this.element;
|
|
36
|
+
const start = input.selectionStart || 0;
|
|
37
|
+
const end = input.selectionEnd || 0;
|
|
38
|
+
const currentValue = input.value;
|
|
39
|
+
const newValue = currentValue.slice(0, start) + filteredText + currentValue.slice(end);
|
|
40
|
+
const newCursor = start + filteredText.length;
|
|
41
|
+
|
|
42
|
+
input.value = newValue;
|
|
43
|
+
input.selectionStart = input.selectionEnd = newCursor;
|
|
44
|
+
|
|
45
|
+
// Continue to handleInput for mask processing, emoji filtering handled above
|
|
46
|
+
this.handleInput({ skipEmojiFilter: true });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
handleInput({ skipEmojiFilter = false } = {}) {
|
|
21
51
|
const cursorPosition = this.element.selectionStart;
|
|
22
|
-
|
|
23
|
-
|
|
52
|
+
let baseValue = this.element.value;
|
|
53
|
+
|
|
54
|
+
// Apply emoji mask if enabled (skip if already filtered in paste handler)
|
|
55
|
+
if (this.hasEmojiMask() && !skipEmojiFilter) {
|
|
56
|
+
const result = applyEmojiMask(this.element);
|
|
57
|
+
baseValue = result.value;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const maskType = this.element.getAttribute("mask");
|
|
61
|
+
let formattedValue = baseValue;
|
|
24
62
|
|
|
25
63
|
const maskKey = {
|
|
26
64
|
currency: 'currency',
|
|
@@ -32,13 +70,14 @@ export default class PbTextInput extends PbEnhancedElement {
|
|
|
32
70
|
}[maskType];
|
|
33
71
|
|
|
34
72
|
if (maskKey && INPUTMASKS[maskKey]) {
|
|
35
|
-
formattedValue = INPUTMASKS[maskKey].format(
|
|
73
|
+
formattedValue = INPUTMASKS[maskKey].format(baseValue);
|
|
36
74
|
}
|
|
37
75
|
|
|
38
76
|
const sanitizedInput = this.element
|
|
39
77
|
.closest(".text_input_wrapper")
|
|
40
78
|
?.querySelector('[data="sanitized-pb-input"]');
|
|
41
79
|
|
|
80
|
+
// Ensure sanitized input uses the already filtered value
|
|
42
81
|
if (sanitizedInput) {
|
|
43
82
|
switch (maskType) {
|
|
44
83
|
case "ssn":
|
|
@@ -55,8 +94,10 @@ export default class PbTextInput extends PbEnhancedElement {
|
|
|
55
94
|
}
|
|
56
95
|
}
|
|
57
96
|
|
|
58
|
-
|
|
59
|
-
|
|
97
|
+
if (maskType) {
|
|
98
|
+
this.element.value = formattedValue;
|
|
99
|
+
setCursorPosition(this.element, cursorPosition, baseValue, formattedValue);
|
|
100
|
+
}
|
|
60
101
|
}
|
|
61
102
|
}
|
|
62
103
|
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
<%= pb_content_tag(:div, id: nil ) do %>
|
|
2
2
|
<% if object.label.present? %>
|
|
3
3
|
<label for="<%= object.input_options[:id] || object.id %>" >
|
|
4
|
+
<% if object.required_indicator %>
|
|
5
|
+
<%= pb_rails("caption", props: { dark: object.dark, classname: "pb_text_input_kit_label" }) do %>
|
|
6
|
+
<%= object.label %><span style="color: #DA0014;"> *</span>
|
|
7
|
+
<% end %>
|
|
8
|
+
<% else %>
|
|
4
9
|
<%= pb_rails("caption", props: { text: object.label, dark: object.dark, classname: "pb_text_input_kit_label" }) %>
|
|
10
|
+
<% end %>
|
|
5
11
|
</label>
|
|
6
12
|
<% end %>
|
|
7
13
|
<%= content_tag(:div, class: "#{add_on_class} text_input_wrapper") do %>
|
|
@@ -18,6 +18,8 @@ module Playbook
|
|
|
18
18
|
prop :autocomplete, default: true
|
|
19
19
|
prop :disabled, type: Playbook::Props::Boolean,
|
|
20
20
|
default: false
|
|
21
|
+
prop :emoji_mask, type: Playbook::Props::Boolean,
|
|
22
|
+
default: false
|
|
21
23
|
prop :error
|
|
22
24
|
prop :inline, type: Playbook::Props::Boolean,
|
|
23
25
|
default: false
|
|
@@ -38,6 +40,8 @@ module Playbook
|
|
|
38
40
|
prop :mask, type: Playbook::Props::Enum,
|
|
39
41
|
values: ["currency", "zip_code", "postal_code", "ssn", "credit_card", "cvv", nil],
|
|
40
42
|
default: nil
|
|
43
|
+
prop :required_indicator, type: Playbook::Props::Boolean,
|
|
44
|
+
default: false
|
|
41
45
|
|
|
42
46
|
def classname
|
|
43
47
|
default_margin_bottom = margin_bottom.present? ? "" : " mb_sm"
|
|
@@ -115,7 +119,9 @@ module Playbook
|
|
|
115
119
|
def validation_data
|
|
116
120
|
fields = input_options.dig(:data) || {}
|
|
117
121
|
fields[:message] = validation_message unless validation_message.blank?
|
|
118
|
-
|
|
122
|
+
fields[:pb_input_mask] = true if mask
|
|
123
|
+
fields[:pb_emoji_mask] = true if emoji_mask
|
|
124
|
+
fields
|
|
119
125
|
end
|
|
120
126
|
|
|
121
127
|
def error_class
|
|
@@ -344,3 +344,72 @@ test('does not add autocomplete attribute otherwise', () => {
|
|
|
344
344
|
const input = within(kit).getByRole('textbox')
|
|
345
345
|
expect(input).not.toHaveAttribute("autocomplete")
|
|
346
346
|
})
|
|
347
|
+
|
|
348
|
+
test('renders required indicator asterisk when requiredIndicator is true', () => {
|
|
349
|
+
render(
|
|
350
|
+
<TextInput
|
|
351
|
+
data={{ testid: testId }}
|
|
352
|
+
label="Email Address"
|
|
353
|
+
requiredIndicator
|
|
354
|
+
/>
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
const kit = screen.getByTestId(testId)
|
|
358
|
+
const label = within(kit).getByText(/Email Address/)
|
|
359
|
+
|
|
360
|
+
expect(label).toBeInTheDocument()
|
|
361
|
+
expect(kit).toHaveTextContent('*')
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
const TextInputEmojiMask = (props) => {
|
|
365
|
+
const [value, setValue] = useState('')
|
|
366
|
+
const handleOnChange = ({ target }) => {
|
|
367
|
+
setValue(target.value)
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return (
|
|
371
|
+
<TextInput
|
|
372
|
+
emojiMask
|
|
373
|
+
onChange={handleOnChange}
|
|
374
|
+
value={value}
|
|
375
|
+
{...props}
|
|
376
|
+
/>
|
|
377
|
+
)
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
test('removes emoji characters when emojiMask is enabled', () => {
|
|
381
|
+
render(
|
|
382
|
+
<TextInputEmojiMask
|
|
383
|
+
data={{ testid: testId }}
|
|
384
|
+
/>
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
const kit = screen.getByTestId(testId)
|
|
388
|
+
const input = within(kit).getByRole('textbox')
|
|
389
|
+
|
|
390
|
+
fireEvent.change(input, { target: { value: 'Hello 👋 World 🌍' } })
|
|
391
|
+
expect(input.value).toBe('Hello World ')
|
|
392
|
+
|
|
393
|
+
fireEvent.change(input, { target: { value: '😀😂🎉' } })
|
|
394
|
+
expect(input.value).toBe('')
|
|
395
|
+
|
|
396
|
+
fireEvent.change(input, { target: { value: 'Hello World' } })
|
|
397
|
+
expect(input.value).toBe('Hello World')
|
|
398
|
+
})
|
|
399
|
+
|
|
400
|
+
test('allows accented characters when emojiMask is enabled', () => {
|
|
401
|
+
render(
|
|
402
|
+
<TextInputEmojiMask
|
|
403
|
+
data={{ testid: testId }}
|
|
404
|
+
/>
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
const kit = screen.getByTestId(testId)
|
|
408
|
+
const input = within(kit).getByRole('textbox')
|
|
409
|
+
|
|
410
|
+
fireEvent.change(input, { target: { value: 'Café résumé naïve' } })
|
|
411
|
+
expect(input.value).toBe('Café résumé naïve')
|
|
412
|
+
|
|
413
|
+
fireEvent.change(input, { target: { value: 'àëǒüñ' } })
|
|
414
|
+
expect(input.value).toBe('àëǒüñ')
|
|
415
|
+
})
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* eslint-disable react-hooks/rules-of-hooks */
|
|
2
2
|
|
|
3
|
-
import React, { forwardRef, useEffect, useRef } from 'react'
|
|
3
|
+
import React, { forwardRef, useEffect, useRef, ChangeEvent, ClipboardEvent } from 'react'
|
|
4
4
|
import classnames from 'classnames'
|
|
5
5
|
|
|
6
6
|
import PbTextarea from '.'
|
|
@@ -14,6 +14,8 @@ import Caption from '../pb_caption/_caption'
|
|
|
14
14
|
import Flex from '../pb_flex/_flex'
|
|
15
15
|
import FlexItem from '../pb_flex/_flex_item'
|
|
16
16
|
|
|
17
|
+
import { stripEmojisForPaste, applyEmojiMask } from '../utilities/emojiMask'
|
|
18
|
+
|
|
17
19
|
type TextareaProps = {
|
|
18
20
|
aria?: {[key: string]: string},
|
|
19
21
|
characterCount?: string,
|
|
@@ -21,6 +23,7 @@ type TextareaProps = {
|
|
|
21
23
|
children?: React.ReactChild[],
|
|
22
24
|
data?: {[key: string]: string},
|
|
23
25
|
disabled?: boolean,
|
|
26
|
+
emojiMask?: boolean,
|
|
24
27
|
error?: string,
|
|
25
28
|
htmlOptions?: {[key: string]: string | number | boolean | (() => void)},
|
|
26
29
|
id?: string,
|
|
@@ -45,6 +48,7 @@ const Textarea = ({
|
|
|
45
48
|
children,
|
|
46
49
|
data = {},
|
|
47
50
|
disabled,
|
|
51
|
+
emojiMask = false,
|
|
48
52
|
htmlOptions = {},
|
|
49
53
|
inline = false,
|
|
50
54
|
resize = 'none',
|
|
@@ -67,6 +71,37 @@ const Textarea = ({
|
|
|
67
71
|
}
|
|
68
72
|
})
|
|
69
73
|
|
|
74
|
+
const handleChange = (e: ChangeEvent<HTMLTextAreaElement>) => {
|
|
75
|
+
// Apply emoji mask if enabled using centralized helper
|
|
76
|
+
if (emojiMask) {
|
|
77
|
+
applyEmojiMask(e.target)
|
|
78
|
+
}
|
|
79
|
+
onChange(e)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Handle paste event for emoji mask - updates textarea value, cursor position, and calls onChange
|
|
83
|
+
const handlePaste = (e: ClipboardEvent<HTMLTextAreaElement>) => {
|
|
84
|
+
if (emojiMask) {
|
|
85
|
+
const pastedText = e.clipboardData.getData('text')
|
|
86
|
+
const filteredText = stripEmojisForPaste(pastedText)
|
|
87
|
+
|
|
88
|
+
if (pastedText !== filteredText) {
|
|
89
|
+
e.preventDefault()
|
|
90
|
+
const textarea = e.currentTarget
|
|
91
|
+
const start = textarea.selectionStart || 0
|
|
92
|
+
const end = textarea.selectionEnd || 0
|
|
93
|
+
const currentValue = textarea.value
|
|
94
|
+
const newValue = currentValue.slice(0, start) + filteredText + currentValue.slice(end)
|
|
95
|
+
const newCursorPosition = start + filteredText.length
|
|
96
|
+
|
|
97
|
+
textarea.value = newValue
|
|
98
|
+
textarea.selectionStart = textarea.selectionEnd = newCursorPosition
|
|
99
|
+
|
|
100
|
+
onChange({ ...e, target: textarea, currentTarget: textarea } as unknown as ChangeEvent<HTMLTextAreaElement>)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
70
105
|
const errorClass = error ? 'error' : null
|
|
71
106
|
const inlineClass = inline ? 'inline' : ''
|
|
72
107
|
const resizeClass = `resize_${resize}`
|
|
@@ -94,7 +129,8 @@ const Textarea = ({
|
|
|
94
129
|
<textarea
|
|
95
130
|
disabled={disabled}
|
|
96
131
|
name={name}
|
|
97
|
-
onChange={onChange}
|
|
132
|
+
onChange={emojiMask ? handleChange : onChange}
|
|
133
|
+
onPaste={emojiMask ? handlePaste : undefined}
|
|
98
134
|
placeholder={placeholder}
|
|
99
135
|
ref={ref}
|
|
100
136
|
required={required}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
|
+
|
|
3
|
+
import Textarea from '../../pb_textarea/_textarea'
|
|
4
|
+
|
|
5
|
+
const TextareaEmojiMask = (props) => {
|
|
6
|
+
const [basicValue, setBasicValue] = useState('')
|
|
7
|
+
|
|
8
|
+
return (
|
|
9
|
+
<div>
|
|
10
|
+
<Textarea
|
|
11
|
+
emojiMask
|
|
12
|
+
label="Emoji Mask"
|
|
13
|
+
onChange={({ target }) => setBasicValue(target.value)}
|
|
14
|
+
placeholder="Try typing or pasting emojis..."
|
|
15
|
+
value={basicValue}
|
|
16
|
+
{...props}
|
|
17
|
+
/>
|
|
18
|
+
</div>
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export default TextareaEmojiMask
|
|
23
|
+
|
|
24
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Use the `emojiMask` / `emoji_mask` prop to prevent users from entering emoji characters (🐸 🐈 🏄♂️) in typed or pasted content. It allows accented characters and other non-ASCII letters (é, ü, 文).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
Textarea w/ Error shows that the
|
|
1
|
+
Textarea w/ Error shows that the input must be filled out (i.e. when used in a form it signals a user to fix an error).
|
|
@@ -7,6 +7,7 @@ examples:
|
|
|
7
7
|
- textarea_error: Textarea w/ Error
|
|
8
8
|
- textarea_character_counter: Character Counter
|
|
9
9
|
- textarea_inline: Inline
|
|
10
|
+
- textarea_emoji_mask: Emoji Mask
|
|
10
11
|
|
|
11
12
|
react:
|
|
12
13
|
- textarea_default: Default
|
|
@@ -15,6 +16,7 @@ examples:
|
|
|
15
16
|
- textarea_error: Textarea w/ Error
|
|
16
17
|
- textarea_character_counter: Character Counter
|
|
17
18
|
- textarea_inline: Inline
|
|
19
|
+
- textarea_emoji_mask: Emoji Mask
|
|
18
20
|
|
|
19
21
|
swift:
|
|
20
22
|
- textarea_default_swift: Default
|
|
@@ -4,3 +4,4 @@ export { default as TextareaCustom } from './_textarea_custom.jsx'
|
|
|
4
4
|
export { default as TextareaError } from './_textarea_error.jsx'
|
|
5
5
|
export { default as TextareaCharacterCounter } from './_textarea_character_counter.jsx'
|
|
6
6
|
export { default as TextareaInline } from './_textarea_inline.jsx'
|
|
7
|
+
export { default as TextareaEmojiMask } from './_textarea_emoji_mask.jsx'
|