@wordpress/ui 0.6.0 → 0.7.1-next.v.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 (147) hide show
  1. package/CHANGELOG.md +18 -1
  2. package/build/badge/badge.cjs +1 -1
  3. package/build/badge/badge.cjs.map +2 -2
  4. package/build/box/box.cjs +3 -7
  5. package/build/box/box.cjs.map +2 -2
  6. package/build/button/button.cjs +3 -3
  7. package/build/button/button.cjs.map +2 -2
  8. package/build/form/primitives/fieldset/root.cjs +3 -3
  9. package/build/form/primitives/fieldset/root.cjs.map +2 -2
  10. package/build/form/primitives/input-layout/input-layout.cjs +3 -3
  11. package/build/form/primitives/input-layout/input-layout.cjs.map +2 -2
  12. package/build/form/primitives/input-layout/slot.cjs +3 -3
  13. package/build/form/primitives/input-layout/slot.cjs.map +2 -2
  14. package/build/form/primitives/select/item.cjs +3 -3
  15. package/build/form/primitives/select/item.cjs.map +2 -2
  16. package/build/form/primitives/select/popup.cjs +3 -3
  17. package/build/form/primitives/select/popup.cjs.map +2 -2
  18. package/build/form/primitives/select/trigger.cjs +3 -3
  19. package/build/form/primitives/select/trigger.cjs.map +2 -2
  20. package/build/icon-button/icon-button.cjs +103 -0
  21. package/build/icon-button/icon-button.cjs.map +7 -0
  22. package/build/icon-button/index.cjs +31 -0
  23. package/build/icon-button/index.cjs.map +7 -0
  24. package/build/icon-button/types.cjs +19 -0
  25. package/build/icon-button/types.cjs.map +7 -0
  26. package/build/index.cjs +5 -0
  27. package/build/index.cjs.map +2 -2
  28. package/build/tabs/index.cjs +40 -0
  29. package/build/tabs/index.cjs.map +7 -0
  30. package/build/tabs/list.cjs +145 -0
  31. package/build/tabs/list.cjs.map +7 -0
  32. package/build/tabs/panel.cjs +67 -0
  33. package/build/tabs/panel.cjs.map +7 -0
  34. package/build/tabs/root.cjs +38 -0
  35. package/build/tabs/root.cjs.map +7 -0
  36. package/build/tabs/tab.cjs +71 -0
  37. package/build/tabs/tab.cjs.map +7 -0
  38. package/build/tabs/types.cjs +19 -0
  39. package/build/tabs/types.cjs.map +7 -0
  40. package/build/tooltip/popup.cjs +3 -3
  41. package/build/tooltip/popup.cjs.map +2 -2
  42. package/build-module/badge/badge.mjs +1 -1
  43. package/build-module/badge/badge.mjs.map +2 -2
  44. package/build-module/box/box.mjs +3 -7
  45. package/build-module/box/box.mjs.map +2 -2
  46. package/build-module/button/button.mjs +3 -3
  47. package/build-module/button/button.mjs.map +2 -2
  48. package/build-module/form/primitives/fieldset/root.mjs +3 -3
  49. package/build-module/form/primitives/fieldset/root.mjs.map +2 -2
  50. package/build-module/form/primitives/input-layout/input-layout.mjs +3 -3
  51. package/build-module/form/primitives/input-layout/input-layout.mjs.map +2 -2
  52. package/build-module/form/primitives/input-layout/slot.mjs +3 -3
  53. package/build-module/form/primitives/input-layout/slot.mjs.map +2 -2
  54. package/build-module/form/primitives/select/item.mjs +3 -3
  55. package/build-module/form/primitives/select/item.mjs.map +2 -2
  56. package/build-module/form/primitives/select/popup.mjs +3 -3
  57. package/build-module/form/primitives/select/popup.mjs.map +2 -2
  58. package/build-module/form/primitives/select/trigger.mjs +3 -3
  59. package/build-module/form/primitives/select/trigger.mjs.map +2 -2
  60. package/build-module/icon-button/icon-button.mjs +68 -0
  61. package/build-module/icon-button/icon-button.mjs.map +7 -0
  62. package/build-module/icon-button/index.mjs +6 -0
  63. package/build-module/icon-button/index.mjs.map +7 -0
  64. package/build-module/icon-button/types.mjs +1 -0
  65. package/build-module/icon-button/types.mjs.map +7 -0
  66. package/build-module/index.mjs +3 -0
  67. package/build-module/index.mjs.map +2 -2
  68. package/build-module/tabs/index.mjs +12 -0
  69. package/build-module/tabs/index.mjs.map +7 -0
  70. package/build-module/tabs/list.mjs +110 -0
  71. package/build-module/tabs/list.mjs.map +7 -0
  72. package/build-module/tabs/panel.mjs +32 -0
  73. package/build-module/tabs/panel.mjs.map +7 -0
  74. package/build-module/tabs/root.mjs +13 -0
  75. package/build-module/tabs/root.mjs.map +7 -0
  76. package/build-module/tabs/tab.mjs +36 -0
  77. package/build-module/tabs/tab.mjs.map +7 -0
  78. package/build-module/tabs/types.mjs +1 -0
  79. package/build-module/tabs/types.mjs.map +7 -0
  80. package/build-module/tooltip/popup.mjs +3 -3
  81. package/build-module/tooltip/popup.mjs.map +2 -2
  82. package/build-types/box/box.d.ts.map +1 -1
  83. package/build-types/box/stories/index.story.d.ts.map +1 -1
  84. package/build-types/button/stories/index.story.d.ts +1 -2
  85. package/build-types/button/stories/index.story.d.ts.map +1 -1
  86. package/build-types/form/primitives/field/stories/index.story.d.ts +0 -1
  87. package/build-types/form/primitives/field/stories/index.story.d.ts.map +1 -1
  88. package/build-types/form/primitives/select/stories/index.story.d.ts +0 -1
  89. package/build-types/form/primitives/select/stories/index.story.d.ts.map +1 -1
  90. package/build-types/icon-button/icon-button.d.ts +13 -0
  91. package/build-types/icon-button/icon-button.d.ts.map +1 -0
  92. package/build-types/icon-button/index.d.ts +2 -0
  93. package/build-types/icon-button/index.d.ts.map +1 -0
  94. package/build-types/icon-button/stories/index.story.d.ts +19 -0
  95. package/build-types/icon-button/stories/index.story.d.ts.map +1 -0
  96. package/build-types/icon-button/test/index.test.d.ts +2 -0
  97. package/build-types/icon-button/test/index.test.d.ts.map +1 -0
  98. package/build-types/icon-button/types.d.ts +36 -0
  99. package/build-types/icon-button/types.d.ts.map +1 -0
  100. package/build-types/index.d.ts +2 -0
  101. package/build-types/index.d.ts.map +1 -1
  102. package/build-types/tabs/index.d.ts +6 -0
  103. package/build-types/tabs/index.d.ts.map +1 -0
  104. package/build-types/tabs/list.d.ts +16 -0
  105. package/build-types/tabs/list.d.ts.map +1 -0
  106. package/build-types/tabs/panel.d.ts +15 -0
  107. package/build-types/tabs/panel.d.ts.map +1 -0
  108. package/build-types/tabs/root.d.ts +15 -0
  109. package/build-types/tabs/root.d.ts.map +1 -0
  110. package/build-types/tabs/stories/index.story.d.ts +13 -0
  111. package/build-types/tabs/stories/index.story.d.ts.map +1 -0
  112. package/build-types/tabs/tab.d.ts +15 -0
  113. package/build-types/tabs/tab.d.ts.map +1 -0
  114. package/build-types/tabs/test/index.test.d.ts +2 -0
  115. package/build-types/tabs/test/index.test.d.ts.map +1 -0
  116. package/build-types/tabs/types.d.ts +33 -0
  117. package/build-types/tabs/types.d.ts.map +1 -0
  118. package/package.json +11 -9
  119. package/src/badge/badge.tsx +1 -1
  120. package/src/box/box.tsx +4 -15
  121. package/src/box/stories/index.story.tsx +9 -1
  122. package/src/button/stories/index.story.tsx +3 -16
  123. package/src/button/style.module.css +6 -3
  124. package/src/form/primitives/field/stories/index.story.tsx +0 -1
  125. package/src/form/primitives/fieldset/style.module.css +1 -1
  126. package/src/form/primitives/input-layout/style.module.css +5 -8
  127. package/src/form/primitives/select/stories/index.story.tsx +0 -1
  128. package/src/icon-button/icon-button.tsx +64 -0
  129. package/src/icon-button/index.ts +1 -0
  130. package/src/icon-button/stories/index.story.tsx +128 -0
  131. package/src/icon-button/style.module.css +9 -0
  132. package/src/icon-button/test/index.test.tsx +86 -0
  133. package/src/icon-button/types.ts +38 -0
  134. package/src/index.ts +2 -0
  135. package/src/tabs/index.ts +6 -0
  136. package/src/tabs/list.tsx +130 -0
  137. package/src/tabs/panel.tsx +23 -0
  138. package/src/tabs/root.tsx +15 -0
  139. package/src/tabs/stories/best-practices.mdx +85 -0
  140. package/src/tabs/stories/index.story.tsx +363 -0
  141. package/src/tabs/style.module.css +269 -0
  142. package/src/tabs/tab.tsx +29 -0
  143. package/src/tabs/test/index.test.tsx +2260 -0
  144. package/src/tabs/types.ts +36 -0
  145. package/src/tooltip/style.module.css +2 -2
  146. package/src/utils/css/item-popup.module.css +1 -1
  147. package/src/utils/css/select-trigger.module.css +1 -1
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Groups the tabs and the corresponding panels.
3
+ *
4
+ * `Tabs` is a collection of React components that combine to render
5
+ * an [ARIA-compliant tabs pattern](https://www.w3.org/WAI/ARIA/apg/patterns/tabs/).
6
+ */
7
+ export declare const Root: import("react").ForwardRefExoticComponent<Omit<Omit<import("@base-ui/react").TabsRootProps & import("react").RefAttributes<HTMLDivElement>, "ref">, "className" | "children" | "render"> & {
8
+ className?: string;
9
+ render?: ((props: import("react").HTMLAttributes<any> & {
10
+ ref?: import("react").Ref<any> | undefined;
11
+ }) => React.ReactElement<unknown>) | React.ReactElement<Record<string, unknown>>;
12
+ } & {
13
+ children?: import("react").ReactNode;
14
+ } & import("react").RefAttributes<HTMLDivElement>>;
15
+ //# sourceMappingURL=root.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"root.d.ts","sourceRoot":"","sources":["../../src/tabs/root.tsx"],"names":[],"mappings":"AAIA;;;;;GAKG;AACH,eAAO,MAAM,IAAI;;;;;;;kDAIhB,CAAC"}
@@ -0,0 +1,13 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { Tabs } from '../..';
3
+ declare const meta: Meta<typeof Tabs.Root>;
4
+ export default meta;
5
+ export declare const Default: StoryObj<typeof Tabs.Root>;
6
+ export declare const Minimal: StoryObj<typeof Tabs.Root>;
7
+ export declare const SizeAndOverflowPlayground: StoryObj<typeof Tabs.Root>;
8
+ export declare const Vertical: StoryObj<typeof Tabs.Root>;
9
+ export declare const WithDisabledTab: StoryObj<typeof Tabs.Root>;
10
+ export declare const WithTabIconsAndTooltips: StoryObj<typeof Tabs.Root>;
11
+ export declare const WithPanelsAlwaysMounted: StoryObj<typeof Tabs.Root>;
12
+ export declare const WithNonFocusablePanels: StoryObj<typeof Tabs.Root>;
13
+ //# sourceMappingURL=index.story.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.story.d.ts","sourceRoot":"","sources":["../../../src/tabs/stories/index.story.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAG5D,OAAO,EAAE,IAAI,EAAW,MAAM,OAAO,CAAC;AAEtC,QAAA,MAAM,IAAI,EAAE,IAAI,CAAE,OAAO,IAAI,CAAC,IAAI,CAQjC,CAAC;AACF,eAAe,IAAI,CAAC;AAUpB,eAAO,MAAM,OAAO,EAAE,QAAQ,CAAE,OAAO,IAAI,CAAC,IAAI,CAsB/C,CAAC;AAEF,eAAO,MAAM,OAAO,EAAE,QAAQ,CAAE,OAAO,IAAI,CAAC,IAAI,CAsB/C,CAAC;AAEF,eAAO,MAAM,yBAAyB,EAAE,QAAQ,CAAE,OAAO,IAAI,CAAC,IAAI,CAqHjE,CAAC;AAEF,eAAO,MAAM,QAAQ,EAAE,QAAQ,CAAE,OAAO,IAAI,CAAC,IAAI,CAWhD,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,QAAQ,CAAE,OAAO,IAAI,CAAC,IAAI,CAyBvD,CAAC;AAgCF,eAAO,MAAM,uBAAuB,EAAE,QAAQ,CAAE,OAAO,IAAI,CAAC,IAAI,CAsC/D,CAAC;AAEF,eAAO,MAAM,uBAAuB,EAAE,QAAQ,CAAE,OAAO,IAAI,CAAC,IAAI,CAsB/D,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,QAAQ,CAAE,OAAO,IAAI,CAAC,IAAI,CAqC9D,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * An individual interactive tab button that toggles the corresponding panel.
3
+ *
4
+ * `Tabs` is a collection of React components that combine to render
5
+ * an [ARIA-compliant tabs pattern](https://www.w3.org/WAI/ARIA/apg/patterns/tabs/).
6
+ */
7
+ export declare const Tab: import("react").ForwardRefExoticComponent<Omit<Omit<import("@base-ui/react").TabsTabProps & import("react").RefAttributes<Element>, "ref">, "className" | "children" | "render"> & {
8
+ className?: string;
9
+ render?: ((props: import("react").HTMLAttributes<any> & {
10
+ ref?: import("react").Ref<any> | undefined;
11
+ }) => React.ReactElement<unknown>) | React.ReactElement<Record<string, unknown>>;
12
+ } & {
13
+ children?: import("react").ReactNode;
14
+ } & import("react").RefAttributes<HTMLButtonElement>>;
15
+ //# sourceMappingURL=tab.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tab.d.ts","sourceRoot":"","sources":["../../src/tabs/tab.tsx"],"names":[],"mappings":"AAQA;;;;;GAKG;AACH,eAAO,MAAM,GAAG;;;;;;;qDAcb,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../../../src/tabs/test/index.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,33 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { Tabs as _Tabs } from '@base-ui/react/tabs';
3
+ import type { ComponentProps } from '../utils/types';
4
+ export type TabRootProps = ComponentProps<typeof _Tabs.Root> & {
5
+ /**
6
+ * The content to be rendered inside the component.
7
+ */
8
+ children?: ReactNode;
9
+ };
10
+ export type TabListProps = ComponentProps<typeof _Tabs.List> & {
11
+ /**
12
+ * The content to be rendered inside the component.
13
+ */
14
+ children?: ReactNode;
15
+ /**
16
+ * The visual variant of the tab list.
17
+ * @default "default"
18
+ */
19
+ variant?: 'minimal' | 'default';
20
+ };
21
+ export type TabProps = ComponentProps<typeof _Tabs.Tab> & {
22
+ /**
23
+ * The content to be rendered inside the component.
24
+ */
25
+ children?: ReactNode;
26
+ };
27
+ export type TabPanelProps = ComponentProps<typeof _Tabs.Panel> & {
28
+ /**
29
+ * The content to be rendered inside the component.
30
+ */
31
+ children?: ReactNode;
32
+ };
33
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/tabs/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,IAAI,IAAI,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAErD,MAAM,MAAM,YAAY,GAAG,cAAc,CAAE,OAAO,KAAK,CAAC,IAAI,CAAE,GAAG;IAChE;;OAEG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,cAAc,CAAE,OAAO,KAAK,CAAC,IAAI,CAAE,GAAG;IAChE;;OAEG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB;;;OAGG;IACH,OAAO,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG,cAAc,CAAE,OAAO,KAAK,CAAC,GAAG,CAAE,GAAG;IAC3D;;OAEG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,cAAc,CAAE,OAAO,KAAK,CAAC,KAAK,CAAE,GAAG;IAClE;;OAEG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;CACrB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wordpress/ui",
3
- "version": "0.6.0",
3
+ "version": "0.7.1-next.v.0+642962a6d",
4
4
  "description": "Themeable React UI components for the WordPress Design System.",
5
5
  "author": "The WordPress Contributors",
6
6
  "license": "GPL-2.0-or-later",
@@ -44,13 +44,15 @@
44
44
  "sideEffects": false,
45
45
  "dependencies": {
46
46
  "@base-ui/react": "^1.0.0",
47
- "@wordpress/a11y": "^4.39.0",
48
- "@wordpress/element": "^6.39.0",
49
- "@wordpress/i18n": "^6.12.0",
50
- "@wordpress/icons": "^11.6.0",
51
- "@wordpress/primitives": "^4.39.0",
52
- "@wordpress/private-apis": "^1.39.0",
53
- "@wordpress/theme": "^0.6.0",
47
+ "@wordpress/a11y": "^4.39.1-next.v.0+642962a6d",
48
+ "@wordpress/compose": "^7.39.1-next.v.0+642962a6d",
49
+ "@wordpress/element": "^6.39.1-next.v.0+642962a6d",
50
+ "@wordpress/i18n": "^6.12.1-next.v.0+642962a6d",
51
+ "@wordpress/icons": "^11.6.1-next.v.0+642962a6d",
52
+ "@wordpress/keycodes": "^4.39.1-next.v.0+642962a6d",
53
+ "@wordpress/primitives": "^4.39.1-next.v.0+642962a6d",
54
+ "@wordpress/private-apis": "^1.39.1-next.v.0+642962a6d",
55
+ "@wordpress/theme": "^0.7.1-next.v.0+642962a6d",
54
56
  "clsx": "^2.1.1"
55
57
  },
56
58
  "devDependencies": {
@@ -68,5 +70,5 @@
68
70
  "publishConfig": {
69
71
  "access": "public"
70
72
  },
71
- "gitHead": "eee1cfb1472f11183e40fb77465a5f13145df7ad"
73
+ "gitHead": "2de56f23b200b6dc4b1feda4408613ad088d160a"
72
74
  }
@@ -70,7 +70,7 @@ export const Badge = forwardRef< HTMLDivElement, BadgeProps >( function Badge(
70
70
  return (
71
71
  <Box
72
72
  { ...intentStyles }
73
- padding={ { inline: 'xs', block: '2xs' } }
73
+ padding={ { inline: 'sm', block: 'xs' } }
74
74
  borderRadius="lg"
75
75
  render={ render }
76
76
  style={ {
package/src/box/box.tsx CHANGED
@@ -20,16 +20,11 @@ const capitalize = ( str: string ): string =>
20
20
  * fallback).
21
21
  *
22
22
  * @param property The CSS property name.
23
- * @param target The design system token target.
24
23
  * @param value The size token name.
25
24
  * @return A CSS value string with variable references.
26
25
  */
27
- const getSpacingValue = (
28
- property: string,
29
- target: string,
30
- value: string
31
- ): string =>
32
- `var(--wpds-dimension-${ property }-${ target }-${ value }, var(--wpds-dimension-${ property }-surface-${ value }))`;
26
+ const getSpacingValue = ( property: string, value: string ): string =>
27
+ `var(--wpds-dimension-${ property }-${ value }, var(--wpds-dimension-${ property }-${ value }))`;
33
28
 
34
29
  /**
35
30
  * Generates CSS styles for properties with optionally directional values,
@@ -37,23 +32,20 @@ const getSpacingValue = (
37
32
  * properties.
38
33
  *
39
34
  * @param property The CSS property name from BoxProps.
40
- * @param target The design system token target.
41
35
  * @param value The property value (single or object with directional keys).
42
36
  * @return A CSSProperties object with the computed styles.
43
37
  */
44
38
  const getDimensionVariantStyles = < T extends keyof BoxProps >(
45
39
  property: T,
46
- target: string,
47
40
  value: NonNullable< BoxProps[ T ] >
48
41
  ): React.CSSProperties =>
49
42
  typeof value !== 'object'
50
- ? { [ property ]: getSpacingValue( property, target, value ) }
43
+ ? { [ property ]: getSpacingValue( property, value ) }
51
44
  : Object.keys( value ).reduce(
52
45
  ( result, key ) => ( {
53
46
  ...result,
54
47
  [ property + capitalize( key ) ]: getSpacingValue(
55
48
  property,
56
- target,
57
49
  value[ key ]
58
50
  ),
59
51
  } ),
@@ -89,10 +81,7 @@ export const Box = forwardRef< HTMLDivElement, BoxProps >( function Box(
89
81
  }
90
82
 
91
83
  if ( padding ) {
92
- Object.assign(
93
- style,
94
- getDimensionVariantStyles( 'padding', target, padding )
95
- );
84
+ Object.assign( style, getDimensionVariantStyles( 'padding', padding ) );
96
85
  }
97
86
 
98
87
  if ( borderRadius ) {
@@ -23,7 +23,15 @@ export const Default: Story = {
23
23
  argTypes: {
24
24
  padding: {
25
25
  control: 'select',
26
- options: [ '2xs', 'xs', 'sm', 'md', 'lg' ] satisfies PaddingSize[],
26
+ options: [
27
+ 'xs',
28
+ 'sm',
29
+ 'md',
30
+ 'lg',
31
+ 'xl',
32
+ '2xl',
33
+ '3xl',
34
+ ] satisfies PaddingSize[],
27
35
  },
28
36
  },
29
37
  };
@@ -1,4 +1,4 @@
1
- import { Fragment, useState } from '@wordpress/element';
1
+ import { Fragment } from '@wordpress/element';
2
2
  import type { Meta, StoryObj } from '@storybook/react-vite';
3
3
  import { cog } from '@wordpress/icons';
4
4
  import { Button } from '../index';
@@ -166,8 +166,7 @@ export const Loading: Story = {
166
166
 
167
167
  /**
168
168
  * The pressed state is only available for buttons with `tone="neutral"` and
169
- * `variant="minimal"`. This represents a toggle button that is currently in an
170
- * active/pressed state.
169
+ * `variant="minimal"` and can be toggled via the `aria-pressed` HTML attribute.
171
170
  */
172
171
  export const Pressed: Story = {
173
172
  ...Default,
@@ -175,18 +174,6 @@ export const Pressed: Story = {
175
174
  ...Default.args,
176
175
  tone: 'neutral',
177
176
  variant: 'minimal',
178
- },
179
- render: ( args ) => {
180
- const [ isPressed, setIsPressed ] = useState( true );
181
-
182
- return (
183
- <Button
184
- { ...args }
185
- aria-pressed={ isPressed }
186
- onClick={ () => setIsPressed( ! isPressed ) }
187
- >
188
- Button
189
- </Button>
190
- );
177
+ 'aria-pressed': true,
191
178
  },
192
179
  };
@@ -17,10 +17,11 @@
17
17
  --wp-ui-button-foreground-color: var(--wpds-color-fg-interactive-brand-strong);
18
18
  --wp-ui-button-foreground-color-active: var(--wpds-color-fg-interactive-brand-strong-active);
19
19
  --wp-ui-button-foreground-color-disabled: var(--wpds-color-fg-interactive-neutral-strong-disabled);
20
- --wp-ui-button-padding-inline: calc(3 * var(--wpds-dimension-base)); /* TODO: Create new interactive padding tokens */
20
+ --wp-ui-button-padding-inline: var(--wpds-dimension-padding-md);
21
21
  --wp-ui-button-height: 40px;
22
22
  --wp-ui-button-aspect-ratio: auto; /* Useful for overrides such as icon buttons */
23
23
  --wp-ui-button-font-size: var(--wpds-font-size-md);
24
+ --wp-ui-button-min-width: calc(4ch + 2 * var(--wp-ui-button-padding-inline));
24
25
 
25
26
  /* by default, borders have the same color as the background */
26
27
  --wp-ui-button-border-color: var(--wp-ui-button-background-color);
@@ -32,8 +33,9 @@
32
33
  display: inline-flex;
33
34
  justify-content: center;
34
35
  align-items: center;
35
- gap: calc(2 * var(--wpds-dimension-base)); /* TODO: Consider new interactive/control gap tokens */
36
+ gap: var(--wpds-dimension-gap-sm);
36
37
  aspect-ratio: var(--wp-ui-button-aspect-ratio);
38
+ min-width: var(--wp-ui-button-min-width);
37
39
  height: var(--wp-ui-button-height);
38
40
  padding-inline: var(--wp-ui-button-padding-inline);
39
41
  border-style: solid;
@@ -119,7 +121,7 @@
119
121
  }
120
122
 
121
123
  .is-small {
122
- --wp-ui-button-padding-inline: calc(2 * var(--wpds-dimension-base)); /* TODO: Create new interactive padding tokens */
124
+ --wp-ui-button-padding-inline: var(--wpds-dimension-padding-sm);
123
125
  --wp-ui-button-height: 24px;
124
126
  }
125
127
 
@@ -188,6 +190,7 @@
188
190
  }
189
191
 
190
192
  .is-unstyled {
193
+ min-width: unset;
191
194
  border: none;
192
195
  background: none;
193
196
  }
@@ -1,6 +1,5 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react-vite';
2
2
  import { useId } from '@wordpress/element';
3
- import '@wordpress/theme/design-tokens.css';
4
3
  import { Field } from '../../../..';
5
4
 
6
5
  const meta: Meta< typeof Field.Root > = {
@@ -4,7 +4,7 @@
4
4
  .root {
5
5
  display: flex;
6
6
  flex-direction: column;
7
- gap: var(--wpds-dimension-gap-2xs);
7
+ gap: var(--wpds-dimension-gap-xs);
8
8
  border: 0;
9
9
  margin: 0;
10
10
  padding: 0;
@@ -2,8 +2,7 @@
2
2
 
3
3
  @layer wp-ui-components {
4
4
  .input-layout {
5
- /* TODO: Use padding tokens */
6
- --wp-ui-input-layout-padding-inline: calc(var(--wpds-dimension-base) * 3);
5
+ --wp-ui-input-layout-padding-inline: var(--wpds-dimension-padding-md);
7
6
 
8
7
  display: flex;
9
8
  height: 40px;
@@ -22,14 +21,12 @@
22
21
  }
23
22
 
24
23
  &.is-size-compact {
25
- /* TODO: Use padding tokens */
26
- --wp-ui-input-layout-padding-inline: calc(var(--wpds-dimension-base) * 2);
24
+ --wp-ui-input-layout-padding-inline: var(--wpds-dimension-padding-sm);
27
25
  height: 32px;
28
26
  }
29
27
 
30
28
  &.is-size-small {
31
- /* TODO: Use padding tokens */
32
- --wp-ui-input-layout-padding-inline: calc(var(--wpds-dimension-base) * 2);
29
+ --wp-ui-input-layout-padding-inline: var(--wpds-dimension-padding-sm);
33
30
  height: 24px;
34
31
  }
35
32
 
@@ -66,10 +63,10 @@
66
63
  &.is-padding-minimal {
67
64
  --wp-ui-input-layout-prefix-padding-start:
68
65
  calc(var(--wp-ui-input-layout-padding-inline) -
69
- var(--wpds-dimension-base));
66
+ var(--wpds-dimension-padding-xs));
70
67
  --wp-ui-input-layout-suffix-padding-end:
71
68
  calc(var(--wp-ui-input-layout-padding-inline) -
72
- var(--wpds-dimension-base));
69
+ var(--wpds-dimension-padding-xs));
73
70
  }
74
71
 
75
72
  &.is-prefix {
@@ -1,5 +1,4 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react-vite';
2
- import '@wordpress/theme/design-tokens.css';
3
2
  import { Select } from '../../../..';
4
3
 
5
4
  const meta: Meta< typeof Select.Root > = {
@@ -0,0 +1,64 @@
1
+ import clsx from 'clsx';
2
+ import { forwardRef } from '@wordpress/element';
3
+ import { Button } from '../button';
4
+ import { Icon } from '../icon';
5
+ import * as Tooltip from '../tooltip';
6
+ import styles from './style.module.css';
7
+ import { type IconButtonProps } from './types';
8
+
9
+ /**
10
+ * An icon-only button with automatic tooltip and optimized styling.
11
+ * Inherits all Button props while providing icon-specific enhancements.
12
+ */
13
+ export const IconButton = forwardRef< HTMLButtonElement, IconButtonProps >(
14
+ function IconButton(
15
+ {
16
+ label,
17
+ className,
18
+ // Prevent accidental forwarding of `children`
19
+ children: _children,
20
+ icon,
21
+ size,
22
+ shortcut,
23
+ ...restProps
24
+ }: IconButtonProps & { children?: unknown },
25
+ ref
26
+ ) {
27
+ const classes = clsx( styles[ 'icon-button' ], className );
28
+
29
+ return (
30
+ <Tooltip.Provider delay={ 0 }>
31
+ <Tooltip.Root>
32
+ <Tooltip.Trigger
33
+ ref={ ref }
34
+ render={
35
+ <Button
36
+ { ...restProps }
37
+ size={ size }
38
+ aria-label={ label }
39
+ aria-keyshortcuts={ shortcut?.ariaKeyShortcut }
40
+ />
41
+ }
42
+ className={ classes }
43
+ >
44
+ <Icon
45
+ icon={ icon }
46
+ size={ size === 'small' ? 22 : 24 }
47
+ />
48
+ </Tooltip.Trigger>
49
+ <Tooltip.Popup>
50
+ { label }
51
+ { shortcut && (
52
+ <>
53
+ { ' ' }
54
+ <span aria-hidden="true">
55
+ { shortcut.displayShortcut }
56
+ </span>
57
+ </>
58
+ ) }
59
+ </Tooltip.Popup>
60
+ </Tooltip.Root>
61
+ </Tooltip.Provider>
62
+ );
63
+ }
64
+ );
@@ -0,0 +1 @@
1
+ export { IconButton } from './icon-button';
@@ -0,0 +1,128 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import {
3
+ cog,
4
+ copy,
5
+ download,
6
+ pencil,
7
+ plus,
8
+ trash,
9
+ upload,
10
+ wordpress,
11
+ } from '@wordpress/icons';
12
+ import { displayShortcut, ariaKeyShortcut } from '@wordpress/keycodes';
13
+ import { IconButton } from '../index';
14
+
15
+ const meta: Meta< typeof IconButton > = {
16
+ title: 'Design System/Components/IconButton',
17
+ component: IconButton,
18
+ argTypes: {
19
+ 'aria-pressed': {
20
+ control: { type: 'boolean' },
21
+ },
22
+ },
23
+ };
24
+ export default meta;
25
+
26
+ type Story = StoryObj< typeof IconButton >;
27
+
28
+ export const Default: Story = {
29
+ args: {
30
+ icon: cog,
31
+ label: 'Settings',
32
+ },
33
+ };
34
+
35
+ export const Outline: Story = {
36
+ ...Default,
37
+ args: {
38
+ ...Default.args,
39
+ variant: 'outline',
40
+ },
41
+ };
42
+
43
+ export const Minimal: Story = {
44
+ ...Default,
45
+ args: {
46
+ ...Default.args,
47
+ variant: 'minimal',
48
+ },
49
+ };
50
+
51
+ export const Neutral: Story = {
52
+ ...Default,
53
+ args: {
54
+ ...Default.args,
55
+ tone: 'neutral',
56
+ label: 'Settings',
57
+ },
58
+ };
59
+
60
+ export const NeutralOutline: Story = {
61
+ ...Default,
62
+ args: {
63
+ ...Default.args,
64
+ tone: 'neutral',
65
+ variant: 'outline',
66
+ label: 'Settings',
67
+ },
68
+ };
69
+
70
+ export const Disabled: Story = {
71
+ ...Default,
72
+ args: {
73
+ ...Default.args,
74
+ disabled: true,
75
+ label: 'Settings',
76
+ },
77
+ };
78
+
79
+ export const WithDifferentIcons: Story = {
80
+ ...Default,
81
+ render: ( args ) => (
82
+ <div
83
+ style={ {
84
+ display: 'flex',
85
+ gap: '1rem',
86
+ alignItems: 'center',
87
+ flexWrap: 'wrap',
88
+ } }
89
+ >
90
+ <IconButton { ...args } icon={ wordpress } label="WordPress" />
91
+ <IconButton { ...args } icon={ plus } label="Add" />
92
+ <IconButton { ...args } icon={ pencil } label="Edit" />
93
+ <IconButton { ...args } icon={ trash } label="Delete" />
94
+ <IconButton { ...args } icon={ download } label="Download" />
95
+ <IconButton { ...args } icon={ upload } label="Upload" />
96
+ </div>
97
+ ),
98
+ };
99
+
100
+ /**
101
+ * The pressed state is only available for buttons with `tone="neutral"` and
102
+ * `variant="minimal"` and can be toggled via the `aria-pressed` HTML attribute.
103
+ */
104
+ export const Pressed: Story = {
105
+ ...Default,
106
+ args: {
107
+ ...Default.args,
108
+ tone: 'neutral',
109
+ variant: 'minimal',
110
+ label: 'Toggle Settings',
111
+ 'aria-pressed': true,
112
+ },
113
+ };
114
+
115
+ const EXAMPLE_SHORTCUT_OBJECT = {
116
+ displayShortcut: displayShortcut.primary( 'c' ),
117
+ ariaKeyShortcut: ariaKeyShortcut.primary( 'c' ),
118
+ };
119
+
120
+ export const WithShortcut: Story = {
121
+ ...Default,
122
+ args: {
123
+ ...Default.args,
124
+ icon: copy,
125
+ label: 'Copy',
126
+ shortcut: EXAMPLE_SHORTCUT_OBJECT,
127
+ },
128
+ };
@@ -0,0 +1,9 @@
1
+ @layer wp-ui-utilities, wp-ui-components, wp-ui-compositions, wp-ui-overrides;
2
+
3
+ @layer wp-ui-compositions {
4
+ .icon-button {
5
+ --wp-ui-button-aspect-ratio: 1;
6
+ --wp-ui-button-padding-inline: 0;
7
+ --wp-ui-button-min-width: unset;
8
+ }
9
+ }
@@ -0,0 +1,86 @@
1
+ import { render, waitFor, screen } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { createRef } from '@wordpress/element';
4
+ import { IconButton } from '../index';
5
+
6
+ describe( 'IconButton', () => {
7
+ it( 'forwards ref', () => {
8
+ const ref = createRef< HTMLButtonElement >();
9
+
10
+ render( <IconButton ref={ ref } label="Click me" icon={ <svg /> } /> );
11
+
12
+ expect( ref.current ).toBeInstanceOf( HTMLButtonElement );
13
+ } );
14
+
15
+ it( 'respects custom render prop as handled by Button', () => {
16
+ render(
17
+ <IconButton
18
+ label="Click me"
19
+ icon={ <svg /> }
20
+ variant="outline"
21
+ disabled
22
+ focusableWhenDisabled
23
+ render={ <button data-testid="button" /> }
24
+ />
25
+ );
26
+
27
+ // Should render as a button from `render` prop...
28
+ const button = screen.getByRole( 'button', { name: 'Click me' } );
29
+ expect( button ).toHaveAttribute( 'data-testid', 'button' );
30
+
31
+ // ...and still inherit the behavior of Button
32
+ expect( button ).toBeEnabled();
33
+ expect( button ).toHaveAttribute( 'aria-disabled', 'true' );
34
+ } );
35
+
36
+ describe( 'shortcut', () => {
37
+ it( 'sets aria-keyshortcuts attribute on the button', () => {
38
+ const { rerender } = render(
39
+ <IconButton
40
+ label="Save"
41
+ icon={ <svg /> }
42
+ shortcut={ {
43
+ displayShortcut: '⌘S',
44
+ ariaKeyShortcut: 'Meta+S',
45
+ } }
46
+ />
47
+ );
48
+
49
+ const button = screen.getByRole( 'button', { name: 'Save' } );
50
+ expect( button ).toHaveAttribute( 'aria-keyshortcuts', 'Meta+S' );
51
+
52
+ // The aria-keyshortcuts attribute is removed when there is no
53
+ // `shortcut` prop.
54
+ rerender( <IconButton label="Save" icon={ <svg /> } /> );
55
+ expect( button ).not.toHaveAttribute( 'aria-keyshortcuts' );
56
+ } );
57
+
58
+ it( 'displays the shortcut in the tooltip but hides it from assistive technology', async () => {
59
+ const user = userEvent.setup();
60
+
61
+ render(
62
+ <IconButton
63
+ label="Save"
64
+ icon={ <svg /> }
65
+ shortcut={ {
66
+ displayShortcut: '⌘S',
67
+ ariaKeyShortcut: 'Meta+S',
68
+ } }
69
+ />
70
+ );
71
+
72
+ const button = screen.getByRole( 'button', { name: 'Save' } );
73
+ await user.hover( button );
74
+
75
+ await waitFor( () => {
76
+ const shortcutElement = screen.getByText( '⌘S' );
77
+ expect( shortcutElement ).toBeVisible();
78
+ } );
79
+
80
+ expect( screen.getByText( '⌘S' ) ).toHaveAttribute(
81
+ 'aria-hidden',
82
+ 'true'
83
+ );
84
+ } );
85
+ } );
86
+ } );