playbook_ui 15.7.0.pre.alpha.PLAY2675dropdownquickpickcustomquickpickdates13330 → 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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/pb_dropdown/_dropdown.tsx +1 -13
  3. data/app/pb_kits/playbook/pb_dropdown/docs/example.yml +0 -2
  4. data/app/pb_kits/playbook/pb_dropdown/docs/index.js +1 -2
  5. data/app/pb_kits/playbook/pb_dropdown/dropdown.rb +1 -6
  6. data/app/pb_kits/playbook/pb_dropdown/dropdown.test.jsx +0 -121
  7. data/app/pb_kits/playbook/pb_dropdown/quickpick/index.ts +9 -85
  8. data/app/pb_kits/playbook/pb_dropdown/quickpick_helper.rb +2 -83
  9. data/app/pb_kits/playbook/pb_form/docs/_form_form_with.html.erb +2 -2
  10. data/app/pb_kits/playbook/pb_text_input/_text_input.tsx +41 -3
  11. data/app/pb_kits/playbook/pb_text_input/docs/_text_input_emoji_mask.html.erb +7 -0
  12. data/app/pb_kits/playbook/pb_text_input/docs/_text_input_emoji_mask.jsx +24 -0
  13. data/app/pb_kits/playbook/pb_text_input/docs/_text_input_emoji_mask.md +2 -0
  14. data/app/pb_kits/playbook/pb_text_input/docs/example.yml +2 -0
  15. data/app/pb_kits/playbook/pb_text_input/docs/index.js +1 -0
  16. data/app/pb_kits/playbook/pb_text_input/index.js +49 -8
  17. data/app/pb_kits/playbook/pb_text_input/text_input.rb +5 -1
  18. data/app/pb_kits/playbook/pb_text_input/text_input.test.js +53 -0
  19. data/app/pb_kits/playbook/pb_textarea/_textarea.tsx +38 -2
  20. data/app/pb_kits/playbook/pb_textarea/docs/_textarea_emoji_mask.html.erb +5 -0
  21. data/app/pb_kits/playbook/pb_textarea/docs/_textarea_emoji_mask.jsx +24 -0
  22. data/app/pb_kits/playbook/pb_textarea/docs/_textarea_emoji_mask.md +1 -0
  23. data/app/pb_kits/playbook/pb_textarea/docs/example.yml +2 -0
  24. data/app/pb_kits/playbook/pb_textarea/docs/index.js +1 -0
  25. data/app/pb_kits/playbook/pb_textarea/index.ts +62 -5
  26. data/app/pb_kits/playbook/pb_textarea/textarea.html.erb +1 -0
  27. data/app/pb_kits/playbook/pb_textarea/textarea.rb +8 -0
  28. data/app/pb_kits/playbook/pb_textarea/textarea.test.js +57 -2
  29. data/app/pb_kits/playbook/utilities/emojiMask.ts +42 -0
  30. data/dist/chunks/{_typeahead-Ckz1ce-2.js → _typeahead-CSCNg6cp.js} +2 -2
  31. data/dist/chunks/{lib-DxDBrGZX.js → lib-DxCgrqqG.js} +1 -1
  32. data/dist/chunks/vendor.js +3 -3
  33. data/dist/playbook-rails-react-bindings.js +1 -1
  34. data/dist/playbook-rails.js +1 -1
  35. data/lib/playbook/forms/builder/form_field_builder.rb +2 -0
  36. data/lib/playbook/version.rb +1 -1
  37. metadata +11 -8
  38. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_quickpick_custom.jsx +0 -56
  39. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_quickpick_custom.md +0 -10
  40. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_quickpick_custom_rails.html.erb +0 -64
  41. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_quickpick_custom_rails.md +0 -10
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b48fc113950407e6045ecd779bef70a624758ae4d2c9b542c38130a31a35c68f
4
- data.tar.gz: fcd6bf8c37129585b0acca7a0d00e6ce54de65bb68fdba3395608883f7ae9011
3
+ metadata.gz: 5671bd3842cbc616980cca133af25a4d32013d8bcf16c2472d5e5cda9d122943
4
+ data.tar.gz: 0c2ce0cd5554eb290beb880f6ad0ce84207c40ceaa428b947c6b9566947388ba
5
5
  SHA512:
6
- metadata.gz: c0af7f993a8f9b64c9fd73d264e7aba87f692387c5c79a798728608c9c487e106270f5e7d1352f074b34bd0523fc15a127cfad5b13c2d6349e30d63fe4e65932
7
- data.tar.gz: bb66651f32b7e9b18f83fb47086580b168691b3000f23f98996a564f37f41d8d3012c5e516ab51cd8cc8843aff71a701a3d0dedf0765e5d939e5f11125bab045
6
+ metadata.gz: 769539b8947c986ca86ad41720be93617da036d335de3ffe9945afe26fcb05bc543d420124486ed2825b85e46ad83bb183ac922a9cbc3f70a1f318f987a5c53b
7
+ data.tar.gz: af02627b473d11689bf92429bf6bfe6dd7a11bfc443f641c48749ef56faeb0358f12779049e6b72ccb8b6486cca3a211ed7572113cbf65751dc2b9e96a4a7a79
@@ -20,23 +20,12 @@ import {
20
20
  handleClickOutside,
21
21
  } from "./utilities";
22
22
 
23
- type CustomQuickPickDate = {
24
- label: string;
25
- value: string[] | { timePeriod: string; amount: number };
26
- };
27
-
28
- type CustomQuickPickDates = {
29
- override?: boolean;
30
- dates: CustomQuickPickDate[];
31
- };
32
-
33
23
  type DropdownProps = {
34
24
  aria?: { [key: string]: string };
35
25
  autocomplete?: boolean;
36
26
  blankSelection?: string;
37
27
  children?: React.ReactChild[] | React.ReactChild | React.ReactElement[];
38
28
  className?: string;
39
- customQuickPickDates?: CustomQuickPickDates;
40
29
  formPillProps?: GenericObject;
41
30
  dark?: boolean;
42
31
  data?: { [key: string]: string };
@@ -74,7 +63,6 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
74
63
  blankSelection = '',
75
64
  children,
76
65
  className,
77
- customQuickPickDates,
78
66
  dark = false,
79
67
  data = {},
80
68
  defaultValue = {},
@@ -107,7 +95,7 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
107
95
  // ------------- Quick Pick ---------------------------------
108
96
  // Use QuickPick options when variant is "quickpick"
109
97
  const dropdownOptions = variant === "quickpick"
110
- ? getQuickPickOptions(rangeEndsToday, customQuickPickDates)
98
+ ? getQuickPickOptions(rangeEndsToday)
111
99
  : (options || []);
112
100
  // ----------------------------------------------------------
113
101
 
@@ -25,7 +25,6 @@ examples:
25
25
  - dropdown_quickpick_rails: Quick Pick Variant
26
26
  - dropdown_quickpick_range_end_rails: Quick Pick Variant (Range Ends Today)
27
27
  - dropdown_quickpick_default_dates: Quick Pick Variant (Default Dates)
28
- - dropdown_quickpick_custom_rails: Quick Pick Variant (Custom Quick Pick Dates)
29
28
  - dropdown_quickpick_with_date_pickers_rails: Quick Pick with Date Pickers
30
29
  - dropdown_quickpick_with_date_pickers_default_rails: Quick Pick with Date Pickers (Default Value)
31
30
 
@@ -58,7 +57,6 @@ examples:
58
57
  - dropdown_quickpick: Quick Pick Variant
59
58
  - dropdown_quickpick_range_end: Quick Pick Variant (Range Ends Today)
60
59
  - dropdown_quickpick_default_dates: Quick Pick Variant (Default Dates)
61
- - dropdown_quickpick_custom: Quick Pick Variant (Custom Quick Pick Dates)
62
60
  - dropdown_quickpick_with_date_pickers: Quick Pick Variant with Date Pickers
63
61
 
64
62
 
@@ -26,5 +26,4 @@ export {default as DropdownWithCustomActiveStyleOptions} from './_dropdown_with_
26
26
  export { default as DropdownQuickpick } from './_dropdown_quickpick.jsx'
27
27
  export { default as DropdownQuickpickRangeEnd } from './_dropdown_quickpick_range_end.jsx'
28
28
  export { default as DropdownQuickpickDefaultDates } from './_dropdown_quickpick_default_dates.jsx'
29
- export { default as DropdownQuickpickWithDatePickers } from './_dropdown_quickpick_with_date_pickers.jsx'
30
- export { default as DropdownQuickpickCustom } from './_dropdown_quickpick_custom.jsx'
29
+ export { default as DropdownQuickpickWithDatePickers } from './_dropdown_quickpick_with_date_pickers.jsx'
@@ -15,8 +15,6 @@ module Playbook
15
15
  prop :default_value
16
16
  prop :blank_selection, type: Playbook::Props::String,
17
17
  default: ""
18
- prop :custom_quick_pick_dates, type: Playbook::Props::HashProp,
19
- default: {}
20
18
  prop :variant, type: Playbook::Props::Enum,
21
19
  values: %w[default subtle quickpick],
22
20
  default: "default"
@@ -96,10 +94,7 @@ module Playbook
96
94
  end
97
95
 
98
96
  def quickpick_options
99
- QuickpickHelper.get_quickpick_options(
100
- range_ends_today: range_ends_today,
101
- custom_quick_pick_dates: custom_quick_pick_dates
102
- )
97
+ QuickpickHelper.get_quickpick_options(range_ends_today: range_ends_today)
103
98
  end
104
99
  end
105
100
  end
@@ -539,125 +539,4 @@ test("quickpick option values are Date objects", () => {
539
539
  expect(endDate.getTime()).not.toBeNaN()
540
540
 
541
541
  expect(endDate.getTime()).toBeGreaterThanOrEqual(startDate.getTime())
542
- })
543
-
544
- test("customQuickPickDates overrides default options when override is true or undefined", () => {
545
- render(
546
- <Dropdown
547
- customQuickPickDates={{
548
- dates: [
549
- {
550
- label: "Last 15 months",
551
- value: { timePeriod: "months", amount: 15 }
552
- },
553
- {
554
- label: "Custom Range",
555
- value: ["06/01/2022", "06/07/2022"]
556
- }
557
- ]
558
- }}
559
- data={{ testid: testId }}
560
- variant="quickpick"
561
- />
562
- )
563
-
564
- const kit = screen.getByTestId(testId)
565
- const options = kit.querySelectorAll('.pb_dropdown_option_list')
566
-
567
- expect(options.length).toBe(2)
568
- expect(options[0]).toHaveTextContent("Last 15 months")
569
- expect(options[1]).toHaveTextContent("Custom Range")
570
- })
571
-
572
- test("customQuickPickDates appends to defaults when override is false", () => {
573
- render(
574
- <Dropdown
575
- customQuickPickDates={{
576
- override: false,
577
- dates: [
578
- {
579
- label: "Custom Option",
580
- value: { timePeriod: "days", amount: 30 }
581
- }
582
- ]
583
- }}
584
- data={{ testid: testId }}
585
- variant="quickpick"
586
- />
587
- )
588
-
589
- const kit = screen.getByTestId(testId)
590
- const options = kit.querySelectorAll('.pb_dropdown_option_list')
591
-
592
- expect(options.length).toBe(11)
593
- expect(options[0]).toHaveTextContent("Today")
594
- expect(options[10]).toHaveTextContent("Custom Option")
595
- })
596
-
597
- test("customQuickPickDates with explicit date array returns correct values", () => {
598
- const onSelectMock = jest.fn()
599
-
600
- render(
601
- <Dropdown
602
- customQuickPickDates={{
603
- dates: [
604
- {
605
- label: "First Week of June 2022",
606
- value: ["06/01/2022", "06/07/2022"]
607
- }
608
- ]
609
- }}
610
- data={{ testid: testId }}
611
- onSelect={onSelectMock}
612
- variant="quickpick"
613
- />
614
- )
615
-
616
- const kit = screen.getByTestId(testId)
617
- const option = kit.querySelector('.pb_dropdown_option_list')
618
-
619
- fireEvent.click(option)
620
-
621
- const selectedItem = onSelectMock.mock.calls[0][0]
622
-
623
- expect(selectedItem.label).toBe("First Week of June 2022")
624
- expect(Array.isArray(selectedItem.value)).toBe(true)
625
- expect(selectedItem.value.length).toBe(2)
626
- })
627
-
628
- test("customQuickPickDates with timePeriod calculates dates correctly", () => {
629
- const onSelectMock = jest.fn()
630
-
631
- render(
632
- <Dropdown
633
- customQuickPickDates={{
634
- dates: [
635
- {
636
- label: "Last 7 days",
637
- value: { timePeriod: "days", amount: 7 }
638
- }
639
- ]
640
- }}
641
- data={{ testid: testId }}
642
- onSelect={onSelectMock}
643
- variant="quickpick"
644
- />
645
- )
646
-
647
- const kit = screen.getByTestId(testId)
648
- const option = kit.querySelector('.pb_dropdown_option_list')
649
-
650
- fireEvent.click(option)
651
-
652
- const selectedItem = onSelectMock.mock.calls[0][0]
653
-
654
- expect(selectedItem.label).toBe("Last 7 days")
655
- expect(Array.isArray(selectedItem.value)).toBe(true)
656
-
657
- const [startDate, endDate] = selectedItem.value
658
- expect(startDate instanceof Date).toBe(true)
659
- expect(endDate instanceof Date).toBe(true)
660
-
661
- const today = new Date()
662
- expect(endDate.toDateString()).toBe(today.toDateString())
663
542
  })
@@ -11,78 +11,8 @@ type QuickPickOption = {
11
11
  id?: string;
12
12
  };
13
13
 
14
- type CustomQuickPickDate = {
15
- label: string;
16
- value: string[] | { timePeriod: string; amount: number };
17
- };
18
-
19
- type CustomQuickPickDates = {
20
- override?: boolean;
21
- dates: CustomQuickPickDate[];
22
- };
23
-
24
- // Helper function to calculate date range from timePeriod and amount
25
- const calculateDateRange = (timePeriod: string, amount: number): Date[] => {
26
- const endDate = new Date();
27
- const startDate = new Date();
28
-
29
- switch (timePeriod) {
30
- case 'days':
31
- startDate.setDate(endDate.getDate() - amount);
32
- break;
33
- case 'weeks':
34
- startDate.setDate(endDate.getDate() - (amount * 7));
35
- break;
36
- case 'months':
37
- startDate.setMonth(endDate.getMonth() - amount);
38
- break;
39
- case 'quarters':
40
- startDate.setMonth(endDate.getMonth() - (amount * 3));
41
- break;
42
- case 'years':
43
- startDate.setFullYear(endDate.getFullYear() - amount);
44
- break;
45
- default:
46
- throw new Error('Invalid time period');
47
- }
48
- return [startDate, endDate];
49
- };
50
-
51
- // Helper to format date for display
52
- const formatDate = (date: Date) => {
53
- const day = String(date.getDate()).padStart(2, "0");
54
- const month = String(date.getMonth() + 1).padStart(2, "0");
55
- const year = date.getFullYear();
56
-
57
- return `${month}/${day}/${year}`;
58
- };
59
-
60
- // Helper to create a QuickPickOption from a custom date
61
- const createOptionFromCustomDate = (customDate: CustomQuickPickDate): QuickPickOption => {
62
- let dateRange: Date[];
63
-
64
- if (Array.isArray(customDate.value)) {
65
- // Explicit date range as string array
66
- dateRange = customDate.value.map((dateStr: string) => new Date(dateStr));
67
- } else {
68
- // Calculate from timePeriod and amount
69
- dateRange = calculateDateRange(
70
- customDate.value.timePeriod,
71
- customDate.value.amount
72
- );
73
- }
74
-
75
- return {
76
- label: customDate.label,
77
- value: dateRange,
78
- formattedStartDate: formatDate(dateRange[0]),
79
- formattedEndDate: formatDate(dateRange[1]),
80
- id: `quickpick-${customDate.label.toLowerCase().replace(/\s+/g, '-')}`,
81
- };
82
- };
83
-
84
14
  // Helper to get QuickPick options with date ranges
85
- const getQuickPickOptions = (rangeEndsToday = false, customQuickPickDates?: CustomQuickPickDates): QuickPickOption[] => {
15
+ const getQuickPickOptions = (rangeEndsToday = false): QuickPickOption[] => {
86
16
  const today = new Date();
87
17
  const yesterday = DateTime.getYesterdayDate(new Date());
88
18
 
@@ -106,7 +36,14 @@ const getQuickPickOptions = (rangeEndsToday = false, customQuickPickDates?: Cust
106
36
  const lastYearStartDate = DateTime.getPreviousYearStartDate(new Date());
107
37
  const lastYearEndDate = DateTime.getPreviousYearEndDate(new Date());
108
38
 
109
- const defaultOptions: QuickPickOption[] = [
39
+ const formatDate = (date: Date) => {
40
+ const day = String(date.getDate()).padStart(2, "0")
41
+ const month = String(date.getMonth() + 1).padStart(2, "0")
42
+ const year = date.getFullYear()
43
+
44
+ return `${month}/${day}/${year}`
45
+ }
46
+ return [
110
47
  { label: 'Today', value: [today, today], id: 'quickpick-today' },
111
48
  { label: 'Yesterday', value: [yesterday, yesterday], formattedStartDate: `${formatDate(yesterday)}`, formattedEndDate: `${formatDate(yesterday)}`, id: 'quickpick-yesterday' },
112
49
  { label: 'This Week', value: [thisWeekStartDate, thisWeekEndDate], formattedStartDate: `${formatDate(thisWeekStartDate)}`, formattedEndDate: `${formatDate(thisWeekEndDate)}`, id: 'quickpick-this-week' },
@@ -118,19 +55,6 @@ const getQuickPickOptions = (rangeEndsToday = false, customQuickPickDates?: Cust
118
55
  { label: 'Last Quarter', value: [lastQuarterStartDate, lastQuarterEndDate], formattedStartDate: `${formatDate(lastQuarterStartDate)}`, formattedEndDate: `${formatDate(lastQuarterEndDate)}`, id: 'quickpick-last-quarter' },
119
56
  { label: 'Last Year', value: [lastYearStartDate, lastYearEndDate], formattedStartDate: `${formatDate(lastYearStartDate)}`, formattedEndDate: `${formatDate(lastYearEndDate)}`, id: 'quickpick-last-year' },
120
57
  ];
121
-
122
- // Handle customQuickPickDates and override logic
123
- if (customQuickPickDates && customQuickPickDates.dates && customQuickPickDates.dates.length > 0) {
124
- const customOptions = customQuickPickDates.dates.map(createOptionFromCustomDate);
125
-
126
- if (customQuickPickDates.override === false) {
127
- return [...defaultOptions, ...customOptions];
128
- }
129
-
130
- return customOptions;
131
- }
132
-
133
- return defaultOptions;
134
58
  };
135
59
 
136
60
  export default getQuickPickOptions
@@ -4,31 +4,7 @@ module Playbook
4
4
  module PbDropdown
5
5
  module QuickpickHelper
6
6
  class << self
7
- def get_quickpick_options(range_ends_today: false, custom_quick_pick_dates: {})
8
- default_options = build_default_options(range_ends_today)
9
-
10
- # Handle custom_quick_pick_dates
11
- return default_options if custom_quick_pick_dates.blank?
12
-
13
- dates = custom_quick_pick_dates[:dates] || custom_quick_pick_dates["dates"]
14
- return default_options if dates.blank?
15
-
16
- custom_options = dates.map { |date_config| build_custom_option(date_config) }
17
-
18
- # Override logic
19
- override = custom_quick_pick_dates[:override]
20
- override = custom_quick_pick_dates["override"] if override.nil?
21
-
22
- if override == false
23
- default_options + custom_options
24
- else
25
- custom_options
26
- end
27
- end
28
-
29
- private
30
-
31
- def build_default_options(range_ends_today)
7
+ def get_quickpick_options(range_ends_today: false)
32
8
  today = Date.today
33
9
  yesterday = today - 1.day
34
10
 
@@ -66,64 +42,7 @@ module Playbook
66
42
  ]
67
43
  end
68
44
 
69
- def build_custom_option(date_config)
70
- label = date_config[:label] || date_config["label"]
71
- value = date_config[:value] || date_config["value"]
72
-
73
- date_range = calculate_date_range(value)
74
- start_date = date_range[0]
75
- end_date = date_range[1]
76
-
77
- {
78
- id: "quickpick-#{label.downcase.gsub(/\s+/, '-')}",
79
- label: label,
80
- value: [start_date.to_s, end_date.to_s],
81
- formatted_start_date: format_date(start_date),
82
- formatted_end_date: format_date(end_date),
83
- }
84
- end
85
-
86
- def calculate_date_range(value)
87
- # Parse date strings if value is an array
88
- if value.is_a?(Array)
89
- [parse_date_string(value[0]), parse_date_string(value[1])]
90
- else
91
- # Calculate date range from time_period and amount
92
- time_period = value[:time_period] || value["time_period"] || value[:timePeriod] || value["timePeriod"]
93
- amount = value[:amount] || value["amount"]
94
-
95
- end_date = Date.today
96
- start_date = calculate_start_date(time_period, amount, end_date)
97
-
98
- [start_date, end_date]
99
- end
100
- end
101
-
102
- def parse_date_string(date_str)
103
- # Handle US date format (MM/DD/YYYY) because Ruby's Date.parse defaults to European format (DD/MM/YYYY)
104
- if date_str.include?("/")
105
- Date.strptime(date_str, "%m/%d/%Y")
106
- else
107
- Date.parse(date_str)
108
- end
109
- end
110
-
111
- def calculate_start_date(time_period, amount, end_date)
112
- case time_period.to_s
113
- when "days"
114
- end_date - amount.days
115
- when "weeks"
116
- end_date - amount.weeks
117
- when "months"
118
- end_date - amount.months
119
- when "quarters"
120
- end_date - (amount * 3).months
121
- when "years"
122
- end_date - amount.years
123
- else
124
- raise ArgumentError, "Invalid time period: #{time_period}"
125
- end
126
- end
45
+ private
127
46
 
128
47
  def format_date(date)
129
48
  date.strftime("%m/%d/%Y")
@@ -90,7 +90,7 @@
90
90
 
91
91
  <%= pb_form_with(scope: :example, url: "", method: :get) do |form| %>
92
92
  <%= form.typeahead :example_typeahead, props: { data: { typeahead_example1: true, user: {} }, label: true, placeholder: "Search for a user" } %>
93
- <%= form.text_field :example_text_field, props: { label: true } %>
93
+ <%= form.text_field :example_text_field, props: { label: "emoji mask demo text input", emoji_mask: true } %>
94
94
  <%= form.text_field :example_text_field_2, props: { label: "Text Field Custom Label" } %>
95
95
  <%= form.phone_number_field :example_phone_number_field, props: { label: "Example phone field", hidden_inputs: true } %>
96
96
  <%= form.email_field :example_email_field, props: { label: true } %>
@@ -98,7 +98,7 @@
98
98
  <%= form.search_field :example_search_field, props: { label: true } %>
99
99
  <%= form.password_field :example_password_field, props: { label: true } %>
100
100
  <%= form.url_field :example_url_field, props: { label: true } %>
101
- <%= form.text_area :example_text_area, props: { label: true } %>
101
+ <%= form.text_area :example_text_area, props: { label: "emoji mask demo textarea", emoji_mask: true } %>
102
102
  <%= form.dropdown_field :example_dropdown, props: { label: true, options: example_dropdown_options } %>
103
103
  <%= form.dropdown_field :example_dropdown_multi, props: { label: true, options: example_dropdown_options, multi_select: true } %>
104
104
  <%= form.select :example_select, [ ["Yes", 1], ["No", 2] ], props: { label: true } %>
@@ -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'
@@ -12,6 +12,7 @@ import Icon from '../pb_icon/_icon'
12
12
  import colors from '../tokens/exports/_colors.module.scss'
13
13
 
14
14
  import { INPUTMASKS } from './inputMask'
15
+ import { stripEmojisForPaste, applyEmojiMask } from '../utilities/emojiMask'
15
16
 
16
17
  type TextInputProps = {
17
18
  aria?: { [key: string]: string },
@@ -19,6 +20,7 @@ type TextInputProps = {
19
20
  data?: { [key: string]: string },
20
21
  dark?: boolean,
21
22
  disabled?: boolean,
23
+ emojiMask?: boolean,
22
24
  error?: string,
23
25
  htmlOptions?: {[key: string]: string | number | boolean | (() => void)},
24
26
  id?: string,
@@ -49,6 +51,7 @@ const TextInput = (props: TextInputProps, ref: React.LegacyRef<HTMLInputElement>
49
51
  dark = false,
50
52
  data = {},
51
53
  disabled,
54
+ emojiMask = false,
52
55
  error,
53
56
  htmlOptions = {},
54
57
  id,
@@ -102,6 +105,11 @@ const TextInput = (props: TextInputProps, ref: React.LegacyRef<HTMLInputElement>
102
105
  const isMaskedInput = mask && mask in INPUTMASKS
103
106
 
104
107
  const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
108
+ // Apply emoji mask if enabled using centralized helper
109
+ if (emojiMask) {
110
+ applyEmojiMask(e.target)
111
+ }
112
+
105
113
  if (isMaskedInput) {
106
114
  const inputValue = e.target.value
107
115
 
@@ -134,6 +142,29 @@ const TextInput = (props: TextInputProps, ref: React.LegacyRef<HTMLInputElement>
134
142
  }
135
143
  }
136
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
+
137
168
  const childInput = children ? children.type === "input" : undefined
138
169
 
139
170
  let formattedValue;
@@ -145,10 +176,16 @@ const TextInput = (props: TextInputProps, ref: React.LegacyRef<HTMLInputElement>
145
176
 
146
177
  const errorId = error ? `${id}-error` : undefined
147
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
+
148
185
  const textInput = (
149
186
  childInput ? React.cloneElement(children, { className: "text_input" }) :
150
187
  (<input
151
- {...domSafeProps(props)}
188
+ {...domSafeProps(domProps)}
152
189
  aria-describedby={errorId}
153
190
  aria-invalid={!!error}
154
191
  autoComplete={typeof autoComplete === "string" ? autoComplete : ( autoComplete ? undefined : "off" )}
@@ -157,7 +194,8 @@ const TextInput = (props: TextInputProps, ref: React.LegacyRef<HTMLInputElement>
157
194
  id={id}
158
195
  key={id}
159
196
  name={name}
160
- onChange={isMaskedInput ? handleChange : onChange}
197
+ onChange={shouldUseCustomHandler ? handleChange : onChange}
198
+ onPaste={emojiMask ? handlePaste : undefined}
161
199
  pattern={isMaskedInput ? INPUTMASKS[mask]?.pattern : undefined}
162
200
  placeholder={placeholder || (isMaskedInput ? INPUTMASKS[mask]?.placeholder : undefined)}
163
201
  ref={ref}
@@ -0,0 +1,7 @@
1
+ <%= pb_rails("text_input", props: {
2
+ emoji_mask: true,
3
+ label: "Emoji Mask",
4
+ placeholder: "Try typing or pasting emojis...",
5
+ }) %>
6
+
7
+
@@ -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
+
@@ -0,0 +1,2 @@
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 (é, ü, 文).
2
+
@@ -11,6 +11,7 @@ examples:
11
11
  - text_input_mask: Mask
12
12
  - text_input_autocomplete: Autocomplete
13
13
  - text_input_required_indicator: Required Indicator
14
+ - text_input_emoji_mask: Emoji Mask
14
15
 
15
16
 
16
17
  react:
@@ -25,6 +26,7 @@ examples:
25
26
  - text_input_sanitize: Sanitized Masked Input
26
27
  - text_input_autocomplete: Autocomplete
27
28
  - text_input_required_indicator: Required Indicator
29
+ - text_input_emoji_mask: Emoji Mask
28
30
 
29
31
 
30
32
  swift:
@@ -9,3 +9,4 @@ 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
11
  export { default as TextInputRequiredIndicator } from './_text_input_required_indicator.jsx'
12
+ export { default as TextInputEmojiMask } from './_text_input_emoji_mask.jsx'