@dynamic-framework/ui-react 2.10.0 → 2.11.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 (145) hide show
  1. package/dist/DPortalStack-6zJm6JoD.js +14 -0
  2. package/dist/DPortalStack-6zJm6JoD.js.map +1 -0
  3. package/dist/DPortalStack-D1-GGU4C.js +12 -0
  4. package/dist/DPortalStack-D1-GGU4C.js.map +1 -0
  5. package/dist/api.json +7429 -0
  6. package/dist/css/dynamic-ui.css +95 -26
  7. package/dist/css/dynamic-ui.min.css +2 -2
  8. package/dist/esm/components/DAlert/DAlert.js +2 -2
  9. package/dist/esm/components/DAlert/DAlert.js.map +1 -1
  10. package/dist/esm/components/DButtonIcon/DButtonIcon.js +6 -3
  11. package/dist/esm/components/DButtonIcon/DButtonIcon.js.map +1 -1
  12. package/dist/esm/components/DDataStateWrapper/DDataStateWrapper.js +10 -3
  13. package/dist/esm/components/DDataStateWrapper/DDataStateWrapper.js.map +1 -1
  14. package/dist/esm/components/DErrorBoundary/DErrorBoundary.js +3 -3
  15. package/dist/esm/components/DErrorBoundary/DErrorBoundary.js.map +1 -1
  16. package/dist/esm/components/DErrorBoundary/components/DefaultErrorBoundary.js +2 -2
  17. package/dist/esm/components/DErrorBoundary/components/DefaultErrorBoundary.js.map +1 -1
  18. package/dist/esm/components/DInput/DInput.js +15 -3
  19. package/dist/esm/components/DInput/DInput.js.map +1 -1
  20. package/dist/esm/components/DInputCheck/DInputCheck.js +45 -8
  21. package/dist/esm/components/DInputCheck/DInputCheck.js.map +1 -1
  22. package/dist/esm/components/DInputCounter/DInputCounter.js +78 -20
  23. package/dist/esm/components/DInputCounter/DInputCounter.js.map +1 -1
  24. package/dist/esm/components/DInputCurrency/DInputCurrency.js +8 -3
  25. package/dist/esm/components/DInputCurrency/DInputCurrency.js.map +1 -1
  26. package/dist/esm/components/DInputPhone/DInputPhone.js +14 -2
  27. package/dist/esm/components/DInputPhone/DInputPhone.js.map +1 -1
  28. package/dist/esm/components/DInputPin/DInputPin.js +3 -1
  29. package/dist/esm/components/DInputPin/DInputPin.js.map +1 -1
  30. package/dist/esm/components/DInputRange/DInputRange.js +16 -2
  31. package/dist/esm/components/DInputRange/DInputRange.js.map +1 -1
  32. package/dist/esm/components/DInputSelect/DInputSelect.js +18 -4
  33. package/dist/esm/components/DInputSelect/DInputSelect.js.map +1 -1
  34. package/dist/esm/components/DInputSwitch/DInputSwitch.js +20 -9
  35. package/dist/esm/components/DInputSwitch/DInputSwitch.js.map +1 -1
  36. package/dist/esm/components/DListGroup/DListGroup.js +10 -2
  37. package/dist/esm/components/DListGroup/DListGroup.js.map +1 -1
  38. package/dist/esm/components/DListGroup/ListGroupContext.js +11 -0
  39. package/dist/esm/components/DListGroup/ListGroupContext.js.map +1 -0
  40. package/dist/esm/components/DListGroup/components/DListGroupItem.js +34 -16
  41. package/dist/esm/components/DListGroup/components/DListGroupItem.js.map +1 -1
  42. package/dist/esm/components/DListGroup/components/warnInvalidListMarkup.js +30 -0
  43. package/dist/esm/components/DListGroup/components/warnInvalidListMarkup.js.map +1 -0
  44. package/dist/esm/components/DPasswordStrengthMeter/DPasswordStrengthMeter.js +2 -2
  45. package/dist/esm/components/DPasswordStrengthMeter/DPasswordStrengthMeter.js.map +1 -1
  46. package/dist/esm/components/DProgress/DProgress.js +9 -3
  47. package/dist/esm/components/DProgress/DProgress.js.map +1 -1
  48. package/dist/esm/components/DSelect/DSelect.js +31 -3
  49. package/dist/esm/components/DSelect/DSelect.js.map +1 -1
  50. package/dist/esm/components/DSelect/createAriaGuidance.js +57 -0
  51. package/dist/esm/components/DSelect/createAriaGuidance.js.map +1 -0
  52. package/dist/esm/components/DTabs/DTabs.js +29 -40
  53. package/dist/esm/components/DTabs/DTabs.js.map +1 -1
  54. package/dist/esm/components/DTabs/TabContext.js +3 -2
  55. package/dist/esm/components/DTabs/TabContext.js.map +1 -1
  56. package/dist/esm/components/DTabs/components/DTabContent.js +2 -2
  57. package/dist/esm/components/DTabs/components/DTabContent.js.map +1 -1
  58. package/dist/esm/components/DTabs/components/DTabsProvider.js +22 -0
  59. package/dist/esm/components/DTabs/components/DTabsProvider.js.map +1 -0
  60. package/dist/esm/components/DTabs/index.js +1 -0
  61. package/dist/esm/components/DTabs/index.js.map +1 -1
  62. package/dist/esm/components/DToast/DToast.js +7 -2
  63. package/dist/esm/components/DToast/DToast.js.map +1 -1
  64. package/dist/esm/components/DToastContainer/useDToast.js +5 -5
  65. package/dist/esm/components/DToastContainer/useDToast.js.map +1 -1
  66. package/dist/esm/components/internal/DFormLabel.js +46 -0
  67. package/dist/esm/components/internal/DFormLabel.js.map +1 -0
  68. package/dist/esm/contexts/DPortalContext.js +43 -8
  69. package/dist/esm/contexts/DPortalContext.js.map +1 -1
  70. package/dist/esm/contexts/portal/DPortalStack.js +12 -0
  71. package/dist/esm/contexts/portal/DPortalStack.js.map +1 -0
  72. package/dist/esm/hooks/useControlledState.js +51 -0
  73. package/dist/esm/hooks/useControlledState.js.map +1 -0
  74. package/dist/esm/hooks/useInputCurrency.js +73 -19
  75. package/dist/esm/hooks/useInputCurrency.js.map +1 -1
  76. package/dist/esm/hooks/useMediaBreakpointUp.js +42 -3
  77. package/dist/esm/hooks/useMediaBreakpointUp.js.map +1 -1
  78. package/dist/esm/hooks/useResponsiveProp.js +13 -2
  79. package/dist/esm/hooks/useResponsiveProp.js.map +1 -1
  80. package/dist/esm/index.js +1 -0
  81. package/dist/esm/index.js.map +1 -1
  82. package/dist/esm/utils/hasLabelContent.js +48 -0
  83. package/dist/esm/utils/hasLabelContent.js.map +1 -0
  84. package/dist/esm/utils/isTextLabel.js +23 -0
  85. package/dist/esm/utils/isTextLabel.js.map +1 -0
  86. package/dist/esm/utils/labelClickGuard.js +61 -0
  87. package/dist/esm/utils/labelClickGuard.js.map +1 -0
  88. package/dist/esm/utils/warnLabelUsage.js +57 -0
  89. package/dist/esm/utils/warnLabelUsage.js.map +1 -0
  90. package/dist/icons-core.json +1 -1
  91. package/dist/index.esm.js +865 -163
  92. package/dist/index.esm.js.map +1 -1
  93. package/dist/index.js +864 -161
  94. package/dist/index.js.map +1 -1
  95. package/dist/types/components/DAlert/DAlert.d.ts +16 -2
  96. package/dist/types/components/DButtonIcon/DButtonIcon.d.ts +9 -1
  97. package/dist/types/components/DDataStateWrapper/DDataStateWrapper.d.ts +29 -4
  98. package/dist/types/components/DDataStateWrapper/index.d.ts +1 -1
  99. package/dist/types/components/DErrorBoundary/DErrorBoundary.d.ts +6 -1
  100. package/dist/types/components/DErrorBoundary/components/DefaultErrorBoundary.d.ts +3 -1
  101. package/dist/types/components/DInput/DInput.d.ts +11 -2
  102. package/dist/types/components/DInputCheck/DInputCheck.d.ts +28 -3
  103. package/dist/types/components/DInputCounter/DInputCounter.d.ts +15 -3
  104. package/dist/types/components/DInputCurrency/DInputCurrency.d.ts +10 -3
  105. package/dist/types/components/DInputPhone/DInputPhone.d.ts +11 -2
  106. package/dist/types/components/DInputPin/DInputPin.d.ts +12 -2
  107. package/dist/types/components/DInputRange/DInputRange.d.ts +15 -2
  108. package/dist/types/components/DInputSelect/DInputSelect.d.ts +18 -3
  109. package/dist/types/components/DInputSwitch/DInputSwitch.d.ts +28 -3
  110. package/dist/types/components/DListGroup/DListGroup.d.ts +12 -1
  111. package/dist/types/components/DListGroup/ListGroupContext.d.ts +8 -0
  112. package/dist/types/components/DListGroup/components/DListGroupItem.d.ts +6 -1
  113. package/dist/types/components/DListGroup/components/warnInvalidListMarkup.d.ts +13 -0
  114. package/dist/types/components/DPasswordStrengthMeter/DPasswordStrengthMeter.d.ts +18 -3
  115. package/dist/types/components/DProgress/DProgress.d.ts +13 -1
  116. package/dist/types/components/DSelect/DSelect.d.ts +23 -3
  117. package/dist/types/components/DSelect/createAriaGuidance.d.ts +21 -0
  118. package/dist/types/components/DTabs/DTabs.d.ts +13 -2
  119. package/dist/types/components/DTabs/TabContext.d.ts +5 -0
  120. package/dist/types/components/DTabs/components/DTabsProvider.d.ts +11 -0
  121. package/dist/types/components/DTabs/index.d.ts +1 -0
  122. package/dist/types/components/DToast/DToast.d.ts +10 -3
  123. package/dist/types/components/DToastContainer/useDToast.d.ts +8 -1
  124. package/dist/types/components/index.d.ts +2 -2
  125. package/dist/types/components/interface.d.ts +7 -0
  126. package/dist/types/components/internal/DFormLabel.d.ts +30 -0
  127. package/dist/types/contexts/portal/DPortalStack.d.ts +13 -0
  128. package/dist/types/hooks/useControlledState.d.ts +17 -0
  129. package/dist/types/hooks/useInputCurrency.d.ts +26 -1
  130. package/dist/types/hooks/useMediaBreakpointUp.d.ts +10 -0
  131. package/dist/types/utils/hasLabelContent.d.ts +30 -0
  132. package/dist/types/utils/isTextLabel.d.ts +17 -0
  133. package/dist/types/utils/labelClickGuard.d.ts +20 -0
  134. package/dist/types/utils/warnLabelUsage.d.ts +43 -0
  135. package/package.json +8 -3
  136. package/src/style/abstracts/variables/_body.scss +2 -2
  137. package/src/style/abstracts/variables/_chips.scss +2 -0
  138. package/src/style/abstracts/variables/_colors.scss +2 -2
  139. package/src/style/base/_buttons.scss +4 -1
  140. package/src/style/base/_input-group.scss +5 -2
  141. package/src/style/base/_list-group.scss +55 -0
  142. package/src/style/components/_d-chip.scss +7 -2
  143. package/src/style/components/_d-tabs.scss +3 -0
  144. package/src/style/helpers/_color-bg.scss +5 -0
  145. package/src/style/root/_root.scss +9 -0
@@ -1,5 +1,6 @@
1
1
  import type { PropsWithChildren } from 'react';
2
2
  import DTabContent from './components/DTabContent';
3
+ import DTabsProvider from './components/DTabsProvider';
3
4
  import type { BaseProps } from '../interface';
4
5
  export type DTabOption = {
5
6
  label: string | React.ReactNode;
@@ -9,16 +10,26 @@ export type DTabOption = {
9
10
  export type TabVariant = 'tabs' | 'pills' | 'underline' | 'toggle-button-group';
10
11
  type Props = BaseProps & PropsWithChildren<{
11
12
  classNameTab?: string;
13
+ /**
14
+ * Class for the panels container. It is only rendered when `DTabs` has
15
+ * children, so a tab bar used as pure navigation leaves no empty node.
16
+ */
17
+ classNameContent?: string;
12
18
  onChange?: (option: DTabOption) => void;
13
19
  options: Array<DTabOption>;
14
- defaultSelected: string;
20
+ /**
21
+ * Ignored inside `DTabs.Provider`, which owns the selection. Without it, the
22
+ * first enabled tab is selected when omitted.
23
+ */
24
+ defaultSelected?: string;
15
25
  vertical?: boolean;
16
26
  variant?: TabVariant;
17
27
  ariaLabel?: string;
18
28
  ariaLabelledBy?: string;
19
29
  }>;
20
- declare function DTabs({ children, defaultSelected, onChange, options, className, classNameTab, style, vertical, variant, dataAttributes, ariaLabel, ariaLabelledBy, }: Props): import("react").JSX.Element;
30
+ declare function DTabs({ children, defaultSelected, onChange, options, className, classNameTab, classNameContent, style, vertical, variant, dataAttributes, ariaLabel, ariaLabelledBy, }: Props): import("react").JSX.Element;
21
31
  declare const _default: typeof DTabs & {
22
32
  Tab: typeof DTabContent;
33
+ Provider: typeof DTabsProvider;
23
34
  };
24
35
  export default _default;
@@ -4,5 +4,10 @@ type TabContextState = {
4
4
  isSelected: (tab: DTabOption['tab']) => boolean;
5
5
  };
6
6
  declare const TabContext: import("react").Context<TabContextState | undefined>;
7
+ type TabsState = {
8
+ selected: string;
9
+ setSelected: (tab: string) => void;
10
+ };
11
+ export declare const TabsStateContext: import("react").Context<TabsState | undefined>;
7
12
  export declare function useTabContext(): TabContextState;
8
13
  export default TabContext;
@@ -0,0 +1,11 @@
1
+ import type { PropsWithChildren } from 'react';
2
+ type Props = PropsWithChildren<{
3
+ defaultSelected: string;
4
+ }>;
5
+ /**
6
+ * Owns the selected tab so the tab bar (`DTabs`) and its panels (`DTabs.Tab`)
7
+ * can live anywhere inside it, e.g. the bar in a header and the panels in
8
+ * another column.
9
+ */
10
+ export default function DTabsProvider({ defaultSelected, children, }: Props): import("react").JSX.Element;
11
+ export {};
@@ -1,5 +1,6 @@
1
1
  import DTabs from './DTabs';
2
2
  export type { DTabOption } from './DTabs';
3
3
  export { default as DTabContent } from './components/DTabContent';
4
+ export { default as DTabsProvider } from './components/DTabsProvider';
4
5
  export { useTabContext } from './TabContext';
5
6
  export default DTabs;
@@ -1,9 +1,16 @@
1
1
  import { PropsWithChildren } from 'react';
2
2
  import DToastHeader from './components/DToastHeader';
3
3
  import DToastBody from './components/DToastBody';
4
- import { BaseProps } from '../interface';
5
- type Props = PropsWithChildren<BaseProps>;
6
- declare function DToast({ children, className, style, dataAttributes, }: Props): import("react").JSX.Element;
4
+ import { BaseProps, LiveRegionRole } from '../interface';
5
+ type Props = PropsWithChildren<BaseProps & {
6
+ /**
7
+ * `alert` (default) interrupts the screen reader, for critical errors.
8
+ * `status` announces without interrupting, for confirmations and other
9
+ * non-critical messages. `none` renders no live region.
10
+ */
11
+ role?: LiveRegionRole;
12
+ }>;
13
+ declare function DToast({ children, role, className, style, dataAttributes, }: Props): import("react").JSX.Element;
7
14
  declare const _default: typeof DToast & {
8
15
  Header: typeof DToastHeader;
9
16
  Body: typeof DToastBody;
@@ -1,5 +1,5 @@
1
1
  import { Renderable, Toast, ToastPosition, ValueFunction } from 'react-hot-toast';
2
- import { ComponentStateColor } from '../interface';
2
+ import { ComponentStateColor, LiveRegionRole } from '../interface';
3
3
  /**
4
4
  * Data used to render the default DToast component via `useDToast`.
5
5
  * When `description` is provided, the toast renders with a header and body layout.
@@ -19,6 +19,13 @@ export type ToastData = {
19
19
  closeIcon?: string;
20
20
  /** Applies the `toast-{color}` CSS modifier class. */
21
21
  color?: ComponentStateColor;
22
+ /**
23
+ * Live region role of the toast. Defaults to `alert`; use `status` for
24
+ * confirmations so the screen reader doesn't interrupt the user.
25
+ */
26
+ role?: LiveRegionRole;
27
+ /** Accessible name of the close button. Defaults to `"Close"`. */
28
+ closeAriaLabel?: string;
22
29
  };
23
30
  /** Options forwarded to react-hot-toast for a single toast instance. */
24
31
  export interface DToastOptions {
@@ -42,7 +42,7 @@ export { default as DStepperDesktop } from './DStepperDesktop';
42
42
  export { default as DStepperMobile } from './DStepperMobile';
43
43
  export { default as DTooltip } from './DTooltip';
44
44
  export { default as DTimeline } from './DTimeline';
45
- export { default as DTabs, useTabContext, DTabContent, } from './DTabs';
45
+ export { default as DTabs, useTabContext, DTabContent, DTabsProvider, } from './DTabs';
46
46
  export type { DTabOption } from './DTabs';
47
47
  export { default as DToast } from './DToast';
48
48
  export { default as DToastContainer, useDToast, } from './DToastContainer';
@@ -54,6 +54,6 @@ export { default as DVoucher, useScreenshot, useScreenshotDownload, useScreensho
54
54
  export { default as DOtp } from './DOtp';
55
55
  export { default as DErrorBoundary, useErrorBoundary, type FallbackProps, getErrorMessage, } from './DErrorBoundary';
56
56
  export { default as DDataStateWrapper, EmptyState, ErrorState, LoadingState, } from './DDataStateWrapper';
57
- export type { DDataStateMessages } from './DDataStateWrapper';
57
+ export type { DDataStateMessages, DDataStateWrapperListProps, DDataStateWrapperSingleProps, } from './DDataStateWrapper';
58
58
  export { default as DConfirmModalContainer } from './DConfirmModal/DConfirmModalContainer';
59
59
  export type { ConfirmModalEntry, ConfirmModalStore } from './DConfirmModal/types';
@@ -11,6 +11,13 @@ export type NavegableProps = {
11
11
  'aria-current'?: string;
12
12
  };
13
13
  export type ComponentSize = 'sm' | 'lg';
14
+ /**
15
+ * How a message is exposed to assistive technology: `alert` interrupts the
16
+ * screen reader (critical errors), `status` is announced without interrupting
17
+ * (non-critical dynamic messages) and `none` is not a live region (static
18
+ * content already on screen).
19
+ */
20
+ export type LiveRegionRole = 'alert' | 'status' | 'none';
14
21
  export type BreakpointSize = 'sm' | 'md' | 'lg' | 'xl' | 'xxl';
15
22
  export type AvatarSize = 'xs' | 'sm' | 'lg' | 'xl' | 'xxl';
16
23
  export type DataAttributes = Record<`data-${string}`, string | number | undefined | null | boolean>;
@@ -0,0 +1,30 @@
1
+ import type { ReactNode } from 'react';
2
+ type Props = {
3
+ htmlFor: string;
4
+ className?: string;
5
+ children: ReactNode;
6
+ };
7
+ /**
8
+ * The `<label>` every form control renders, with the click guard already wired.
9
+ *
10
+ * Internal: it exists so the guard — and the lint exception it needs — lives in
11
+ * one place instead of being repeated across every input component.
12
+ *
13
+ * The guard runs in the capture phase, not on bubble: a tooltip or modal
14
+ * trigger that calls `stopPropagation()` in its own handler — a common pattern —
15
+ * would otherwise keep the guard from ever running, and the browser would go on
16
+ * to activate the labelled control. Confirmed in Chromium, where the same
17
+ * trigger toggles the control with a bubble-phase guard and does not with this
18
+ * one. Capturing changes nothing else: `preventDefault` applies to the default
19
+ * action, which is evaluated after propagation either way.
20
+ *
21
+ * The `jsx-a11y` rules below assume a click handler is being used to make a
22
+ * non-interactive element interactive, which then owes the keyboard an
23
+ * equivalent. This handler does the opposite: it suppresses a pointer-only
24
+ * behaviour, the click a label forwards to its control, and adds no way to
25
+ * activate anything. Keyboard support for a nested trigger is the consumer's:
26
+ * `<a href>` carries its own, while a `span` with a role and a tabindex is
27
+ * focusable but inert until they handle Enter and Space themselves.
28
+ */
29
+ export default function DFormLabel({ htmlFor, className, children }: Props): import("react").JSX.Element;
30
+ export {};
@@ -0,0 +1,13 @@
1
+ import type { FC } from 'react';
2
+ type Props = {
3
+ stack: Array<{
4
+ name: string;
5
+ Component: FC<{
6
+ name: string;
7
+ payload: any;
8
+ }>;
9
+ payload: unknown;
10
+ }>;
11
+ };
12
+ export default function DPortalStack({ stack }: Props): import("react").JSX.Element;
13
+ export {};
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Backs a value that the consumer may or may not drive, so a control works in
3
+ * both modes without a copy of the state drifting away from the props.
4
+ *
5
+ * A control counts as controlled only when the consumer passes the value *and*
6
+ * an `onChange` to push it back. The value on its own has always meant "start
7
+ * here" in this library — `<DInputCheck type="radio" checked />` inside a radio
8
+ * group, for one — so treating it as controlled would silently freeze those
9
+ * controls, and React emits no warning to say why: the components always attach
10
+ * their own `onChange` handler.
11
+ *
12
+ * @param value the value coming from props, or `undefined` when not passed.
13
+ * @param isControlled whether the consumer can drive the value back.
14
+ * @param initialValue what to start from when no value is passed.
15
+ * @returns the value to render, and a setter to call on every user change.
16
+ */
17
+ export default function useControlledState<T>(value: T | undefined, isControlled: boolean, initialValue: T): [T, (next: T) => void];
@@ -1,9 +1,34 @@
1
1
  import type { RefObject, ForwardedRef, FocusEvent } from 'react';
2
2
  import type { Options } from 'currency.js';
3
- export default function useInputCurrency(currencyOptions: Options, value?: number, onFocus?: (event: FocusEvent<HTMLInputElement>) => void, onChange?: (value?: number) => void, onBlur?: (event: FocusEvent<HTMLInputElement>) => void, ref?: ForwardedRef<HTMLInputElement>, minValue?: number, maxValue?: number): {
3
+ /**
4
+ * State and handlers for a currency input: a formatted string while the field
5
+ * is idle and the raw number while it is being edited.
6
+ *
7
+ * `minValue`/`maxValue` bound the value. With `clamp` (default `true`) an
8
+ * out-of-range value is brought into range whenever it is not being typed:
9
+ * on mount, when `value` or the bounds change, and on blur. Every time the
10
+ * clamp changes the value, `onChange` receives the clamped number, so the
11
+ * input and the consumer's state never disagree. While the field is focused
12
+ * the typed number is reported as is, so a partial entry is not rewritten.
13
+ *
14
+ * With `clamp: false` the value is never changed: the bounds only feed
15
+ * `isOverMax` / `isUnderMin`, so the consumer can show its own message (e.g.
16
+ * "You exceeded the limit") and keep the entered amount.
17
+ *
18
+ * Controlled usage follows the same contract as a native controlled input:
19
+ * reflect `onChange` into `value` in the same event (`setState` in the
20
+ * handler). The hook keeps showing `value` whenever it isn't being edited, so
21
+ * a consumer can reject a change by keeping `value` as it was. Deferring the
22
+ * update (a timer, awaiting a request) makes the hook see a stale `value` in
23
+ * between: the field can flash the previous amount and a clamp can be
24
+ * reported again when the deferred value arrives.
25
+ */
26
+ export default function useInputCurrency(currencyOptions: Options, value?: number, onFocus?: (event: FocusEvent<HTMLInputElement>) => void, onChange?: (value?: number) => void, onBlur?: (event: FocusEvent<HTMLInputElement>) => void, ref?: ForwardedRef<HTMLInputElement>, minValue?: number, maxValue?: number, clamp?: boolean): {
4
27
  inputRef: RefObject<HTMLInputElement | null>;
5
28
  innerValue: string;
6
29
  innerType: string;
30
+ isOverMax: boolean;
31
+ isUnderMin: boolean;
7
32
  handleOnFocus: (event: FocusEvent<HTMLInputElement>) => void;
8
33
  handleOnChange: (newValue?: string) => void;
9
34
  handleOnBlur: (event: FocusEvent<HTMLInputElement>) => void;
@@ -1,3 +1,13 @@
1
+ import type { BreakpointProps } from '../contexts/DContext';
2
+ /** Clears the cached CSS breakpoints. Only meant for tests. */
3
+ export declare function resetCssBreakpointsCache(): void;
4
+ /**
5
+ * Pixel value of a breakpoint. `DContextProvider` reads them from the CSS and
6
+ * shares them through the context; outside of it (a tree without the
7
+ * provider, or its first render) they are read from the same CSS variables
8
+ * here, so responsive props resolve either way.
9
+ */
10
+ export declare function useBreakpointValue(breakpoint: keyof BreakpointProps): string;
1
11
  export declare function useMediaBreakpointUpXs(useListener?: boolean): boolean;
2
12
  export declare function useMediaBreakpointUpSm(useListener?: boolean): boolean;
3
13
  export declare function useMediaBreakpointUpMd(useListener?: boolean): boolean;
@@ -0,0 +1,30 @@
1
+ import type { ReactNode } from 'react';
2
+ /**
3
+ * Whether a `label` node has something to render.
4
+ *
5
+ * The render paths cannot test `label` for truthiness: since the prop widened
6
+ * to `ReactNode` it also accepts `0`, which React renders as the text "0" and
7
+ * which `isTextLabel` counts as a text label, yet which a truthiness check
8
+ * would drop — leaving the control visibly unlabelled.
9
+ *
10
+ * Absent (`undefined`, `null`), empty (`''`) and boolean labels render nothing,
11
+ * so they are the ones that stand for "no label". The empty string matters
12
+ * because it is the default several components fall back to.
13
+ *
14
+ * Arrays are inspected rather than taken at face value: `label={items.map(...)}`
15
+ * over an empty list yields `[]`, while a list of skipped entries yields
16
+ * `[false, null]`. Both render nothing, so treating them as content would leave
17
+ * an empty `<label>` on the page — and make the naming warning fire over a
18
+ * label that was never there.
19
+ *
20
+ * Only arrays, though, even though `ReactNode` admits any iterable. Reading a
21
+ * one-shot iterator — a generator — would exhaust it here, and the component
22
+ * goes on to render the same value, so the label would vanish precisely because
23
+ * we checked for it. An array can be walked as often as needed; anything else
24
+ * is taken on faith.
25
+ *
26
+ * Anything else — an element, a portal, a generator — counts as content: what
27
+ * it renders is only knowable by rendering it, and an element that draws
28
+ * nothing visible is the consumer's decision rather than an absent label.
29
+ */
30
+ export default function hasLabelContent(label: ReactNode): boolean;
@@ -0,0 +1,17 @@
1
+ import type { ReactNode } from 'react';
2
+ /**
3
+ * Narrows a `label` node to the plain-text case.
4
+ *
5
+ * `label` accepts any `ReactNode` so patterns like a terms-and-conditions link
6
+ * or an info trigger can live next to the field name. Anything that is not text
7
+ * cannot be forwarded to a string-only attribute (`aria-label`, `placeholder`)
8
+ * nor laid out by Bootstrap's `form-floating`, so those paths check this first.
9
+ *
10
+ * Numbers and bigints count as text: React renders them as their string form
11
+ * and so does `String()`, which is what the string-only attributes end up
12
+ * receiving. `bigint` is part of `ReactNode` under these React types, so
13
+ * leaving it out made a label React draws as text get treated as a rich node.
14
+ * React 18 cannot render one, which `DFormLabel` handles by rendering its
15
+ * string form — the classification stays true for every supported React.
16
+ */
17
+ export default function isTextLabel(label: ReactNode): label is string | number | bigint;
@@ -0,0 +1,20 @@
1
+ import type { MouseEvent } from 'react';
2
+ /**
3
+ * Stops a click on a non-native trigger inside a `<label>` from also activating
4
+ * the labelled control.
5
+ *
6
+ * Verified against Chromium, which forwards the click for every one of the
7
+ * `PSEUDO_INTERACTIVE` selectors above while exempting the native ones. Note
8
+ * that jsdom does not reproduce this: its `isInteractiveContent` counts any
9
+ * element carrying a `tabindex` as interactive content, so a jsdom test passes
10
+ * whether or not this guard exists. That is why the guard is unit-tested
11
+ * directly rather than through a rendered component.
12
+ *
13
+ * Native interactive content returns early rather than falling through, since
14
+ * `preventDefault` on those clicks would cancel the link navigation or the
15
+ * image map the label is meant to let through.
16
+ *
17
+ * `DFormLabel` wires this in the capture phase so a trigger that stops
18
+ * propagation cannot skip it.
19
+ */
20
+ export default function labelClickGuard(event: MouseEvent<HTMLLabelElement>): void;
@@ -0,0 +1,43 @@
1
+ import type { ReactNode } from 'react';
2
+ type WarnLabelUsageOptions = {
3
+ /** Component name, used to key the warning and to address the developer. */
4
+ component: string;
5
+ /** The `label` prop as received. */
6
+ label: ReactNode;
7
+ /** Whether an explicit accessible name reaches the control. */
8
+ hasAccessibleName: boolean;
9
+ /**
10
+ * The prop this component actually accepts for the accessible name, so the
11
+ * advice is usable: components that spread the native input attributes take
12
+ * `aria-label`, the rest expose the camelCase `ariaLabel` and reject the
13
+ * native one at compile time.
14
+ */
15
+ accessibleNameProp: 'aria-label' | 'ariaLabel';
16
+ /** Whether the component is rendering in Bootstrap's floating-label layout. */
17
+ floatingLabel?: boolean;
18
+ };
19
+ /**
20
+ * Warns, only outside production builds, about the two ways a non-text `label`
21
+ * goes wrong.
22
+ *
23
+ * A text label doubles as the control's accessible name. A `ReactNode` one does
24
+ * not: the name becomes whatever the subtree happens to compute to, which for a
25
+ * label carrying a link or an icon trigger reads as the wrong thing or as
26
+ * nothing at all — so an explicit name is needed. Which prop carries it differs
27
+ * per component, hence `accessibleNameProp`: naming the wrong one would send
28
+ * the developer to a prop their component rejects.
29
+ *
30
+ * `form-floating` is the second case: it animates a single line of text between
31
+ * the placeholder and the label position, so a node with its own height or its
32
+ * own interactive children breaks the layout rather than the semantics.
33
+ *
34
+ * Labels the render paths drop are skipped through the same `hasLabelContent`
35
+ * they use, so `label={condition && <span />}` with a false condition warns
36
+ * about nothing: there is no label on the page to name.
37
+ *
38
+ * The `process.env.NODE_ENV` guard belongs at the call site — that is what
39
+ * bundlers constant-fold, dropping this module from a consumer's production
40
+ * bundle.
41
+ */
42
+ export default function warnLabelUsage({ component, label, hasAccessibleName, accessibleNameProp, floatingLabel, }: WarnLabelUsageOptions): void;
43
+ export {};
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "sideEffects": [
4
4
  "*.css"
5
5
  ],
6
- "version": "2.10.0",
6
+ "version": "2.11.0",
7
7
  "description": "React Dynamic Framework",
8
8
  "license": "https://github.com/dynamic-framework/dynamic-ui/blob/master/LICENSE.md",
9
9
  "repository": {
@@ -45,11 +45,12 @@
45
45
  "test:coverage": "jest --testPathPatterns=\\.spec\\.tsx?$ --coverage",
46
46
  "test:a11y": "jest --testPathPatterns=\\.a11y\\.test\\.tsx$",
47
47
  "test:storybook": "test-storybook",
48
- "build": "npm run clean && npm run build:react && npm run build:scss && npm run build:icons-core && npm run cp",
48
+ "build": "npm run clean && npm run build:react && npm run build:scss && npm run build:icons-core && npm run build:api && npm run cp",
49
49
  "build:react": "tsc -p ./tsconfig.build.json && rollup -c rollup.config.mjs",
50
50
  "build:react:watch": "tsc -p ./tsconfig.build.json && rollup -c rollup.config.mjs",
51
51
  "build:scss": "node scripts/build-scss.js",
52
52
  "build:icons-core": "npx tsx scripts/build-icons-core.ts",
53
+ "build:api": "node scripts/generate-api.mjs && mkdir -p dist && cp registry/api.json dist/api.json",
53
54
  "build:scss:watch": "npx nodemon --watch src --ext scss --exec 'npm run build:scss'",
54
55
  "dev:ts": "tsc -p tsconfig.build.json",
55
56
  "dev:rollup": "rollup -c rollup.config.mjs",
@@ -67,6 +68,9 @@
67
68
  "validate:registry": "node scripts/validate-registry.mjs",
68
69
  "generate:token-overrides": "node scripts/generate-token-overrides.mjs && node scripts/generate-token-overrides-schema.mjs",
69
70
  "validate:token-overrides": "node scripts/validate-token-overrides.mjs",
71
+ "theme:expand": "node scripts/theme/theme-expand.mjs",
72
+ "theme:validate": "node scripts/theme/theme-validate.mjs",
73
+ "theme:preview": "vite scripts/theme --port 4173 --open /preview/",
70
74
  "eslint": "npm run eslint:src && npm run eslint:stories && npm run eslint:test",
71
75
  "eslint:src": "eslint 'src/**/*.ts' 'src/**/*.tsx' --ignore-pattern '**/*.spec.tsx'",
72
76
  "eslint:stories": "eslint stories/**/*.stories.tsx",
@@ -75,7 +79,7 @@
75
79
  "postversion": "npm run build:scss",
76
80
  "publish:cdn": "npm run publish:cdn-version && npm run publish:cdn-latest",
77
81
  "publish:cdn-version": "node scripts/publish-cdn.js",
78
- "publish:cdn-latest": "aws s3 sync ./dist/ s3://dynamicframework-cdn/assets/latest/ui-react --delete --exclude 'api.json' --acl public-read",
82
+ "publish:cdn-latest": "aws s3 sync ./dist/ s3://dynamicframework-cdn/assets/latest/ui-react --delete --exclude 'api.json' --exclude 'manifests/*' --acl public-read",
79
83
  "publish:cdn-rc": "aws s3 sync ./dist/ s3://dynamicframework-cdn/assets/rc/ui-react --delete --acl public-read",
80
84
  "publish:cdn-dev": "aws s3 sync ./dist/ s3://dynamicframework-cdn/assets/dev/ui-react --delete --acl public-read",
81
85
  "lint-staged": "lint-staged",
@@ -145,6 +149,7 @@
145
149
  "autoprefixer": "~10.4.16",
146
150
  "axe-playwright": "~1.2.3",
147
151
  "conventional-changelog-conventionalcommits": "~6.1.0",
152
+ "culori": "^4.0.2",
148
153
  "eslint": "~8.52.0",
149
154
  "eslint-config-airbnb": "~19.0.4",
150
155
  "eslint-config-airbnb-typescript": "~17.1.0",
@@ -17,10 +17,10 @@ $body-color: map-get($all-colors, $body-color-mapping) !default;
17
17
  $body-bg: #f5f6fa !default;
18
18
 
19
19
  $body-secondary-color: var(--#{$prefix}body-color-rgb) !default;
20
- $body-secondary-bg: var(--#{$prefix}gray-200) !default;
20
+ $body-secondary-bg: var(--#{$prefix}gray-200-rgb) !default;
21
21
 
22
22
  $body-tertiary-color: var(--#{$prefix}body-color-rgb) !default;
23
- $body-tertiary-bg: var(--#{$prefix}gray-100) !default;
23
+ $body-tertiary-bg: var(--#{$prefix}gray-100-rgb) !default;
24
24
 
25
25
  $body-emphasis-color: var(--#{$prefix}black-rgb) !default;
26
26
  // end custom
@@ -10,4 +10,6 @@ $chip-padding-y: $spacer-1 !default;
10
10
  $chip-font-size: $small-font-size !default;
11
11
  $chip-font-weight: $font-weight-bold !default;
12
12
  $chip-line-height: $line-height-base !default;
13
+ $chip-icon-container-size: 1lh !default;
14
+ $chip-icon-container-size-fallback: $line-height-base * 1em !default;
13
15
  // scss-docs-end chip-variables
@@ -404,7 +404,7 @@ $info-text-emphasis: var(--#{$prefix}info-600) !default;
404
404
  $warning-text-emphasis: var(--#{$prefix}warning-800) !default;
405
405
  $danger-text-emphasis: var(--#{$prefix}danger-600) !default;
406
406
  $light-text-emphasis: var(--#{$prefix}gray-600) !default;
407
- $dark-text-emphasis: var(--#{$prefix}gray-25) !default;
407
+ $dark-text-emphasis: var(--#{$prefix}gray-900) !default;
408
408
  // scss-docs-end theme-text-variables
409
409
 
410
410
  // scss-docs-start theme-bg-subtle-variables
@@ -415,7 +415,7 @@ $info-bg-subtle: var(--#{$prefix}info-25) !default;
415
415
  $warning-bg-subtle: var(--#{$prefix}warning-25) !default;
416
416
  $danger-bg-subtle: var(--#{$prefix}danger-25) !default;
417
417
  $light-bg-subtle: var(--#{$prefix}gray-100) !default;
418
- $dark-bg-subtle: var(--#{$prefix}gray-400) !default;
418
+ $dark-bg-subtle: var(--#{$prefix}gray-300) !default;
419
419
  // scss-docs-end theme-bg-subtle-variables
420
420
 
421
421
  // scss-docs-start theme-border-subtle-variables
@@ -149,8 +149,11 @@
149
149
  @include box-shadow(none);
150
150
  }
151
151
 
152
+ // The glyph follows the button's font size unless a size is set explicitly,
153
+ // through DIcon's `size` (inline) or `--bs-icon-component-size` on the icon
154
+ // or the button.
152
155
  .d-icon {
153
- --#{$prefix}icon-size: var(--#{$prefix}btn-font-size);
156
+ --#{$prefix}icon-size: var(--#{$prefix}icon-component-size, var(--#{$prefix}btn-font-size));
154
157
  }
155
158
  }
156
159
 
@@ -71,11 +71,14 @@
71
71
  --#{$prefix}input-group-sm-icon-size: #{$input-group-sm-icon-size};
72
72
  --#{$prefix}input-group-lg-icon-size: #{$input-group-lg-icon-size};
73
73
 
74
- &.input-group-sm .d-icon {
74
+ // Icons inside a button (a DButton or DButtonIcon in `inputStart`/`inputEnd`)
75
+ // keep following the button's font size, as before `.btn .d-icon` honored
76
+ // an explicit icon size.
77
+ &.input-group-sm .d-icon:not(.btn .d-icon) {
75
78
  --#{$prefix}icon-component-size: var(--#{$prefix}input-group-sm-icon-size);
76
79
  }
77
80
 
78
- &.input-group-lg .d-icon {
81
+ &.input-group-lg .d-icon:not(.btn .d-icon) {
79
82
  --#{$prefix}icon-component-size: var(--#{$prefix}input-group-lg-icon-size);
80
83
  }
81
84
 
@@ -4,3 +4,58 @@
4
4
  gap: var(--#{$prefix}list-group-item-gap);
5
5
  align-items: center;
6
6
  }
7
+
8
+ // Interactive item inside a list: the <li> is the visual item (borders,
9
+ // colors, active/disabled) and the link or button fills it, so the group keeps
10
+ // `ul/ol > li` semantics and Bootstrap's sibling selectors still apply.
11
+ .d-list-group-item-interactive {
12
+ padding: 0;
13
+
14
+ &:not(.active):focus-within {
15
+ z-index: 1;
16
+ color: var(--#{$prefix}list-group-action-hover-color);
17
+ background-color: var(--#{$prefix}list-group-action-hover-bg);
18
+ }
19
+ }
20
+
21
+ .d-list-group-item-link {
22
+ display: flex;
23
+ flex: 1 1 auto;
24
+ gap: var(--#{$prefix}list-group-item-gap);
25
+ align-items: center;
26
+ align-self: stretch;
27
+ width: 100%;
28
+ padding: var(--#{$prefix}list-group-item-padding-y) var(--#{$prefix}list-group-item-padding-x);
29
+ font: inherit;
30
+ color: inherit;
31
+ text-align: inherit;
32
+ text-decoration: none;
33
+ background-color: transparent;
34
+ border: 0;
35
+ border-radius: inherit;
36
+
37
+ &:hover {
38
+ color: inherit;
39
+ }
40
+ }
41
+
42
+ // In a numbered list the counter goes inside the link or button, so the whole
43
+ // row, number included, is clickable. The <li> drops its own counter.
44
+ .list-group-numbered > .list-group-item.d-list-group-item-interactive::before {
45
+ content: none;
46
+ counter-increment: none;
47
+ }
48
+
49
+ .list-group-numbered > .d-list-group-item-interactive > .d-list-group-item-link::before {
50
+ content: counters(section, ".") ". ";
51
+ content: counters(section, ".") ". " / "";
52
+ counter-increment: section;
53
+ }
54
+
55
+ // The generated number is decoration: an <ol> already announces the position,
56
+ // so screen readers skip it (the second declaration uses the alt-text syntax;
57
+ // browsers without it keep the first).
58
+ .list-group-numbered > .list-group-item::before {
59
+ content: counters(section, ".") ". ";
60
+ content: counters(section, ".") ". " / "";
61
+ }
@@ -8,6 +8,7 @@
8
8
  --#{$prefix}chip-font-size: #{$chip-font-size};
9
9
  --#{$prefix}chip-font-weight: #{$chip-font-weight};
10
10
  --#{$prefix}chip-line-height: #{$chip-line-height};
11
+ --#{$prefix}chip-icon-container-size: #{$chip-icon-container-size};
11
12
 
12
13
  display: inline-flex;
13
14
  flex-direction: row;
@@ -29,14 +30,18 @@
29
30
  display: flex;
30
31
  align-items: center;
31
32
  justify-content: center;
32
- width: var(--#{$prefix}chip-line-height);
33
- height: var(--#{$prefix}chip-line-height);
33
+ width: var(--#{$prefix}chip-icon-container-size);
34
+ height: var(--#{$prefix}chip-icon-container-size);
34
35
  padding: 0;
35
36
  margin: 0;
36
37
  background: transparent;
37
38
  border: 0;
38
39
  }
39
40
 
41
+ @supports not (width: 1lh) {
42
+ --#{$prefix}chip-icon-container-size: #{$chip-icon-container-size-fallback};
43
+ }
44
+
40
45
  @each $theme, $value in map-remove($theme-colors, "light", "dark", "warning") {
41
46
  $color: color-contrast($value);
42
47
  &.d-chip-#{$theme} {
@@ -1,4 +1,6 @@
1
1
  .d-tabs {
2
+ --#{$prefix}tabs-content-margin-bottom: var(--#{$prefix}ref-spacer-4);
3
+
2
4
  display: flex;
3
5
  width: 100%;
4
6
  }
@@ -14,6 +16,7 @@
14
16
 
15
17
  .d-tabs-content {
16
18
  width: 100%;
19
+ margin-bottom: var(--#{$prefix}tabs-content-margin-bottom);
17
20
  }
18
21
 
19
22
  .nav-toggle-button-group {
@@ -5,6 +5,11 @@
5
5
  color: var(--#{$prefix}secondary-700);
6
6
  background: var(--#{$prefix}secondary-50);
7
7
  }
8
+ } @else if map-has-key($theme-colors, $color) {
9
+ .text-bg-#{$color} {
10
+ color: var(--#{$prefix}#{$color}-text-bg-color) if($enable-important-utilities, !important, null);
11
+ background-color: RGBA(var(--#{$prefix}#{$color}-rgb), var(--#{$prefix}bg-opacity, 1)) if($enable-important-utilities, !important, null);
12
+ }
8
13
  } @else {
9
14
  .text-bg-#{$color} {
10
15
  color: color-contrast-var($value) if($enable-important-utilities, !important, null);
@@ -71,6 +71,15 @@
71
71
  --#{$prefix}#{$color}-border-subtle: #{$value};
72
72
  }
73
73
 
74
+ // Text color of `.text-bg-<color>`, resolved in Sass against the default
75
+ // role value. It lives in a variable so a theme that redefines
76
+ // `--bs-<color>-rgb` can move it too. `secondary` already reads its ramp.
77
+ @each $color, $value in $theme-colors {
78
+ @if $color != "secondary" {
79
+ --#{$prefix}#{$color}-text-bg-color: #{color-contrast-var($value)};
80
+ }
81
+ }
82
+
74
83
  // Fonts
75
84
 
76
85
  // Note: Use `inspect` for lists so that quoted items keep the quotes.