playbook_ui 18.2.0.pre.rc.2 → 18.2.0

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 (116) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/pb_background/_background.tsx +3 -1
  3. data/app/pb_kits/playbook/pb_background/kit.schema.json +1 -0
  4. data/app/pb_kits/playbook/pb_caption/_caption.tsx +3 -1
  5. data/app/pb_kits/playbook/pb_caption/kit.schema.json +1 -0
  6. data/app/pb_kits/playbook/pb_container/_container.tsx +3 -1
  7. data/app/pb_kits/playbook/pb_container/kit.schema.json +1 -0
  8. data/app/pb_kits/playbook/pb_currency/_currency.tsx +3 -1
  9. data/app/pb_kits/playbook/pb_currency/kit.schema.json +1 -0
  10. data/app/pb_kits/playbook/pb_dashboard_value/_dashboard_value.tsx +3 -1
  11. data/app/pb_kits/playbook/pb_dashboard_value/kit.schema.json +1 -0
  12. data/app/pb_kits/playbook/pb_detail/_detail.tsx +3 -1
  13. data/app/pb_kits/playbook/pb_detail/kit.schema.json +1 -0
  14. data/app/pb_kits/playbook/pb_dropdown/_dropdown.tsx +6 -5
  15. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_quickpick_custom.md +7 -2
  16. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_quickpick_custom_rails.md +7 -2
  17. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_quickpick_enable_time.html.erb +33 -0
  18. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_quickpick_enable_time.jsx +42 -0
  19. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_quickpick_enable_time.md +24 -0
  20. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_highlight.jsx +101 -0
  21. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_highlight.md +3 -0
  22. data/app/pb_kits/playbook/pb_dropdown/docs/example.yml +3 -0
  23. data/app/pb_kits/playbook/pb_dropdown/docs/index.js +2 -0
  24. data/app/pb_kits/playbook/pb_dropdown/index.js +19 -10
  25. data/app/pb_kits/playbook/pb_dropdown/quickpick/index.ts +60 -18
  26. data/app/pb_kits/playbook/pb_dropdown/quickpick_helper.rb +92 -3
  27. data/app/pb_kits/playbook/pb_dropdown/utilities/index.ts +8 -1
  28. data/app/pb_kits/playbook/pb_dropdown/utilities/quickpickDateSync.ts +137 -0
  29. data/app/pb_kits/playbook/pb_filter/Filter/InteractiveFilter.tsx +1 -1
  30. data/app/pb_kits/playbook/pb_highlight/_highlight.tsx +3 -1
  31. data/app/pb_kits/playbook/pb_highlight/kit.schema.json +1 -0
  32. data/app/pb_kits/playbook/pb_icon_stat_value/_icon_stat_value.tsx +3 -1
  33. data/app/pb_kits/playbook/pb_icon_stat_value/kit.schema.json +1 -0
  34. data/app/pb_kits/playbook/pb_icon_value/_icon_value.tsx +3 -1
  35. data/app/pb_kits/playbook/pb_icon_value/kit.schema.json +1 -0
  36. data/app/pb_kits/playbook/pb_label_value/_label_value.tsx +3 -1
  37. data/app/pb_kits/playbook/pb_label_value/kit.schema.json +1 -0
  38. data/app/pb_kits/playbook/pb_map/docs/_map_default.md +3 -3
  39. data/app/pb_kits/playbook/pb_message/_message.tsx +3 -1
  40. data/app/pb_kits/playbook/pb_message/kit.schema.json +1 -0
  41. data/app/pb_kits/playbook/pb_multiple_users/_multiple_users.tsx +3 -1
  42. data/app/pb_kits/playbook/pb_multiple_users/kit.schema.json +1 -0
  43. data/app/pb_kits/playbook/pb_multiple_users_stacked/_multiple_users_stacked.tsx +4 -2
  44. data/app/pb_kits/playbook/pb_multiple_users_stacked/kit.schema.json +1 -0
  45. data/app/pb_kits/playbook/pb_rich_text_editor/TipTap/MoreExtensionsDropdown.tsx +10 -3
  46. data/app/pb_kits/playbook/pb_rich_text_editor/TipTap/Toolbar.tsx +16 -1
  47. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_playground.json +2 -3
  48. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_playground.overrides.json +2 -3
  49. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_attributes.jsx +1 -5
  50. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_default.jsx +1 -5
  51. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_default.md +2 -3
  52. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_focus.jsx +1 -5
  53. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_height.jsx +3 -13
  54. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_inline.jsx +1 -5
  55. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_label.jsx +2 -3
  56. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_markdown_support.jsx +1 -2
  57. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_min_height.jsx +3 -13
  58. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_preview.jsx +1 -5
  59. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_required_indicator.jsx +1 -5
  60. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_simple.jsx +1 -5
  61. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_sticky.jsx +1 -5
  62. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_templates.jsx +1 -5
  63. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_more_extensions.jsx +3 -8
  64. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_more_extensions.md +1 -1
  65. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_rails_default.md +2 -2
  66. data/app/pb_kits/playbook/pb_rich_text_editor/rich_text_editor.test.js +2 -1
  67. data/app/pb_kits/playbook/pb_rich_text_editor/rich_text_editor_advanced.test.js +7 -5
  68. data/app/pb_kits/playbook/pb_rich_text_editor/rich_text_editor_rails.js +15 -4
  69. data/app/pb_kits/playbook/pb_stat_value/_stat_value.tsx +3 -1
  70. data/app/pb_kits/playbook/pb_stat_value/kit.schema.json +1 -0
  71. data/app/pb_kits/playbook/pb_table/_table.tsx +70 -197
  72. data/app/pb_kits/playbook/pb_table/index.ts +53 -122
  73. data/app/pb_kits/playbook/pb_table/styles/_sticky_columns.scss +26 -3
  74. data/app/pb_kits/playbook/pb_table/utilities/stickyColumns.test.ts +266 -0
  75. data/app/pb_kits/playbook/pb_table/utilities/stickyColumns.ts +145 -0
  76. data/app/pb_kits/playbook/pb_title_count/_title_count.tsx +3 -1
  77. data/app/pb_kits/playbook/pb_title_count/kit.schema.json +1 -0
  78. data/app/pb_kits/playbook/pb_title_detail/_title_detail.tsx +3 -1
  79. data/app/pb_kits/playbook/pb_title_detail/kit.schema.json +1 -0
  80. data/app/pb_kits/playbook/utilities/test/globalProps/onClick.test.js +237 -0
  81. data/dist/ai/all-schemas.json +16 -0
  82. data/dist/ai/forms.json +1 -1
  83. data/dist/ai/index.json +2 -2
  84. data/dist/ai/kits/background.schema.json +1 -0
  85. data/dist/ai/kits/caption.schema.json +1 -0
  86. data/dist/ai/kits/container.schema.json +1 -0
  87. data/dist/ai/kits/currency.schema.json +1 -0
  88. data/dist/ai/kits/dashboard_value.schema.json +1 -0
  89. data/dist/ai/kits/detail.schema.json +1 -0
  90. data/dist/ai/kits/highlight.schema.json +1 -0
  91. data/dist/ai/kits/icon_stat_value.schema.json +1 -0
  92. data/dist/ai/kits/icon_value.schema.json +1 -0
  93. data/dist/ai/kits/label_value.schema.json +1 -0
  94. data/dist/ai/kits/message.schema.json +1 -0
  95. data/dist/ai/kits/multiple_users.schema.json +1 -0
  96. data/dist/ai/kits/multiple_users_stacked.schema.json +1 -0
  97. data/dist/ai/kits/stat_value.schema.json +1 -0
  98. data/dist/ai/kits/title_count.schema.json +1 -0
  99. data/dist/ai/kits/title_detail.schema.json +1 -0
  100. data/dist/ai/playgrounds/rich_text_editor.json +1 -1
  101. data/dist/chunks/{_detail-BqqzPTNr.js → _detail-Bsvl694i.js} +2 -2
  102. data/dist/chunks/{_pb_line_graph-BGyCJjgG.js → _pb_line_graph-Du7xE9vl.js} +1 -1
  103. data/dist/chunks/_table-Bf6LGyL9.js +1 -0
  104. data/dist/chunks/_typeahead-w6S5Vx4b.js +1 -0
  105. data/dist/chunks/{globalProps-K1DCn3il.js → globalProps-B120NbSJ.js} +1 -1
  106. data/dist/chunks/{lib-7vm9BMqp.js → lib-Buo6zHEw.js} +1 -1
  107. data/dist/chunks/quickpickDateSync-DLDx4zce.js +1 -0
  108. data/dist/chunks/{time_picker_helper-BZutIzB3.js → time_picker_helper-CYGg0UQ0.js} +1 -1
  109. data/dist/chunks/vendor.js +2 -2
  110. data/dist/playbook-rails-react-bindings.js +1 -1
  111. data/dist/playbook-rails.js +1 -1
  112. data/dist/playbook.css +1 -1
  113. data/lib/playbook/version.rb +1 -1
  114. metadata +19 -13
  115. data/dist/chunks/_table-CEZU0qUK.js +0 -1
  116. data/dist/chunks/_typeahead-C7iPR5ZF.js +0 -1
@@ -2,6 +2,13 @@
2
2
  // This provides date range options similar to the DatePicker QuickPick functionality
3
3
 
4
4
  import DateTime from '../../pb_kit/dateTime';
5
+ import {
6
+ endOfDay,
7
+ parseQuickPickDateOnly,
8
+ parseQuickPickDateTime,
9
+ startOfDay,
10
+ stringHasExplicitTime,
11
+ } from '../utilities/quickpickDateSync';
5
12
 
6
13
  type QuickPickOption = {
7
14
  label: string;
@@ -13,7 +20,7 @@ type QuickPickOption = {
13
20
 
14
21
  type CustomQuickPickDate = {
15
22
  label: string;
16
- value: string[] | { timePeriod: string; amount: number };
23
+ value: Array<Date | string> | { timePeriod: string; amount: number };
17
24
  };
18
25
 
19
26
  type CustomQuickPickDates = {
@@ -21,6 +28,11 @@ type CustomQuickPickDates = {
21
28
  dates: CustomQuickPickDate[];
22
29
  };
23
30
 
31
+ const toQuickPickRange = (start: Date, end: Date): Date[] => [
32
+ startOfDay(start),
33
+ endOfDay(end),
34
+ ];
35
+
24
36
  // Helper function to calculate date range from timePeriod and amount
25
37
  const calculateDateRange = (timePeriod: string, amount: number): Date[] => {
26
38
  const endDate = new Date();
@@ -45,7 +57,7 @@ const calculateDateRange = (timePeriod: string, amount: number): Date[] => {
45
57
  default:
46
58
  throw new Error('Invalid time period');
47
59
  }
48
- return [startDate, endDate];
60
+ return toQuickPickRange(startDate, endDate);
49
61
  };
50
62
 
51
63
  // Helper to format date for display
@@ -57,13 +69,32 @@ const formatDate = (date: Date) => {
57
69
  return `${month}/${day}/${year}`;
58
70
  };
59
71
 
72
+ // Date objects keep their authored time (including midnight / 23:59:00).
73
+ // Date-only strings get SOD/EOD; timed strings keep their parsed time.
74
+ const parseCustomDateBoundary = (value: Date | string): Date => {
75
+ if (value instanceof Date) return value;
76
+ if (stringHasExplicitTime(value)) {
77
+ return parseQuickPickDateTime(value) ?? new Date(value);
78
+ }
79
+ return parseQuickPickDateOnly(value) ?? new Date(value);
80
+ };
81
+
82
+ const boundaryHasExplicitTime = (value: Date | string): boolean =>
83
+ value instanceof Date || stringHasExplicitTime(value);
84
+
60
85
  // Helper to create a QuickPickOption from a custom date
61
86
  const createOptionFromCustomDate = (customDate: CustomQuickPickDate): QuickPickOption => {
62
87
  let dateRange: Date[];
63
-
88
+
64
89
  if (Array.isArray(customDate.value)) {
65
- // Explicit date range as string array
66
- dateRange = customDate.value.map((dateStr: string) => new Date(dateStr));
90
+ const startRaw = parseCustomDateBoundary(customDate.value[0]);
91
+ const endRaw = parseCustomDateBoundary(customDate.value[1]);
92
+ const startHasTime = boundaryHasExplicitTime(customDate.value[0]);
93
+ const endHasTime = boundaryHasExplicitTime(customDate.value[1]);
94
+ dateRange = [
95
+ startHasTime ? startRaw : startOfDay(startRaw),
96
+ endHasTime ? endRaw : endOfDay(endRaw),
97
+ ];
67
98
  } else {
68
99
  // Calculate from timePeriod and amount
69
100
  dateRange = calculateDateRange(
@@ -81,6 +112,17 @@ const createOptionFromCustomDate = (customDate: CustomQuickPickDate): QuickPickO
81
112
  };
82
113
  };
83
114
 
115
+ const option = (label: string, start: Date, end: Date, id: string): QuickPickOption => {
116
+ const value = toQuickPickRange(start, end);
117
+ return {
118
+ label,
119
+ value,
120
+ formattedStartDate: formatDate(value[0]),
121
+ formattedEndDate: formatDate(value[1]),
122
+ id,
123
+ };
124
+ };
125
+
84
126
  // Helper to get QuickPick options with date ranges
85
127
  const getQuickPickOptions = (rangeEndsToday = false, customQuickPickDates?: CustomQuickPickDates): QuickPickOption[] => {
86
128
  const today = new Date();
@@ -107,30 +149,30 @@ const getQuickPickOptions = (rangeEndsToday = false, customQuickPickDates?: Cust
107
149
  const lastYearEndDate = DateTime.getPreviousYearEndDate(new Date());
108
150
 
109
151
  const defaultOptions: QuickPickOption[] = [
110
- { label: 'Today', value: [today, today], id: 'quickpick-today' },
111
- { label: 'Yesterday', value: [yesterday, yesterday], formattedStartDate: `${formatDate(yesterday)}`, formattedEndDate: `${formatDate(yesterday)}`, id: 'quickpick-yesterday' },
112
- { label: 'This Week', value: [thisWeekStartDate, thisWeekEndDate], formattedStartDate: `${formatDate(thisWeekStartDate)}`, formattedEndDate: `${formatDate(thisWeekEndDate)}`, id: 'quickpick-this-week' },
113
- { label: 'This Month', value: [thisMonthStartDate, thisMonthEndDate], formattedStartDate: `${formatDate(thisMonthStartDate)}`, formattedEndDate: `${formatDate(thisMonthEndDate)}`, id: 'quickpick-this-month' },
114
- { label: 'This Quarter', value: [thisQuarterStartDate, thisQuarterEndDate], formattedStartDate: `${formatDate(thisQuarterStartDate)}`, formattedEndDate: `${formatDate(thisQuarterEndDate)}`, id: 'quickpick-this-quarter' },
115
- { label: 'This Year', value: [thisYearStartDate, thisYearEndDate], formattedStartDate: `${formatDate(thisYearStartDate)}`, formattedEndDate: `${formatDate(thisYearEndDate)}`, id: 'quickpick-this-year' },
116
- { label: 'Last Week', value: [lastWeekStartDate, lastWeekEndDate], formattedStartDate: `${formatDate(lastWeekStartDate)}`, formattedEndDate: `${formatDate(lastWeekEndDate)}`, id: 'quickpick-last-week' },
117
- { label: 'Last Month', value: [lastMonthStartDate, lastMonthEndDate], formattedStartDate: `${formatDate(lastMonthStartDate)}`, formattedEndDate: `${formatDate(lastMonthEndDate)}`, id: 'quickpick-last-month' },
118
- { label: 'Last Quarter', value: [lastQuarterStartDate, lastQuarterEndDate], formattedStartDate: `${formatDate(lastQuarterStartDate)}`, formattedEndDate: `${formatDate(lastQuarterEndDate)}`, id: 'quickpick-last-quarter' },
119
- { label: 'Last Year', value: [lastYearStartDate, lastYearEndDate], formattedStartDate: `${formatDate(lastYearStartDate)}`, formattedEndDate: `${formatDate(lastYearEndDate)}`, id: 'quickpick-last-year' },
152
+ option('Today', today, today, 'quickpick-today'),
153
+ option('Yesterday', yesterday, yesterday, 'quickpick-yesterday'),
154
+ option('This Week', thisWeekStartDate, thisWeekEndDate, 'quickpick-this-week'),
155
+ option('This Month', thisMonthStartDate, thisMonthEndDate, 'quickpick-this-month'),
156
+ option('This Quarter', thisQuarterStartDate, thisQuarterEndDate, 'quickpick-this-quarter'),
157
+ option('This Year', thisYearStartDate, thisYearEndDate, 'quickpick-this-year'),
158
+ option('Last Week', lastWeekStartDate, lastWeekEndDate, 'quickpick-last-week'),
159
+ option('Last Month', lastMonthStartDate, lastMonthEndDate, 'quickpick-last-month'),
160
+ option('Last Quarter', lastQuarterStartDate, lastQuarterEndDate, 'quickpick-last-quarter'),
161
+ option('Last Year', lastYearStartDate, lastYearEndDate, 'quickpick-last-year'),
120
162
  ];
121
163
 
122
164
  // Handle customQuickPickDates and override logic
123
165
  if (customQuickPickDates && customQuickPickDates.dates && customQuickPickDates.dates.length > 0) {
124
166
  const customOptions = customQuickPickDates.dates.map(createOptionFromCustomDate);
125
-
167
+
126
168
  if (customQuickPickDates.override === false) {
127
169
  return [...defaultOptions, ...customOptions];
128
170
  }
129
-
171
+
130
172
  return customOptions;
131
173
  }
132
174
 
133
175
  return defaultOptions;
134
176
  };
135
177
 
136
- export default getQuickPickOptions
178
+ export default getQuickPickOptions
@@ -3,6 +3,16 @@
3
3
  module Playbook
4
4
  module PbDropdown
5
5
  module QuickpickHelper
6
+ # Marker for absolute ISO boundaries (Z / numeric offset). Wall-clock
7
+ # DateTime.civil also has offset 0, so offset alone cannot distinguish —
8
+ # format_date / format_value use this flag to preserve Z/±HH:MM on emit.
9
+ module AbsoluteInstant
10
+ def absolute_instant?
11
+ true
12
+ end
13
+ end
14
+ private_constant :AbsoluteInstant
15
+
6
16
  class << self
7
17
  def get_quickpick_options(range_ends_today: false, custom_quick_pick_dates: {})
8
18
  default_options = build_default_options(range_ends_today)
@@ -77,7 +87,7 @@ module Playbook
77
87
  {
78
88
  id: "quickpick-#{label.downcase.gsub(/\s+/, '-')}",
79
89
  label: label,
80
- value: [start_date.to_s, end_date.to_s],
90
+ value: [format_value(start_date), format_value(end_date)],
81
91
  formatted_start_date: format_date(start_date),
82
92
  formatted_end_date: format_date(end_date),
83
93
  }
@@ -86,7 +96,7 @@ module Playbook
86
96
  def calculate_date_range(value)
87
97
  # Parse date strings if value is an array
88
98
  if value.is_a?(Array)
89
- [parse_date_string(value[0]), parse_date_string(value[1])]
99
+ [parse_boundary(value[0]), parse_boundary(value[1])]
90
100
  else
91
101
  # Calculate date range from time_period and amount
92
102
  time_period = value[:time_period] || value["time_period"] || value[:timePeriod] || value["timePeriod"]
@@ -99,6 +109,19 @@ module Playbook
99
109
  end
100
110
  end
101
111
 
112
+ def parse_boundary(date_str)
113
+ date_str = date_str.to_s
114
+ if string_has_explicit_time?(date_str)
115
+ parse_datetime_string(date_str)
116
+ else
117
+ parse_date_string(date_str)
118
+ end
119
+ end
120
+
121
+ def string_has_explicit_time?(value)
122
+ value.match?(/[T\s]\d{1,2}:\d{2}/) || value.match?(/\d{1,2}:\d{2}\s*(AM|PM)/i)
123
+ end
124
+
102
125
  def parse_date_string(date_str)
103
126
  # Handle US date format (MM/DD/YYYY) because Ruby's Date.parse defaults to European format (DD/MM/YYYY)
104
127
  if date_str.include?("/")
@@ -108,6 +131,40 @@ module Playbook
108
131
  end
109
132
  end
110
133
 
134
+ # Parses timed custom boundaries. Mirrors React parseQuickPickDateTime:
135
+ # 1) US m/d/Y + time (optional :SS / AM|PM) → DateTime.civil (wall-clock)
136
+ # 2) Offset-less YYYY-MM-DDTHH:MM:SS → DateTime.civil (wall-clock; not Time.parse)
137
+ # 3) ISO with Z / numeric offset → DateTime.iso8601 (absolute; emit keeps offset)
138
+ # Unsupported timed strings raise — no process-TZ Time.parse fallback.
139
+ # Date-only boundaries stay on parse_date_string (unchanged).
140
+ def parse_datetime_string(date_str)
141
+ trimmed = date_str.to_s.strip
142
+
143
+ if (match = trimmed.match(%r{\A(\d{1,2})/(\d{1,2})/(\d{4})\s+(\d{1,2}):(\d{2})(?::(\d{2}))?\s*(AM|PM)?\z}i))
144
+ month, day, year, hour, min, sec, meridiem = match.captures
145
+ hour = hour.to_i
146
+ min = min.to_i
147
+ sec = (sec || 0).to_i
148
+ if meridiem
149
+ meridiem = meridiem.upcase
150
+ hour = 0 if meridiem == "AM" && hour == 12
151
+ hour += 12 if meridiem == "PM" && hour != 12
152
+ end
153
+ return DateTime.civil(year.to_i, month.to_i, day.to_i, hour, min, sec)
154
+ end
155
+
156
+ # Offset-less wall-clock ISO (same shape as WALL_CLOCK_ISO in quickpickDateSync.ts).
157
+ if (match = trimmed.match(/\A(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2}))?(?:\.\d+)?\z/))
158
+ year, month, day, hour, min, sec = match.captures
159
+ return DateTime.civil(year.to_i, month.to_i, day.to_i, hour.to_i, min.to_i, (sec || 0).to_i)
160
+ end
161
+
162
+ # Absolute ISO with Z or numeric offset (React: new Date(iso)).
163
+ return DateTime.iso8601(trimmed).extend(AbsoluteInstant) if trimmed.match?(/\A\d{4}-\d{2}-\d{2}T/) && trimmed.match?(/[Zz]|[+-]\d{2}:?\d{2}\z/)
164
+
165
+ raise ArgumentError, "Unsupported QuickPick timed value: #{date_str.inspect}"
166
+ end
167
+
111
168
  def calculate_start_date(time_period, amount, end_date)
112
169
  case time_period.to_s
113
170
  when "days"
@@ -125,8 +182,40 @@ module Playbook
125
182
  end
126
183
  end
127
184
 
185
+ # Date-only → m/d/Y (SOD/EOD applied at sync when enableTime).
186
+ # Wall-clock Time/DateTime → offset-less YYYY-MM-DDTHH:MM:SS (local clock face).
187
+ # Absolute ISO (AbsoluteInstant) → iso8601 with Z/±HH:MM so JS new Date keeps
188
+ # the same instant (React parseQuickPickDateTime already handles both).
128
189
  def format_date(date)
129
- date.strftime("%m/%d/%Y")
190
+ if time_like?(date)
191
+ format_timed(date)
192
+ else
193
+ date.strftime("%m/%d/%Y")
194
+ end
195
+ end
196
+
197
+ def format_value(date)
198
+ if time_like?(date)
199
+ format_timed(date)
200
+ else
201
+ date.to_s
202
+ end
203
+ end
204
+
205
+ def format_timed(date)
206
+ if absolute_instant?(date)
207
+ date.iso8601
208
+ else
209
+ date.strftime("%Y-%m-%dT%H:%M:%S")
210
+ end
211
+ end
212
+
213
+ def absolute_instant?(date)
214
+ date.respond_to?(:absolute_instant?) && date.absolute_instant?
215
+ end
216
+
217
+ def time_like?(date)
218
+ date.is_a?(Time) || date.is_a?(DateTime) || defined?(ActiveSupport::TimeWithZone) && date.is_a?(ActiveSupport::TimeWithZone)
130
219
  end
131
220
 
132
221
  def format_date_range(start_date, end_date)
@@ -1,2 +1,9 @@
1
1
  export { separateChildComponents, prepareSubcomponents } from './subComponentHelper';
2
- export { handleClickOutside } from './clickOutsideHelper';
2
+ export { handleClickOutside } from './clickOutsideHelper';
3
+ export {
4
+ endOfDay,
5
+ parseQuickPickDateOnly,
6
+ setQuickPickDateOnPicker,
7
+ startOfDay,
8
+ stringHasExplicitTime,
9
+ } from './quickpickDateSync';
@@ -0,0 +1,137 @@
1
+ // QuickPick → DatePicker sync helpers.
2
+ // End-of-day uses 23:59:00 to match DatePicker enableTime minute precision
3
+ // (default timeFormat has no seconds).
4
+ //
5
+ // Contract when the target Flatpickr has enableTime:
6
+ // - Date-only strings (e.g. m/d/Y) → start of day / end of day by bound
7
+ // - Timed strings → parse to Date and pass through unchanged
8
+ // - Date objects → pass through unchanged (presets already SOD/EOD via toQuickPickRange;
9
+ // custom values may intentionally use midnight or 23:59:00)
10
+
11
+ type FlatpickrLike = {
12
+ setDate: (date: Date | string, triggerChange?: boolean, format?: string) => void
13
+ config?: { enableTime?: boolean }
14
+ }
15
+
16
+ export type QuickPickDateBound = "start" | "end"
17
+
18
+ const DATE_ONLY_MDY = /^(\d{1,2})\/(\d{1,2})\/(\d{4})$/
19
+ // Mirrors Rails QuickpickHelper#parse_datetime_string for US m/d/Y + optional AM/PM.
20
+ const MDY_WITH_TIME =
21
+ /^(\d{1,2})\/(\d{1,2})\/(\d{4})\s+(\d{1,2}):(\d{2})(?::(\d{2}))?\s*(AM|PM)?$/i
22
+ // Offset-less wall-clock from Rails format_date / format_value (no Z / ±HH:MM).
23
+ const WALL_CLOCK_ISO =
24
+ /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2}))?(?:\.\d+)?$/
25
+
26
+ export const startOfDay = (date: Date): Date => {
27
+ const next = new Date(date.getTime())
28
+ next.setHours(0, 0, 0, 0)
29
+ return next
30
+ }
31
+
32
+ export const endOfDay = (date: Date): Date => {
33
+ const next = new Date(date.getTime())
34
+ next.setHours(23, 59, 0, 0)
35
+ return next
36
+ }
37
+
38
+ export const stringHasExplicitTime = (value: string): boolean => {
39
+ return /[T\s]\d{1,2}:\d{2}/.test(value) || /\d{1,2}:\d{2}\s*(AM|PM)/i.test(value)
40
+ }
41
+
42
+ export const parseQuickPickDateOnly = (value: string): Date | null => {
43
+ const match = value.trim().match(DATE_ONLY_MDY)
44
+ if (!match) return null
45
+ const month = Number(match[1])
46
+ const day = Number(match[2])
47
+ const year = Number(match[3])
48
+ return new Date(year, month - 1, day)
49
+ }
50
+
51
+ /**
52
+ * Parse timed QuickPick custom strings to a local Date.
53
+ * - m/d/Y + time (optional seconds / AM|PM) → wall-clock components (Safari-safe)
54
+ * - Offset-less YYYY-MM-DDTHH:MM:SS → wall-clock components
55
+ * - ISO with Z / offset → absolute instant via Date constructor
56
+ */
57
+ export const parseQuickPickDateTime = (value: string): Date | null => {
58
+ const trimmed = value.trim()
59
+
60
+ const mdy = trimmed.match(MDY_WITH_TIME)
61
+ if (mdy) {
62
+ const month = Number(mdy[1])
63
+ const day = Number(mdy[2])
64
+ const year = Number(mdy[3])
65
+ let hour = Number(mdy[4])
66
+ const minute = Number(mdy[5])
67
+ const second = mdy[6] != null ? Number(mdy[6]) : 0
68
+ const meridiem = mdy[7]?.toUpperCase()
69
+ if (meridiem === "AM" && hour === 12) hour = 0
70
+ if (meridiem === "PM" && hour !== 12) hour += 12
71
+ return new Date(year, month - 1, day, hour, minute, second)
72
+ }
73
+
74
+ const wallClock = trimmed.match(WALL_CLOCK_ISO)
75
+ if (wallClock) {
76
+ const year = Number(wallClock[1])
77
+ const month = Number(wallClock[2])
78
+ const day = Number(wallClock[3])
79
+ const hour = Number(wallClock[4])
80
+ const minute = Number(wallClock[5])
81
+ const second = wallClock[6] != null ? Number(wallClock[6]) : 0
82
+ return new Date(year, month - 1, day, hour, minute, second)
83
+ }
84
+
85
+ const parsed = new Date(trimmed)
86
+ if (Number.isNaN(parsed.getTime())) return null
87
+ return parsed
88
+ }
89
+
90
+ /**
91
+ * Apply a QuickPick date to a Flatpickr instance.
92
+ * With enableTime and a date-only string: start → 00:00:00, end → 23:59:00.
93
+ * Timed strings and Date values are passed through unchanged.
94
+ * Date-only pickers keep the existing m/d/Y string path for date-only values.
95
+ */
96
+ export const setQuickPickDateOnPicker = (
97
+ picker: FlatpickrLike | null | undefined,
98
+ value: Date | string | null | undefined,
99
+ bound: QuickPickDateBound,
100
+ ): void => {
101
+ if (!picker || value == null || value === "") return
102
+
103
+ const enableTime = Boolean(picker.config?.enableTime)
104
+
105
+ if (!enableTime) {
106
+ if (typeof value === "string") {
107
+ // Timed payloads (ISO / datetime) are not valid for Flatpickr's m/d/Y format.
108
+ if (stringHasExplicitTime(value)) {
109
+ const parsed = parseQuickPickDateTime(value)
110
+ if (!parsed) return
111
+ picker.setDate(parsed, true)
112
+ return
113
+ }
114
+ picker.setDate(value, true, "m/d/Y")
115
+ } else {
116
+ picker.setDate(value, true)
117
+ }
118
+ return
119
+ }
120
+
121
+ if (typeof value === "string") {
122
+ if (stringHasExplicitTime(value)) {
123
+ const parsed = parseQuickPickDateTime(value)
124
+ if (!parsed) return
125
+ picker.setDate(parsed, true)
126
+ return
127
+ }
128
+
129
+ let date = parseQuickPickDateOnly(value) ?? new Date(value)
130
+ if (Number.isNaN(date.getTime())) return
131
+ date = bound === "start" ? startOfDay(date) : endOfDay(date)
132
+ picker.setDate(date, true)
133
+ return
134
+ }
135
+
136
+ picker.setDate(value, true)
137
+ }
@@ -28,7 +28,7 @@ export type DropdownInteractiveConfig = {
28
28
  override?: boolean,
29
29
  dates: {
30
30
  label: string,
31
- value: string[] | { timePeriod: string, amount: number },
31
+ value: Array<Date | string> | { timePeriod: string, amount: number },
32
32
  }[],
33
33
  },
34
34
  value?: string,
@@ -1,6 +1,7 @@
1
1
  import React from 'react'
2
2
  import classnames from 'classnames'
3
3
  import { globalProps, GlobalProps } from '../utilities/globalProps'
4
+ import { globalEventProps, GlobalEventProps } from '../utilities/globalEventProps'
4
5
  import { buildDataProps, buildHtmlProps } from '../utilities/props'
5
6
  import { findHighlightChunks } from './_highlightChunks'
6
7
 
@@ -12,7 +13,7 @@ type HighlightProps = {
12
13
  children?: React.ReactChild[] | React.ReactChild | string,
13
14
  text?: string,
14
15
  highlightedText?: string[],
15
- } & GlobalProps
16
+ } & GlobalProps & GlobalEventProps
16
17
 
17
18
  const Highlight = (props: HighlightProps): React.ReactElement => {
18
19
  const {
@@ -35,6 +36,7 @@ const Highlight = (props: HighlightProps): React.ReactElement => {
35
36
  <span
36
37
  {...dataProps}
37
38
  {...htmlProps}
39
+ {...globalEventProps(props)}
38
40
  id={id}
39
41
  >
40
42
  {chunks.map((chunk, index) => {
@@ -22,6 +22,7 @@
22
22
  }
23
23
  },
24
24
  "globalProps": true,
25
+ "globalEventProps": true,
25
26
  "usage": {
26
27
  "react": {
27
28
  "import": "import { Highlight } from 'playbook-ui'",
@@ -3,6 +3,7 @@ import classnames from 'classnames'
3
3
 
4
4
  import { buildAriaProps, buildCss, buildDataProps, buildHtmlProps } from '../utilities/props'
5
5
  import { globalProps, GlobalProps } from '../utilities/globalProps'
6
+ import { GlobalEventProps, globalEventProps } from '../utilities/globalEventProps'
6
7
 
7
8
  import Body from '../pb_body/_body'
8
9
  import Caption from '../pb_caption/_caption'
@@ -33,7 +34,7 @@ type IconStatValueProps = {
33
34
  | "yellow"
34
35
  | "orange"
35
36
  | "green"
36
- } & GlobalProps
37
+ } & GlobalProps & GlobalEventProps
37
38
 
38
39
  const IconStatValue = (props: IconStatValueProps): React.ReactElement => {
39
40
  const {
@@ -94,6 +95,7 @@ const IconStatValue = (props: IconStatValueProps): React.ReactElement => {
94
95
  {...ariaProps}
95
96
  {...dataProps}
96
97
  {...htmlProps}
98
+ {...globalEventProps(props)}
97
99
  className={classes}
98
100
  id={id}
99
101
  >
@@ -89,6 +89,7 @@
89
89
  }
90
90
  },
91
91
  "globalProps": true,
92
+ "globalEventProps": true,
92
93
  "usage": {
93
94
  "react": {
94
95
  "import": "import { IconStatValue } from 'playbook-ui'",
@@ -3,6 +3,7 @@ import classnames from 'classnames'
3
3
 
4
4
  import { buildAriaProps, buildCss, buildDataProps, buildHtmlProps } from '../utilities/props'
5
5
  import { globalProps } from '../utilities/globalProps'
6
+ import { GlobalEventProps, globalEventProps } from '../utilities/globalEventProps'
6
7
 
7
8
  import Body from '../pb_body/_body'
8
9
  import Icon from '../pb_icon/_icon'
@@ -18,7 +19,7 @@ type IconValueProps = {
18
19
  htmlOptions?: {[key: string]: string | number | boolean | (() => void)},
19
20
  id?: string,
20
21
  text: string,
21
- }
22
+ } & GlobalEventProps
22
23
 
23
24
  const IconValue = (props: IconValueProps): React.ReactElement => {
24
25
  const {
@@ -46,6 +47,7 @@ const IconValue = (props: IconValueProps): React.ReactElement => {
46
47
  {...ariaProps}
47
48
  {...dataProps}
48
49
  {...htmlProps}
50
+ {...globalEventProps(props)}
49
51
  className={classes}
50
52
  id={id}
51
53
  >
@@ -42,6 +42,7 @@
42
42
  }
43
43
  },
44
44
  "globalProps": true,
45
+ "globalEventProps": true,
45
46
  "usage": {
46
47
  "react": {
47
48
  "import": "import { IconValue } from 'playbook-ui'",
@@ -2,6 +2,7 @@ import React from "react";
2
2
  import classnames from "classnames";
3
3
  import { buildAriaProps, buildCss, buildDataProps, buildHtmlProps } from "../utilities/props";
4
4
  import { globalProps } from "../utilities/globalProps";
5
+ import { GlobalEventProps, globalEventProps } from "../utilities/globalEventProps";
5
6
  import DateTime from '../pb_kit/dateTime';
6
7
 
7
8
  import Body from "../pb_body/_body";
@@ -25,7 +26,7 @@ type LabelValueProps = {
25
26
  icon?: string;
26
27
  description?: string;
27
28
  title?: string;
28
- };
29
+ } & GlobalEventProps;
29
30
 
30
31
  const dateString = (value: Date) => {
31
32
  const month = DateTime.toMonthNum(value);
@@ -67,6 +68,7 @@ const LabelValue = (props: LabelValueProps) => {
67
68
  {...ariaProps}
68
69
  {...dataProps}
69
70
  {...htmlProps}
71
+ {...globalEventProps(props)}
70
72
  className={classes}
71
73
  id={id}
72
74
  title={title}
@@ -77,6 +77,7 @@
77
77
  }
78
78
  },
79
79
  "globalProps": true,
80
+ "globalEventProps": true,
80
81
  "usage": {
81
82
  "react": {
82
83
  "import": "import { LabelValue } from 'playbook-ui'",
@@ -1,9 +1,9 @@
1
1
  This kit provides a wrapping class to place around the MapLibre library. Complete docs for using the library can be found [here](https://maplibre.org/maplibre-gl-js-docs/api/).
2
2
 
3
3
  Basic setup to start using MapLibre:
4
- - Install the npm package using `yarn add maplibre-gl`
5
- - To include the maplibre css, include a link to the CSS file as a CDN in your stylesheet using the following syntax: `@import url("https://unpkg.com/maplibre-gl@6.13.0/dist/maplibre-gl.css");`
6
- OR include it as a link in the <head> tag `<link href='https://unpkg.com/maplibre-gl@6.13.0/dist/maplibre-gl.css' rel='stylesheet' />`
4
+ - Install the npm package using `yarn add maplibre-gl@6.4.1`
5
+ - To include the maplibre css, include a link to the CSS file as a CDN in your stylesheet using the following syntax: `@import url("https://unpkg.com/maplibre-gl@6.4.1/dist/maplibre-gl.css");`
6
+ OR include it as a link in the <head> tag `<link href='https://unpkg.com/maplibre-gl@6.4.1/dist/maplibre-gl.css' rel='stylesheet' />`
7
7
  - You can now use MapLibre within the Map Kit as shown in this example.
8
8
 
9
9
  __Notes__ :
@@ -3,6 +3,7 @@ import classnames from 'classnames'
3
3
 
4
4
  import { buildAriaProps, buildCss, buildDataProps, buildHtmlProps } from '../utilities/props'
5
5
  import { globalProps } from '../utilities/globalProps'
6
+ import { GlobalEventProps, globalEventProps } from '../utilities/globalEventProps'
6
7
 
7
8
  import Avatar from '../pb_avatar/_avatar'
8
9
  import Body from '../pb_body/_body'
@@ -28,7 +29,7 @@ type MessageProps = {
28
29
  timestampObject?: Date,
29
30
  timezone?: string,
30
31
  alignTimestamp?: string,
31
- }
32
+ } & GlobalEventProps
32
33
 
33
34
  const Message = (props: MessageProps) => {
34
35
  const {
@@ -68,6 +69,7 @@ const Message = (props: MessageProps) => {
68
69
  {...ariaProps}
69
70
  {...dataProps}
70
71
  {...htmlProps}
72
+ {...globalEventProps(props)}
71
73
  className={classes}
72
74
  id={id}
73
75
  >
@@ -94,6 +94,7 @@
94
94
  }
95
95
  },
96
96
  "globalProps": true,
97
+ "globalEventProps": true,
97
98
  "usage": {
98
99
  "react": {
99
100
  "import": "import { Message } from 'playbook-ui'",
@@ -3,6 +3,7 @@ import classnames from 'classnames'
3
3
 
4
4
  import { buildAriaProps, buildCss, buildDataProps, buildHtmlProps } from '../utilities/props'
5
5
  import { globalProps } from '../utilities/globalProps'
6
+ import { GlobalEventProps, globalEventProps } from '../utilities/globalEventProps'
6
7
 
7
8
  import Avatar from '../pb_avatar/_avatar'
8
9
  import Tooltip from '../pb_tooltip/_tooltip'
@@ -19,7 +20,7 @@ type MultipleUsersProps = {
19
20
  size?: "md" | "lg" | "sm" | "xl" | "xs" | "xxs",
20
21
  withTooltip?: boolean,
21
22
  users: Array<{ [key: string]: string }>,
22
- }
23
+ } & GlobalEventProps
23
24
 
24
25
  const MultipleUsers = (props: MultipleUsersProps): React.ReactElement => {
25
26
  const {
@@ -126,6 +127,7 @@ const MultipleUsers = (props: MultipleUsersProps): React.ReactElement => {
126
127
  {...ariaProps}
127
128
  {...dataProps}
128
129
  {...htmlProps}
130
+ {...globalEventProps(props)}
129
131
  className={classes}
130
132
  id={id}
131
133
  >
@@ -60,6 +60,7 @@
60
60
  }
61
61
  },
62
62
  "globalProps": true,
63
+ "globalEventProps": true,
63
64
  "usage": {
64
65
  "react": {
65
66
  "import": "import { MultipleUsers } from 'playbook-ui'",