@siratik/wp-component-library 0.1.1 → 0.1.2

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 (96) hide show
  1. package/LICENSE +5 -0
  2. package/README.md +43 -5
  3. package/components/attachment-image/README.md +118 -0
  4. package/components/attachment-image/index.js +105 -0
  5. package/components/attributes-panel/README.md +92 -0
  6. package/components/attributes-panel/index.js +324 -0
  7. package/components/badge/README.md +61 -0
  8. package/components/badge/index.js +105 -0
  9. package/components/block-appender/README.md +158 -0
  10. package/components/block-appender/index.js +135 -0
  11. package/components/block-extension/index.js +214 -0
  12. package/components/business-hours/SpecialHoursControl.js +211 -0
  13. package/components/business-hours/WeeklyHoursControl.js +419 -0
  14. package/components/business-hours/index.js +8 -0
  15. package/components/button/README.md +172 -0
  16. package/components/button/index.js +84 -0
  17. package/components/captcha-placeholder/CloudflareTurnstile.js +111 -0
  18. package/components/captcha-placeholder/GoogleRecaptcha.js +132 -0
  19. package/components/captcha-placeholder/HCaptcha.js +99 -0
  20. package/components/captcha-placeholder/README.md +228 -0
  21. package/components/captcha-placeholder/index.js +39 -0
  22. package/components/captcha-placeholder/styles.js +33 -0
  23. package/components/geocoder/AddressEntryControl.js +390 -0
  24. package/components/geocoder/GeocoderSearchControl.js +353 -0
  25. package/components/geocoder/countryOptions.js +66 -0
  26. package/components/geocoder/geocoder.js +400 -0
  27. package/components/geocoder/index.js +16 -0
  28. package/components/icon-picker/Icon.js +53 -0
  29. package/components/icon-picker/IconPicker.js +70 -0
  30. package/components/icon-picker/IconPickerModal.js +474 -0
  31. package/components/icon-picker/IconPickerToolbarButton.js +60 -0
  32. package/components/icon-picker/InlineIconPicker.js +92 -0
  33. package/components/icon-picker/api.js +181 -0
  34. package/components/icon-picker/config-bootstrap.js +108 -0
  35. package/components/icon-picker/hooks.js +157 -0
  36. package/components/icon-picker/index.js +39 -0
  37. package/components/icon-picker/load-script.js +33 -0
  38. package/components/icon-picker/loaders.js +79 -0
  39. package/components/icon-picker/store.js +223 -0
  40. package/components/icon-picker/styles.js +232 -0
  41. package/components/icon-picker/utils.js +138 -0
  42. package/components/layout/README.md +268 -0
  43. package/components/link-tools-panel/README.md +49 -0
  44. package/components/link-tools-panel/index.js +198 -0
  45. package/components/link-tools-panel/styles.js +90 -0
  46. package/components/media/README.md +288 -0
  47. package/components/media/SettingsMediaUpload.js +187 -0
  48. package/components/media/editor-media-upload.js +65 -0
  49. package/components/media/image-controls.js +196 -0
  50. package/components/media/index.js +13 -0
  51. package/components/media/inspector-media-upload.js +290 -0
  52. package/components/media/toolbar-media-upload.js +53 -0
  53. package/components/media/utils/aspect-ratios.js +118 -0
  54. package/components/meta/MetaPanel.js +75 -0
  55. package/components/meta/README.md +46 -0
  56. package/components/meta/index.js +6 -0
  57. package/components/meta/meta-gated-editor.js +282 -0
  58. package/components/query/README.md +340 -0
  59. package/components/query/cardTemplatePartPanel.js +69 -0
  60. package/components/query/cardTemplatePartSelect.js +165 -0
  61. package/components/query/cardTemplatePartUtils.js +119 -0
  62. package/components/query/columnCountControl.js +63 -0
  63. package/components/query/displayTypeSelect.js +52 -0
  64. package/components/query/examples.js +166 -0
  65. package/components/query/hideCurrentPostControl.js +47 -0
  66. package/components/query/index.js +20 -0
  67. package/components/query/manualPostSelector.js +125 -0
  68. package/components/query/manualTermSelector.js +141 -0
  69. package/components/query/orderBySelect.js +95 -0
  70. package/components/query/postTypeSelect.js +67 -0
  71. package/components/query/postsPerPageControl.js +51 -0
  72. package/components/query/selectionModeControl.js +61 -0
  73. package/components/query/taxonomySelect.js +139 -0
  74. package/components/query/utils.js +75 -0
  75. package/components/repeater/README.md +159 -0
  76. package/components/repeater/index.js +829 -0
  77. package/components/segmented-bar/README.md +30 -0
  78. package/components/segmented-bar/index.js +68 -0
  79. package/components/sortable-select/README.md +153 -0
  80. package/components/sortable-select/custom-sortable-token.js +127 -0
  81. package/components/sortable-select/index.js +693 -0
  82. package/components/style-picker/README.md +36 -0
  83. package/components/style-picker/index.js +92 -0
  84. package/components/variable-field/README.md +299 -0
  85. package/components/variable-field/index.js +592 -0
  86. package/components/variable-field/value-to-chip-html.js +38 -0
  87. package/components/variable-inserter/README.md +68 -0
  88. package/components/variable-inserter/index.js +168 -0
  89. package/constants/statusColors.js +1 -1
  90. package/editor.js +16 -0
  91. package/index.js +127 -0
  92. package/package.json +21 -13
  93. package/utils/kitaris-localize.js +128 -0
  94. package/utils/slug-aware-color-setter.js +27 -0
  95. package/build/index.asset.php +0 -20
  96. package/build/index.js +0 -1242
@@ -0,0 +1,419 @@
1
+ /**
2
+ * Weekly hours editor: one row per day (closed, 24 hours, or open–close
3
+ * times), plus optional Open 24/7 and 24-hour format toggles. Controlled:
4
+ * `hours` uses the organization hours shape and `onHoursChange` receives the
5
+ * whole next object.
6
+ *
7
+ * @package Siratik/WP-Component-Library
8
+ */
9
+
10
+ import styled from "@emotion/styled";
11
+ import {
12
+ Flex,
13
+ FlexItem,
14
+ PanelRow,
15
+ TimePicker,
16
+ ToggleControl,
17
+ } from "@wordpress/components";
18
+ import { Fragment, useEffect, useState } from "@wordpress/element";
19
+ import { __ } from "@wordpress/i18n";
20
+
21
+ const StyledWeeklyHours = styled.div`
22
+ .business-hours {
23
+ align-items: center;
24
+ position: relative;
25
+ padding-bottom: 0.75rem;
26
+ padding-top: 0.75rem;
27
+
28
+ &:before {
29
+ content: "";
30
+ border-top: 1px dashed var(--wpds-color-stroke-surface-neutral, rgb(217, 219, 222));
31
+ position: absolute;
32
+ width: 100%;
33
+ top: -5px;
34
+ left: 0;
35
+ right: 0;
36
+ margin: 0 auto;
37
+ }
38
+ &:first-of-type {
39
+ &:before {
40
+ display: none;
41
+ }
42
+ }
43
+
44
+ &:last-of-type {
45
+ &:after {
46
+ content: "";
47
+ border-bottom: 1px dashed var(--wpds-color-stroke-surface-neutral, rgb(217, 219, 222));
48
+ position: absolute;
49
+ width: 100%;
50
+ bottom: -5px;
51
+ left: 0;
52
+ right: 0;
53
+ margin: 0 auto;
54
+ }
55
+ }
56
+
57
+ &__time-group {
58
+ display: flex;
59
+ align-items: center;
60
+ justify-content: flex-end;
61
+ margin: 0 !important;
62
+ }
63
+
64
+ &__day {
65
+ min-width: 80px;
66
+ font-weight: 600;
67
+ margin-bottom: 0;
68
+
69
+ @media (max-width: 768px) {
70
+ min-width: 60px;
71
+ font-size: 0.875rem;
72
+ }
73
+ }
74
+
75
+ &__time {
76
+ width: 80px;
77
+
78
+ @media (max-width: 768px) {
79
+ width: auto;
80
+ min-width: 70px;
81
+ }
82
+ }
83
+
84
+ &__am-pm {
85
+ width: 60px;
86
+ }
87
+
88
+ &__dash {
89
+ padding-left: 0.5rem;
90
+ padding-right: 0.5rem;
91
+
92
+ @media (max-width: 768px) {
93
+ padding-left: 0.25rem;
94
+ padding-right: 0.25rem;
95
+ }
96
+ }
97
+
98
+ .components-toggle-control {
99
+ padding-left: 1rem;
100
+
101
+ @media (max-width: 768px) {
102
+ padding-left: 0.5rem;
103
+
104
+ .components-form-toggle__label {
105
+ font-size: 0.875rem;
106
+ }
107
+ }
108
+ }
109
+
110
+ &__toggle-closed,
111
+ &__toggle-24hrs {
112
+ @media (max-width: 480px) {
113
+ min-width: auto !important;
114
+ }
115
+ }
116
+
117
+ .components-flex {
118
+ @media (max-width: 576px) {
119
+ flex-wrap: wrap;
120
+ gap: 0.5rem !important;
121
+ }
122
+ }
123
+
124
+ .components-flex-item {
125
+ @media (max-width: 576px) {
126
+ &:first-of-type {
127
+ width: 100%;
128
+ margin-bottom: 0.5rem;
129
+ }
130
+ }
131
+
132
+ &:last-child {
133
+ @media (max-width: 480px) {
134
+ width: 100%;
135
+ min-width: auto !important;
136
+
137
+ .components-flex {
138
+ justify-content: flex-start !important;
139
+ }
140
+ }
141
+ }
142
+ }
143
+
144
+ .components-datetime__time-input {
145
+ @media (max-width: 480px) {
146
+ font-size: 0.875rem;
147
+ }
148
+ }
149
+ }
150
+ `;
151
+
152
+ const DAYS_OF_WEEK = [
153
+ "monday",
154
+ "tuesday",
155
+ "wednesday",
156
+ "thursday",
157
+ "friday",
158
+ "saturday",
159
+ "sunday",
160
+ ];
161
+
162
+ /**
163
+ * Individual Day Hours Component
164
+ */
165
+ function DayHours({ day, dayHours, updateHours, is24HourFormat = false }) {
166
+ const capitalizedDay = day.charAt(0).toUpperCase() + day.slice(1);
167
+
168
+ // Convert 24-hour time string to time object for TimePicker
169
+ const timeStringToTimeObject = (timeString) => {
170
+ if (!timeString) {
171
+ return { hours: 9, minutes: 0 };
172
+ }
173
+ const [hours, minutes] = timeString.split(":");
174
+ return {
175
+ hours: parseInt(hours),
176
+ minutes: parseInt(minutes),
177
+ };
178
+ };
179
+
180
+ // Convert TimePicker time object to 24-hour format string
181
+ const timeObjectToTimeString = (timeObject) => {
182
+ if (
183
+ !timeObject ||
184
+ typeof timeObject.hours !== "number" ||
185
+ typeof timeObject.minutes !== "number"
186
+ ) {
187
+ return "09:00";
188
+ }
189
+ const hours = timeObject.hours.toString().padStart(2, "0");
190
+ const minutes = timeObject.minutes.toString().padStart(2, "0");
191
+ return `${hours}:${minutes}`;
192
+ };
193
+
194
+ // Get current times - always use 24-hour format for storage
195
+ const currentOpenTime = dayHours.open_time || "09:00";
196
+ const currentCloseTime = dayHours.close_time || "17:00";
197
+
198
+ // State for TimePicker (always 24-hour format internally)
199
+ const [openTime, setOpenTime] = useState(() =>
200
+ timeStringToTimeObject(currentOpenTime),
201
+ );
202
+ const [closeTime, setCloseTime] = useState(() =>
203
+ timeStringToTimeObject(currentCloseTime),
204
+ );
205
+
206
+ // Update state when props change
207
+ useEffect(() => {
208
+ setOpenTime(timeStringToTimeObject(dayHours.open_time || "09:00"));
209
+ setCloseTime(timeStringToTimeObject(dayHours.close_time || "17:00"));
210
+ }, [dayHours.open_time, dayHours.close_time]);
211
+
212
+ const handleOpenTimeChange = (newTimeObject) => {
213
+ setOpenTime(newTimeObject);
214
+ const timeString = timeObjectToTimeString(newTimeObject);
215
+ updateHours(day, "open_time", timeString);
216
+ };
217
+
218
+ const handleCloseTimeChange = (newTimeObject) => {
219
+ setCloseTime(newTimeObject);
220
+ const timeString = timeObjectToTimeString(newTimeObject);
221
+ updateHours(day, "close_time", timeString);
222
+ };
223
+
224
+ let dayStatus;
225
+ if (dayHours.closed) {
226
+ dayStatus = (
227
+ <Flex justify="flex-end">
228
+ <span
229
+ style={{
230
+ color: "#757575",
231
+ fontStyle: "italic",
232
+ }}
233
+ >
234
+ {__("Closed all day", "wp-component-library")}
235
+ </span>
236
+ </Flex>
237
+ );
238
+ } else if (dayHours.open_24_hours) {
239
+ dayStatus = (
240
+ <Flex justify="flex-end">
241
+ <span
242
+ style={{
243
+ color: "#757575",
244
+ }}
245
+ >
246
+ {__("Open 24 hours", "wp-component-library")}
247
+ </span>
248
+ </Flex>
249
+ );
250
+ } else {
251
+ dayStatus = (
252
+ <Flex justify="flex-end" align="center" gap="2">
253
+ <TimePicker.TimeInput
254
+ key={`${day}-open-${is24HourFormat}`}
255
+ className="business-hours__time business-hours__time--open"
256
+ value={openTime}
257
+ onChange={handleOpenTimeChange}
258
+ is12Hour={!is24HourFormat}
259
+ />
260
+ <span className="business-hours__dash">–</span>
261
+ <TimePicker.TimeInput
262
+ key={`${day}-close-${is24HourFormat}`}
263
+ className="business-hours__time business-hours__time--close"
264
+ value={closeTime}
265
+ onChange={handleCloseTimeChange}
266
+ is12Hour={!is24HourFormat}
267
+ />
268
+ </Flex>
269
+ );
270
+ }
271
+
272
+ return (
273
+ <PanelRow className="business-hours">
274
+ <Flex align="center" gap="3" style={{ width: "100%" }}>
275
+ <FlexItem style={{ minWidth: "80px" }}>
276
+ <p className="business-hours__day">{capitalizedDay}</p>
277
+ </FlexItem>
278
+
279
+ <FlexItem
280
+ style={{ minWidth: "60px" }}
281
+ className="business-hours__toggle-closed"
282
+ >
283
+ <ToggleControl
284
+ __nextHasNoMarginBottom
285
+ className="business-hours__closed"
286
+ label={__("Closed", "wp-component-library")}
287
+ checked={dayHours.closed ?? false}
288
+ onChange={(newValue) =>
289
+ updateHours(day, "closed", newValue)
290
+ }
291
+ />
292
+ </FlexItem>
293
+
294
+ <FlexItem
295
+ style={{ minWidth: "60px" }}
296
+ className="business-hours__toggle-24hrs"
297
+ >
298
+ {!dayHours.closed && (
299
+ <ToggleControl
300
+ __nextHasNoMarginBottom
301
+ className="business-hours__AlwaysOpen"
302
+ label={__("24 Hrs", "wp-component-library")}
303
+ checked={dayHours.open_24_hours ?? false}
304
+ onChange={(newValue) =>
305
+ updateHours(day, "open_24_hours", newValue)
306
+ }
307
+ />
308
+ )}
309
+ </FlexItem>
310
+
311
+ <FlexItem style={{ flex: 1, minWidth: "200px" }}>
312
+ {dayStatus}
313
+ </FlexItem>
314
+ </Flex>
315
+ </PanelRow>
316
+ );
317
+ }
318
+
319
+ /**
320
+ * Weekly schedule UI + optional branch-only toggles (24/7, time format).
321
+ * Use with controlled `hours` / `onHoursChange` for primary hours body or extra locations.
322
+ *
323
+ * @param {Object} props
324
+ * @param {Object} props.hours Hours blob (same shape as organization `hours`).
325
+ * @param {Function} props.onHoursChange Full hours object updater.
326
+ * @param {boolean} props.includeSecondaryToggles Show Open 24/7 + 24h format (extra locations modal).
327
+ */
328
+ export function WeeklyHoursControl({
329
+ hours,
330
+ onHoursChange,
331
+ includeSecondaryToggles = false,
332
+ className = "",
333
+ }) {
334
+ const businessHours = hours || {};
335
+ const alwaysOpen = businessHours.always_open ?? false;
336
+ const is24HourFormat = businessHours.is_24_hour_format ?? false;
337
+
338
+ const updateHours = (day, field, value) => {
339
+ onHoursChange({
340
+ ...businessHours,
341
+ [day]: {
342
+ ...(businessHours?.[day] || {}),
343
+ [field]: value,
344
+ },
345
+ });
346
+ };
347
+
348
+ const updateGlobalSetting = (field, value) => {
349
+ onHoursChange({
350
+ ...businessHours,
351
+ [field]: value,
352
+ });
353
+ };
354
+
355
+ return (
356
+ <StyledWeeklyHours className={`weekly-hours-control business-hours-schedule-form ${className}`.trim()}>
357
+ {includeSecondaryToggles ? (
358
+ <Flex
359
+ gap="2rem"
360
+ align="center"
361
+ wrap
362
+ style={{ marginBottom: "1rem" }}
363
+ >
364
+ <ToggleControl
365
+ __nextHasNoMarginBottom
366
+ label={__("Open 24/7", "wp-component-library")}
367
+ checked={alwaysOpen}
368
+ onChange={(newValue) =>
369
+ updateGlobalSetting("always_open", newValue)
370
+ }
371
+ />
372
+ <ToggleControl
373
+ __nextHasNoMarginBottom
374
+ label={__("24-hour format", "wp-component-library")}
375
+ checked={is24HourFormat}
376
+ onChange={(newValue) =>
377
+ updateGlobalSetting(
378
+ "is_24_hour_format",
379
+ newValue,
380
+ )
381
+ }
382
+ />
383
+ </Flex>
384
+ ) : null}
385
+
386
+ {alwaysOpen ? (
387
+ <div
388
+ style={{
389
+ textAlign: "center",
390
+ padding: "2rem",
391
+ color: "#757575",
392
+ }}
393
+ >
394
+ <p>
395
+ {__(
396
+ "Hours are set to open 24/7 all days.",
397
+ "wp-component-library",
398
+ )}
399
+ </p>
400
+ </div>
401
+ ) : (
402
+ <Fragment>
403
+ {DAYS_OF_WEEK.map((day) => (
404
+ <Fragment key={day}>
405
+ <DayHours
406
+ day={day}
407
+ dayHours={businessHours?.[day] || {}}
408
+ updateHours={updateHours}
409
+ is24HourFormat={is24HourFormat}
410
+ />
411
+ </Fragment>
412
+ ))}
413
+ </Fragment>
414
+ )}
415
+ </StyledWeeklyHours>
416
+ );
417
+ }
418
+
419
+ export default WeeklyHoursControl;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Weekly business hours editor.
3
+ *
4
+ * @package Siratik/WP-Component-Library
5
+ */
6
+
7
+ export { WeeklyHoursControl } from "./WeeklyHoursControl";
8
+ export { SpecialHoursControl } from "./SpecialHoursControl";
@@ -0,0 +1,172 @@
1
+ # Button Component
2
+
3
+ A frontend button component for rendering styled buttons with accessibility features. This component provides a consistent button implementation with support for different sizes, styles, and screen reader text.
4
+
5
+ ## Features
6
+
7
+ - Multiple button styles and sizes
8
+ - Screen reader support for accessibility
9
+ - BEM-style CSS class generation
10
+ - Flexible wrapper attributes
11
+ - Link target support for external links
12
+ - Consistent styling system
13
+
14
+ ## Usage
15
+
16
+ ```jsx
17
+ import { Button } from '@siratik/wp-component-library/components/button';
18
+
19
+ // Basic button
20
+ <Button
21
+ text="Click Me"
22
+ link="/about"
23
+ />
24
+
25
+ // Styled button with size variant
26
+ <Button
27
+ text="Get Started"
28
+ link="/signup"
29
+ style="primary"
30
+ size="large"
31
+ className="cta-button"
32
+ />
33
+
34
+ // External link with screen reader text
35
+ <Button
36
+ text="View Report"
37
+ link="https://example.com/report"
38
+ target="_blank"
39
+ screenReader="Opens in new window"
40
+ />
41
+ ```
42
+
43
+ ## Props
44
+
45
+ | Prop | Type | Default | Description |
46
+ |------|------|---------|-------------|
47
+ | `className` | `string` | - | Additional CSS classes for the wrapper |
48
+ | `size` | `string` | `"default"` | Button size variant (small, default, large, etc.) |
49
+ | `style` | `string` | `"default"` | Button style variant (default, primary, secondary, etc.) |
50
+ | `text` | `string` | `"Button Text"` | Button display text |
51
+ | `link` | `string` | `"#"` | Button URL/href |
52
+ | `target` | `string` | - | Link target attribute (_blank, _self, etc.) |
53
+ | `screenReader` | `string` | - | Additional text for screen readers |
54
+ | `wrapperAttributes` | `object` | - | Additional HTML attributes for wrapper |
55
+
56
+ ## Button Styles
57
+
58
+ The component supports various style variants through the `style` prop:
59
+ - `default` - Standard button appearance
60
+ - `primary` - Primary action button
61
+ - `secondary` - Secondary action button
62
+ - Custom styles can be added via CSS
63
+
64
+ ## Button Sizes
65
+
66
+ Available size variants through the `size` prop:
67
+ - `small` - Compact button
68
+ - `default` - Standard size
69
+ - `large` - Larger button
70
+ - Custom sizes can be defined via CSS
71
+
72
+ ## CSS Classes
73
+
74
+ The component generates BEM-style classes:
75
+
76
+ ### Wrapper Classes
77
+ - Base: `wp-block-kitaris-button`
78
+ - Style modifier: `is-style-{style}`
79
+ - Size modifier: `is-size-{size}`
80
+ - Custom classes: Added via `className` prop
81
+
82
+ ### Inner Elements
83
+ - Link text: `wp-block-kitaris-button__text`
84
+ - Screen reader text: `screen-reader-text`
85
+
86
+ ## Accessibility
87
+
88
+ ### Screen Reader Support
89
+ Additional context for screen readers can be provided:
90
+ ```jsx
91
+ <Button
92
+ text="Download"
93
+ link="/file.pdf"
94
+ screenReader="PDF document, 2.5MB"
95
+ />
96
+ ```
97
+
98
+ ### Target Attribute
99
+ When using `target="_blank"`, consider adding screen reader text:
100
+ ```jsx
101
+ <Button
102
+ text="External Site"
103
+ link="https://example.com"
104
+ target="_blank"
105
+ screenReader="Opens in new window"
106
+ />
107
+ ```
108
+
109
+ ## Examples
110
+
111
+ ### Call-to-Action Button
112
+ ```jsx
113
+ <Button
114
+ text="Start Free Trial"
115
+ link="/trial"
116
+ style="primary"
117
+ size="large"
118
+ className="hero-cta"
119
+ />
120
+ ```
121
+
122
+ ### Download Button
123
+ ```jsx
124
+ <Button
125
+ text="Download Guide"
126
+ link="/downloads/guide.pdf"
127
+ style="secondary"
128
+ screenReader="PDF, 5MB"
129
+ wrapperAttributes={{
130
+ 'data-track': 'download',
131
+ 'data-file': 'guide.pdf'
132
+ }}
133
+ />
134
+ ```
135
+
136
+ ### Navigation Button
137
+ ```jsx
138
+ <Button
139
+ text="Next Page"
140
+ link="/page/2"
141
+ size="small"
142
+ className="pagination-button"
143
+ />
144
+ ```
145
+
146
+ ### External Link Button
147
+ ```jsx
148
+ <Button
149
+ text="Visit Partner Site"
150
+ link="https://partner.com"
151
+ target="_blank"
152
+ style="outline"
153
+ screenReader="Opens partner.com in new window"
154
+ />
155
+ ```
156
+
157
+ ## Styling
158
+
159
+ The component is designed to work with your theme's button styles. You can customize appearance by:
160
+
161
+ 1. Using predefined style and size variants
162
+ 2. Adding custom CSS classes via `className`
163
+ 3. Defining CSS rules for the generated BEM classes
164
+ 4. Using wrapper attributes for data attributes or additional HTML attributes
165
+
166
+ ## Notes
167
+
168
+ - This is a presentational component that renders button-like elements using spans
169
+ - For actual form submissions or interactive buttons, use the WordPress Button component
170
+ - The component maintains consistent structure for reliable styling
171
+ - Screen reader text is properly hidden visually but remains accessible
172
+ - All props are optional with sensible defaults
@@ -0,0 +1,84 @@
1
+ /** Variants, as on kitaris/button. */
2
+ const VARIANTS = ["primary", "secondary", "tertiary", "text-link"];
3
+
4
+ /**
5
+ * Style and variant classes, as siratik-utility's Button::render() writes
6
+ * them. `style` is the block style ("" or "fill", "outline"); the older form,
7
+ * a variant passed as `style` ("default", "secondary", "tertiary",
8
+ * "text-link"), still works.
9
+ *
10
+ * @param {string} style Block style, or a legacy variant.
11
+ * @param {string} variant Variant.
12
+ * @return {string} Classes.
13
+ */
14
+ const styleAndVariantClasses = (style, variant) => {
15
+ let nextStyle = style || "";
16
+ let nextVariant = variant || "";
17
+
18
+ if (nextStyle === "default" || VARIANTS.includes(nextStyle)) {
19
+ if (!nextVariant && nextStyle !== "default") {
20
+ nextVariant = nextStyle;
21
+ }
22
+ nextStyle = "";
23
+ }
24
+ if (!VARIANTS.includes(nextVariant)) {
25
+ nextVariant = "primary";
26
+ }
27
+
28
+ return [
29
+ nextStyle && nextStyle !== "fill" ? `is-style-${nextStyle}` : "",
30
+ `is-variant-${nextVariant}`,
31
+ ]
32
+ .filter(Boolean)
33
+ .join(" ");
34
+ };
35
+
36
+ /**
37
+ * Render a button component.
38
+ *
39
+ * @since 0.1.0
40
+ *
41
+ * @param {Object} props Component properties.
42
+ * @param {string} props.className Optional. Additional class for the button.
43
+ * @param {string} props.style Optional. Block style: "" (Fill) or "outline".
44
+ * Legacy: a variant name.
45
+ * @param {string} props.variant Optional. "primary" (default), "secondary",
46
+ * "tertiary" or "text-link".
47
+ * @param {string} props.text Button text. Defaults to 'Button Text'.
48
+ * @param {string} props.link Button link. Defaults to '#'.
49
+ * @param {string|null} props.target Optional. Link target.
50
+ * @param {string|null} props.screenReader Optional. Screen reader text.
51
+ * @returns {WPElement} Button element to render.
52
+ */
53
+ function ButtonFrontend({
54
+ className = null,
55
+ size = "default",
56
+ style = "",
57
+ variant = "",
58
+ text = "Button Text",
59
+ screenReader = null,
60
+ }) {
61
+ // Add screen reader text
62
+ const screenReaderElement = screenReader ? (
63
+ <span className="screen-reader-only">{screenReader}</span>
64
+ ) : null;
65
+
66
+ // Add classes
67
+ const wrapperClass = className
68
+ ? `${className}__button is-size-${size}`
69
+ : `wp-block-kitaris-button ${styleAndVariantClasses(style, variant)} is-size-${size}`;
70
+ const linkClass = className
71
+ ? `wp-block-kitaris-button__link wp-block-kitaris-button__text`
72
+ : "wp-block-kitaris-button__text";
73
+
74
+ return (
75
+ <span className={wrapperClass}>
76
+ <span className={linkClass}>
77
+ {text}
78
+ {screenReaderElement}
79
+ </span>
80
+ </span>
81
+ );
82
+ }
83
+
84
+ export { ButtonFrontend };