@fpkit/acss 6.2.0 → 6.4.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 (121) hide show
  1. package/libs/chunk-25KCUE3R.cjs +17 -0
  2. package/libs/chunk-25KCUE3R.cjs.map +1 -0
  3. package/libs/chunk-34NWHFHP.js +10 -0
  4. package/libs/chunk-34NWHFHP.js.map +1 -0
  5. package/libs/{chunk-SQ44OCJ2.js → chunk-6NMLU5FA.js} +2 -2
  6. package/libs/{chunk-GVVCXXKI.cjs → chunk-6YVR4TDM.cjs} +3 -3
  7. package/libs/chunk-DSQ2TUCR.js +7 -0
  8. package/libs/chunk-DSQ2TUCR.js.map +1 -0
  9. package/libs/{chunk-H6A2CUWA.js → chunk-VQTCTLFN.js} +2 -2
  10. package/libs/chunk-ZJ4RUKI2.cjs +14 -0
  11. package/libs/chunk-ZJ4RUKI2.cjs.map +1 -0
  12. package/libs/{chunk-H4JRUNKU.cjs → chunk-ZOPHCNFD.cjs} +3 -3
  13. package/libs/components/alert/alert.css +1 -1
  14. package/libs/components/alert/alert.css.map +1 -1
  15. package/libs/components/alert/alert.min.css +2 -2
  16. package/libs/components/button.cjs +3 -3
  17. package/libs/components/button.d.cts +34 -1
  18. package/libs/components/button.d.ts +34 -1
  19. package/libs/components/button.js +1 -1
  20. package/libs/components/buttons/button.css +1 -1
  21. package/libs/components/buttons/button.css.map +1 -1
  22. package/libs/components/buttons/button.min.css +2 -2
  23. package/libs/components/buttons/icon-button.css +1 -0
  24. package/libs/components/buttons/icon-button.css.map +1 -0
  25. package/libs/components/buttons/icon-button.min.css +3 -0
  26. package/libs/components/dialog/dialog.cjs +4 -4
  27. package/libs/components/dialog/dialog.css +1 -1
  28. package/libs/components/dialog/dialog.css.map +1 -1
  29. package/libs/components/dialog/dialog.js +2 -2
  30. package/libs/components/dialog/dialog.min.css +2 -2
  31. package/libs/components/link/link.css +1 -1
  32. package/libs/components/link/link.min.css +1 -1
  33. package/libs/components/modal.cjs +3 -3
  34. package/libs/components/modal.js +2 -2
  35. package/libs/components/popover/popover.cjs +3 -8
  36. package/libs/components/popover/popover.css +1 -0
  37. package/libs/components/popover/popover.css.map +1 -0
  38. package/libs/components/popover/popover.d.cts +54 -26
  39. package/libs/components/popover/popover.d.ts +54 -26
  40. package/libs/components/popover/popover.js +1 -2
  41. package/libs/components/popover/popover.min.css +3 -0
  42. package/libs/hooks.cjs +3 -6
  43. package/libs/hooks.cjs.map +1 -1
  44. package/libs/hooks.d.cts +30 -10
  45. package/libs/hooks.d.ts +30 -10
  46. package/libs/hooks.js +5 -1
  47. package/libs/hooks.js.map +1 -1
  48. package/libs/index.cjs +35 -35
  49. package/libs/index.cjs.map +1 -1
  50. package/libs/index.css +1 -1
  51. package/libs/index.css.map +1 -1
  52. package/libs/index.d.cts +65 -3
  53. package/libs/index.d.ts +65 -3
  54. package/libs/index.js +9 -10
  55. package/libs/index.js.map +1 -1
  56. package/package.json +2 -2
  57. package/src/components/alert/alert.scss +0 -13
  58. package/src/components/buttons/README.mdx +107 -11
  59. package/src/components/buttons/STYLES.mdx +182 -47
  60. package/src/components/buttons/button.scss +93 -16
  61. package/src/components/buttons/button.stories.tsx +149 -0
  62. package/src/components/buttons/button.test.tsx +12 -0
  63. package/src/components/buttons/button.tsx +50 -6
  64. package/src/components/buttons/icon-button.mdx +204 -0
  65. package/src/components/buttons/icon-button.scss +83 -0
  66. package/src/components/buttons/icon-button.stories.tsx +200 -0
  67. package/src/components/buttons/icon-button.test.tsx +132 -0
  68. package/src/components/buttons/icon-button.tsx +75 -0
  69. package/src/components/dialog/dialog-modal.stories.tsx +71 -0
  70. package/src/components/dialog/dialog-modal.tsx +29 -3
  71. package/src/components/dialog/dialog.scss +1 -0
  72. package/src/components/dialog/dialog.test.tsx +119 -0
  73. package/src/components/dialog/dialog.types.ts +8 -1
  74. package/src/components/form/select.tsx +55 -51
  75. package/src/components/link/link.scss +2 -2
  76. package/src/components/popover/README.mdx +478 -0
  77. package/src/components/popover/STYLES.mdx +389 -0
  78. package/src/components/popover/index.ts +3 -0
  79. package/src/components/popover/popover.scss +249 -0
  80. package/src/components/popover/popover.stories.tsx +315 -15
  81. package/src/components/popover/popover.test.tsx +249 -37
  82. package/src/components/popover/popover.tsx +165 -62
  83. package/src/hooks/popover/popover.tsx +26 -10
  84. package/src/hooks/popover/use-popover.tsx +30 -10
  85. package/src/hooks.ts +5 -0
  86. package/src/index.scss +1 -0
  87. package/src/index.ts +1 -0
  88. package/src/sass/utilities/_display.scss +156 -0
  89. package/src/sass/utilities/_index.scss +3 -0
  90. package/src/sass/utilities/display.mdx +203 -0
  91. package/src/sass/utilities/display.stories.tsx +141 -0
  92. package/src/styles/alert/alert.css +0 -13
  93. package/src/styles/alert/alert.css.map +1 -1
  94. package/src/styles/buttons/button.css +78 -16
  95. package/src/styles/buttons/button.css.map +1 -1
  96. package/src/styles/buttons/icon-button.css +71 -0
  97. package/src/styles/buttons/icon-button.css.map +1 -0
  98. package/src/styles/dialog/dialog.css +1 -0
  99. package/src/styles/dialog/dialog.css.map +1 -1
  100. package/src/styles/index.css +404 -31
  101. package/src/styles/index.css.map +1 -1
  102. package/src/styles/link/link.css +2 -2
  103. package/src/styles/popover/popover.css +190 -0
  104. package/src/styles/popover/popover.css.map +1 -0
  105. package/src/types/popover.d.ts +64 -0
  106. package/libs/chunk-4I5MF54P.js +0 -8
  107. package/libs/chunk-4I5MF54P.js.map +0 -1
  108. package/libs/chunk-GCGKYLDG.js +0 -7
  109. package/libs/chunk-GCGKYLDG.js.map +0 -1
  110. package/libs/chunk-NZVSXRTB.cjs +0 -16
  111. package/libs/chunk-NZVSXRTB.cjs.map +0 -1
  112. package/libs/chunk-PDD4N5P5.cjs +0 -10
  113. package/libs/chunk-PDD4N5P5.cjs.map +0 -1
  114. package/libs/chunk-S7NIA6PI.cjs +0 -17
  115. package/libs/chunk-S7NIA6PI.cjs.map +0 -1
  116. package/libs/chunk-X2RDXWH5.js +0 -10
  117. package/libs/chunk-X2RDXWH5.js.map +0 -1
  118. /package/libs/{chunk-SQ44OCJ2.js.map → chunk-6NMLU5FA.js.map} +0 -0
  119. /package/libs/{chunk-GVVCXXKI.cjs.map → chunk-6YVR4TDM.cjs.map} +0 -0
  120. /package/libs/{chunk-H6A2CUWA.js.map → chunk-VQTCTLFN.js.map} +0 -0
  121. /package/libs/{chunk-H4JRUNKU.cjs.map → chunk-ZOPHCNFD.cjs.map} +0 -0
@@ -446,5 +446,124 @@ describe("DialogModal", () => {
446
446
  expect(triggerButton).toBeInTheDocument();
447
447
  expect(triggerButton).not.toBeDisabled();
448
448
  });
449
+
450
+ it("adds aria-haspopup='dialog' to the regular button trigger", () => {
451
+ render(
452
+ <DialogModal dialogTitle="Test" btnLabel="Open">
453
+ Content
454
+ </DialogModal>
455
+ );
456
+
457
+ const triggerButton = screen.getByRole("button", { name: /open/i });
458
+ expect(triggerButton).toHaveAttribute("aria-haspopup", "dialog");
459
+ });
460
+ });
461
+
462
+ describe("Icon Button Trigger", () => {
463
+ const TestIcon = () => (
464
+ <svg data-testid="test-icon" aria-hidden="true">
465
+ <circle cx="12" cy="12" r="10" />
466
+ </svg>
467
+ );
468
+
469
+ it("renders IconButton when icon prop is provided", () => {
470
+ render(
471
+ <DialogModal dialogTitle="Test" btnLabel="Settings" icon={<TestIcon />}>
472
+ Content
473
+ </DialogModal>
474
+ );
475
+
476
+ const iconButton = screen.getByRole("button", { name: /settings/i });
477
+ expect(iconButton).toHaveAttribute("data-icon-btn");
478
+ });
479
+
480
+ it("renders regular Button when icon prop is not provided", () => {
481
+ render(
482
+ <DialogModal dialogTitle="Test" btnLabel="Open">
483
+ Content
484
+ </DialogModal>
485
+ );
486
+
487
+ const button = screen.getByRole("button", { name: /open/i });
488
+ expect(button).not.toHaveAttribute("data-icon-btn");
489
+ });
490
+
491
+ it("uses btnLabel as aria-label on the icon button", () => {
492
+ render(
493
+ <DialogModal dialogTitle="Test" btnLabel="Settings" icon={<TestIcon />}>
494
+ Content
495
+ </DialogModal>
496
+ );
497
+
498
+ const iconButton = screen.getByRole("button", { name: /settings/i });
499
+ expect(iconButton).toHaveAttribute("aria-label", "Settings");
500
+ });
501
+
502
+ it("passes btnLabel as visible label on the icon button", () => {
503
+ render(
504
+ <DialogModal dialogTitle="Test" btnLabel="Settings" icon={<TestIcon />}>
505
+ Content
506
+ </DialogModal>
507
+ );
508
+
509
+ // IconButton renders the label in a span with data-icon-label
510
+ const iconButton = screen.getByRole("button", { name: /settings/i });
511
+ expect(iconButton.querySelector("[data-icon-label]")).toHaveTextContent("Settings");
512
+ });
513
+
514
+ it("adds aria-haspopup='dialog' to the icon button trigger", () => {
515
+ render(
516
+ <DialogModal dialogTitle="Test" btnLabel="Settings" icon={<TestIcon />}>
517
+ Content
518
+ </DialogModal>
519
+ );
520
+
521
+ const iconButton = screen.getByRole("button", { name: /settings/i });
522
+ expect(iconButton).toHaveAttribute("aria-haspopup", "dialog");
523
+ });
524
+
525
+ it("opens dialog when icon button is clicked", async () => {
526
+ const user = userEvent.setup();
527
+
528
+ render(
529
+ <DialogModal dialogTitle="Test Dialog" btnLabel="Settings" icon={<TestIcon />}>
530
+ Dialog content
531
+ </DialogModal>
532
+ );
533
+
534
+ const iconButton = screen.getByRole("button", { name: /settings/i });
535
+ await user.click(iconButton);
536
+
537
+ await waitFor(() => {
538
+ expect(screen.getByRole("dialog")).toBeInTheDocument();
539
+ expect(screen.getByText("Dialog content")).toBeInTheDocument();
540
+ });
541
+ });
542
+
543
+ it("applies btnSize to the icon button", () => {
544
+ render(
545
+ <DialogModal dialogTitle="Test" btnLabel="Settings" icon={<TestIcon />} btnSize="lg">
546
+ Content
547
+ </DialogModal>
548
+ );
549
+
550
+ const iconButton = screen.getByRole("button", { name: /settings/i });
551
+ expect(iconButton).toHaveAttribute("data-btn", "lg");
552
+ });
553
+
554
+ it("forwards btnProps to the icon button", () => {
555
+ render(
556
+ <DialogModal
557
+ dialogTitle="Test"
558
+ btnLabel="Settings"
559
+ icon={<TestIcon />}
560
+ btnProps={{ "data-testid": "icon-trigger" }}
561
+ >
562
+ Content
563
+ </DialogModal>
564
+ );
565
+
566
+ expect(screen.getByTestId("icon-trigger")).toBeInTheDocument();
567
+ });
449
568
  });
450
569
  });
@@ -1,4 +1,4 @@
1
- import { CSSProperties, ReactNode } from "react";
1
+ import { CSSProperties, ReactElement, ReactNode } from "react";
2
2
 
3
3
  /**
4
4
  * Base properties shared by all dialog variants.
@@ -70,6 +70,7 @@ export interface DialogProps extends BaseDialogProps {
70
70
  * @property {string} [btnLabel="Open Dialog"] - Text label for the trigger button
71
71
  * @property {"sm" | "md" | "lg"} [btnSize="sm"] - Size variant for the trigger button
72
72
  * @property {() => void} [btnOnClick] - Callback fired when trigger button is clicked (before opening)
73
+ * @property {ReactElement} [icon] - Optional icon element. When provided, renders IconButton instead of Button as trigger.
73
74
  */
74
75
  export interface DialogModalProps extends BaseDialogProps {
75
76
  /** If true, renders as non-modal inline alert using dialog.show() */
@@ -92,6 +93,12 @@ export interface DialogModalProps extends BaseDialogProps {
92
93
  btnOnClick?: () => void;
93
94
  /** Additional props to pass to the trigger button component */
94
95
  btnProps?: Record<string, unknown>;
96
+ /**
97
+ * Optional icon element. When provided, renders an IconButton instead of a regular Button as the trigger.
98
+ * `btnLabel` serves as both `aria-label` and the visible label text (shown at desktop widths via IconButton's responsive label).
99
+ * Note: `aria-labelledby` cannot be passed via `btnProps` when icon is set — use `btnLabel` instead.
100
+ */
101
+ icon?: ReactElement;
95
102
  }
96
103
 
97
104
  /**
@@ -1,9 +1,9 @@
1
- import UI from '../ui'
2
- import React from 'react'
3
- import { useDisabledState } from '../../hooks/use-disabled-state'
1
+ import UI from "../ui";
2
+ import React from "react";
3
+ import { useDisabledState } from "../../hooks/use-disabled-state";
4
4
 
5
- export type { SelectProps } from './form.types'
6
- import type { SelectProps } from './form.types'
5
+ export type { SelectProps } from "./form.types";
6
+ import type { SelectProps } from "./form.types";
7
7
 
8
8
  /**
9
9
  * Option component props interface
@@ -11,56 +11,57 @@ import type { SelectProps } from './form.types'
11
11
  *
12
12
  * @interface OptionProps
13
13
  */
14
- export interface OptionProps extends Omit<React.ComponentPropsWithoutRef<'option'>, 'className'> {
14
+ export interface OptionProps
15
+ extends Omit<React.ComponentPropsWithoutRef<"option">, "className"> {
15
16
  /**
16
17
  * Value for the select option (required, unless using legacy selectValue)
17
18
  */
18
- value?: string | number
19
+ value?: string | number;
19
20
 
20
21
  /**
21
22
  * Display label for the option (defaults to value if not provided)
22
23
  */
23
- label?: string
24
+ label?: string;
24
25
 
25
26
  /**
26
27
  * CSS class names (preferred over 'className' for consistency with fpkit components)
27
28
  */
28
- classes?: string
29
+ classes?: string;
29
30
 
30
31
  /**
31
32
  * Inline CSS styles object
32
33
  */
33
- styles?: React.CSSProperties
34
+ styles?: React.CSSProperties;
34
35
 
35
36
  /**
36
37
  * Disabled state for the option
37
38
  * @default false
38
39
  */
39
- disabled?: boolean
40
+ disabled?: boolean;
40
41
 
41
42
  /**
42
43
  * Children content (overrides label if provided)
43
44
  */
44
- children?: React.ReactNode
45
+ children?: React.ReactNode;
45
46
 
46
47
  /**
47
48
  * Visual variant for styling via data-option attribute
48
49
  * Use with CSS: option[data-option="primary"] { ... }
49
50
  * @example 'primary' | 'secondary' | 'success' | 'error'
50
51
  */
51
- variant?: string
52
+ variant?: string;
52
53
 
53
54
  /**
54
55
  * Size variant for styling via data-size attribute
55
56
  * @example 'sm' | 'md' | 'lg'
56
57
  */
57
- size?: string
58
+ size?: string;
58
59
 
59
60
  /**
60
61
  * Additional data attributes for custom styling
61
62
  * @example { 'data-highlighted': true, 'data-category': 'premium' }
62
63
  */
63
- dataAttributes?: Record<string, string | boolean | number>
64
+ dataAttributes?: Record<string, string | boolean | number>;
64
65
  }
65
66
 
66
67
  /**
@@ -88,7 +89,10 @@ export interface OptionProps extends Omit<React.ComponentPropsWithoutRef<'option
88
89
  * @param {OptionProps} props - Component props
89
90
  * @returns {JSX.Element} Option element
90
91
  */
91
- export const Option = React.forwardRef<HTMLOptionElement, OptionProps & Partial<SelectOptionsProps>>(
92
+ export const Option = React.forwardRef<
93
+ HTMLOptionElement,
94
+ OptionProps & Partial<SelectOptionsProps>
95
+ >(
92
96
  (
93
97
  {
94
98
  value,
@@ -105,18 +109,18 @@ export const Option = React.forwardRef<HTMLOptionElement, OptionProps & Partial<
105
109
  selectLabel,
106
110
  ...props
107
111
  },
108
- ref
112
+ ref,
109
113
  ) => {
110
114
  // Map legacy props to new props
111
- const optionValue = value ?? selectValue
112
- const optionLabel = label ?? selectLabel
115
+ const optionValue = value ?? selectValue;
116
+ const optionLabel = label ?? selectLabel;
113
117
 
114
118
  // Build data attributes object for styling
115
119
  const combinedDataAttrs = {
116
- ...(variant && { 'data-option': variant }),
117
- ...(size && { 'data-size': size }),
120
+ ...(variant && { "data-option": variant }),
121
+ ...(size && { "data-size": size }),
118
122
  ...dataAttributes,
119
- }
123
+ };
120
124
 
121
125
  return (
122
126
  <UI
@@ -131,17 +135,17 @@ export const Option = React.forwardRef<HTMLOptionElement, OptionProps & Partial<
131
135
  >
132
136
  {children || optionLabel || optionValue}
133
137
  </UI>
134
- )
135
- }
136
- )
138
+ );
139
+ },
140
+ );
137
141
 
138
- Option.displayName = 'Select.Option'
142
+ Option.displayName = "Select.Option";
139
143
 
140
144
  // Legacy type export for backwards compatibility
141
- export type SelectOptionsProps = Omit<OptionProps, 'classes' | 'styles'> & {
142
- selectValue: string | number
143
- selectLabel?: string
144
- }
145
+ export type SelectOptionsProps = Omit<OptionProps, "classes" | "styles"> & {
146
+ selectValue: string | number;
147
+ selectLabel?: string;
148
+ };
145
149
 
146
150
  /**
147
151
  * Select component - Accessible dropdown selection input with validation support
@@ -187,7 +191,7 @@ export const Select = React.forwardRef<HTMLSelectElement, SelectProps>(
187
191
  children,
188
192
  required,
189
193
  selected,
190
- validationState = 'none',
194
+ validationState = "none",
191
195
  errorMessage,
192
196
  hintText,
193
197
  onBlur,
@@ -197,7 +201,7 @@ export const Select = React.forwardRef<HTMLSelectElement, SelectProps>(
197
201
  onEnter,
198
202
  ...props
199
203
  },
200
- ref
204
+ ref,
201
205
  ) => {
202
206
  // Use the disabled state hook with enhanced API for automatic className merging
203
207
  const { disabledProps, handlers } = useDisabledState<HTMLSelectElement>(
@@ -210,33 +214,33 @@ export const Select = React.forwardRef<HTMLSelectElement, SelectProps>(
210
214
  onKeyDown: (e: React.KeyboardEvent<HTMLSelectElement>) => {
211
215
  // Handle Enter key press for accessibility
212
216
  // Enables keyboard-only users to trigger actions after selection
213
- if (e.key === 'Enter' && onEnter) {
214
- onEnter(e)
217
+ if (e.key === "Enter" && onEnter) {
218
+ onEnter(e);
215
219
  }
216
220
  // Always call consumer's onKeyDown if provided
217
221
  if (onKeyDown) {
218
- onKeyDown(e)
222
+ onKeyDown(e);
219
223
  }
220
224
  },
221
225
  },
222
226
  // Automatic className merging - hook combines disabled class with user classes
223
227
  className: classes,
224
- }
225
- )
228
+ },
229
+ );
226
230
 
227
231
  // Determine aria-invalid based on validation state
228
- const isInvalid = validationState === 'invalid'
232
+ const isInvalid = validationState === "invalid";
229
233
 
230
234
  // Generate describedby IDs for error and hint text
231
- const describedByIds: string[] = []
235
+ const describedByIds: string[] = [];
232
236
  if (errorMessage && id) {
233
- describedByIds.push(`${id}-error`)
237
+ describedByIds.push(`${id}-error`);
234
238
  }
235
239
  if (hintText && id) {
236
- describedByIds.push(`${id}-hint`)
240
+ describedByIds.push(`${id}-hint`);
237
241
  }
238
242
  const ariaDescribedBy =
239
- describedByIds.length > 0 ? describedByIds.join(' ') : undefined
243
+ describedByIds.length > 0 ? describedByIds.join(" ") : undefined;
240
244
 
241
245
  return (
242
246
  <UI
@@ -249,7 +253,7 @@ export const Select = React.forwardRef<HTMLSelectElement, SelectProps>(
249
253
  {...handlers}
250
254
  required={required}
251
255
  aria-required={required}
252
- aria-disabled={disabledProps['aria-disabled']}
256
+ aria-disabled={disabledProps["aria-disabled"]}
253
257
  aria-invalid={isInvalid}
254
258
  aria-describedby={ariaDescribedBy}
255
259
  style={styles}
@@ -257,19 +261,19 @@ export const Select = React.forwardRef<HTMLSelectElement, SelectProps>(
257
261
  >
258
262
  {children || <option value="" />}
259
263
  </UI>
260
- )
261
- }
262
- )
264
+ );
265
+ },
266
+ );
263
267
 
264
- Select.displayName = 'Select'
268
+ Select.displayName = "Select";
265
269
 
266
270
  // Create a compound component with proper typing
267
271
  type SelectComponent = typeof Select & {
268
- Option: typeof Option
269
- }
272
+ Option: typeof Option;
273
+ };
270
274
 
271
275
  // Type assertion to allow adding static property to ForwardRefExoticComponent
272
276
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
273
- ;(Select as any).Option = Option
277
+ (Select as any).Option = Option;
274
278
 
275
- export default Select as SelectComponent
279
+ export default Select as SelectComponent;
@@ -98,7 +98,7 @@ a[href] {
98
98
  font-size: var(--link-fs);
99
99
  padding-inline: var(--link-fs);
100
100
  padding-block: calc(var(--link-fs) - 0.4rem);
101
- border-radius: var(--link-radius, 99rem);
101
+ border-radius: var(--link-radius, 100vw);
102
102
  display: inline-flex;
103
103
  align-items: center;
104
104
  justify-content: center;
@@ -126,7 +126,7 @@ a[href] {
126
126
  // Pill variant (rounded corners)
127
127
  &[data-link~="pill"],
128
128
  &:has(> i) {
129
- --link-radius: 99rem;
129
+ --link-radius: 100vw;
130
130
  --link-decoration: none;
131
131
  font-style: normal;
132
132