@a-type/ui 2.6.1 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/dist/cjs/components/button/Button.d.ts +14 -1
  2. package/dist/cjs/components/button/Button.js +84 -8
  3. package/dist/cjs/components/button/Button.js.map +1 -1
  4. package/dist/cjs/components/button/Button.stories.d.ts +14 -1
  5. package/dist/cjs/components/button/Button.stories.js +27 -6
  6. package/dist/cjs/components/button/Button.stories.js.map +1 -1
  7. package/dist/cjs/components/button/classes.d.ts +2 -2
  8. package/dist/cjs/components/button/classes.js +12 -7
  9. package/dist/cjs/components/button/classes.js.map +1 -1
  10. package/dist/cjs/components/camera/Camera.js +3 -3
  11. package/dist/cjs/components/camera/Camera.js.map +1 -1
  12. package/dist/cjs/components/card/Card.d.ts +2 -2
  13. package/dist/cjs/components/card/Card.stories.js +7 -7
  14. package/dist/cjs/components/card/Card.stories.js.map +1 -1
  15. package/dist/cjs/components/contextMenu/contextMenu.js +2 -2
  16. package/dist/cjs/components/contextMenu/contextMenu.js.map +1 -1
  17. package/dist/cjs/components/datePicker/DatePicker.js +4 -4
  18. package/dist/cjs/components/datePicker/DatePicker.js.map +1 -1
  19. package/dist/cjs/components/dropdownMenu/DropdownMenu.js +5 -5
  20. package/dist/cjs/components/dropdownMenu/DropdownMenu.js.map +1 -1
  21. package/dist/cjs/components/emojiPicker/EmojiPicker.js +2 -2
  22. package/dist/cjs/components/emojiPicker/EmojiPicker.js.map +1 -1
  23. package/dist/cjs/components/horizontalList/HorizontalList.js +1 -1
  24. package/dist/cjs/components/horizontalList/HorizontalList.js.map +1 -1
  25. package/dist/cjs/components/horizontalList/HorizontalList.stories.js +2 -2
  26. package/dist/cjs/components/horizontalList/HorizontalList.stories.js.map +1 -1
  27. package/dist/cjs/components/icon/Icon.js +7 -1
  28. package/dist/cjs/components/icon/Icon.js.map +1 -1
  29. package/dist/cjs/components/icon/IconLoadingContext.d.ts +2 -0
  30. package/dist/cjs/components/icon/IconLoadingContext.js +12 -0
  31. package/dist/cjs/components/icon/IconLoadingContext.js.map +1 -0
  32. package/dist/cjs/components/imageUploader/ImageUploader.js +1 -1
  33. package/dist/cjs/components/imageUploader/ImageUploader.js.map +1 -1
  34. package/dist/cjs/components/particles/ParticleLayer.stories.js +1 -1
  35. package/dist/cjs/components/particles/ParticleLayer.stories.js.map +1 -1
  36. package/dist/cjs/components/select/Select.js +8 -4
  37. package/dist/cjs/components/select/Select.js.map +1 -1
  38. package/dist/cjs/components/spinner/Spinner.js +1 -1
  39. package/dist/cjs/components/spinner/Spinner.js.map +1 -1
  40. package/dist/cjs/themes.stories.d.ts +1 -0
  41. package/dist/cjs/themes.stories.js +3 -2
  42. package/dist/cjs/themes.stories.js.map +1 -1
  43. package/dist/cjs/uno/colors.d.ts +1 -0
  44. package/dist/cjs/uno/colors.js +1 -0
  45. package/dist/cjs/uno/colors.js.map +1 -1
  46. package/dist/cjs/uno/shadows.js +11 -8
  47. package/dist/cjs/uno/shadows.js.map +1 -1
  48. package/dist/css/main.css +12 -12
  49. package/dist/esm/components/button/Button.d.ts +14 -1
  50. package/dist/esm/components/button/Button.js +82 -7
  51. package/dist/esm/components/button/Button.js.map +1 -1
  52. package/dist/esm/components/button/Button.stories.d.ts +14 -1
  53. package/dist/esm/components/button/Button.stories.js +27 -6
  54. package/dist/esm/components/button/Button.stories.js.map +1 -1
  55. package/dist/esm/components/button/classes.d.ts +2 -2
  56. package/dist/esm/components/button/classes.js +13 -8
  57. package/dist/esm/components/button/classes.js.map +1 -1
  58. package/dist/esm/components/camera/Camera.js +3 -3
  59. package/dist/esm/components/camera/Camera.js.map +1 -1
  60. package/dist/esm/components/card/Card.d.ts +2 -2
  61. package/dist/esm/components/card/Card.stories.js +7 -7
  62. package/dist/esm/components/card/Card.stories.js.map +1 -1
  63. package/dist/esm/components/contextMenu/contextMenu.js +2 -2
  64. package/dist/esm/components/contextMenu/contextMenu.js.map +1 -1
  65. package/dist/esm/components/datePicker/DatePicker.js +4 -4
  66. package/dist/esm/components/datePicker/DatePicker.js.map +1 -1
  67. package/dist/esm/components/dropdownMenu/DropdownMenu.js +5 -5
  68. package/dist/esm/components/dropdownMenu/DropdownMenu.js.map +1 -1
  69. package/dist/esm/components/emojiPicker/EmojiPicker.js +2 -2
  70. package/dist/esm/components/emojiPicker/EmojiPicker.js.map +1 -1
  71. package/dist/esm/components/horizontalList/HorizontalList.js +1 -1
  72. package/dist/esm/components/horizontalList/HorizontalList.js.map +1 -1
  73. package/dist/esm/components/horizontalList/HorizontalList.stories.js +2 -2
  74. package/dist/esm/components/horizontalList/HorizontalList.stories.js.map +1 -1
  75. package/dist/esm/components/icon/Icon.js +7 -1
  76. package/dist/esm/components/icon/Icon.js.map +1 -1
  77. package/dist/esm/components/icon/IconLoadingContext.d.ts +2 -0
  78. package/dist/esm/components/icon/IconLoadingContext.js +8 -0
  79. package/dist/esm/components/icon/IconLoadingContext.js.map +1 -0
  80. package/dist/esm/components/imageUploader/ImageUploader.js +1 -1
  81. package/dist/esm/components/imageUploader/ImageUploader.js.map +1 -1
  82. package/dist/esm/components/particles/ParticleLayer.stories.js +1 -1
  83. package/dist/esm/components/particles/ParticleLayer.stories.js.map +1 -1
  84. package/dist/esm/components/select/Select.js +8 -4
  85. package/dist/esm/components/select/Select.js.map +1 -1
  86. package/dist/esm/components/spinner/Spinner.js +1 -1
  87. package/dist/esm/components/spinner/Spinner.js.map +1 -1
  88. package/dist/esm/themes.stories.d.ts +1 -0
  89. package/dist/esm/themes.stories.js +3 -2
  90. package/dist/esm/themes.stories.js.map +1 -1
  91. package/dist/esm/uno/colors.d.ts +1 -0
  92. package/dist/esm/uno/colors.js +1 -0
  93. package/dist/esm/uno/colors.js.map +1 -1
  94. package/dist/esm/uno/shadows.js +11 -8
  95. package/dist/esm/uno/shadows.js.map +1 -1
  96. package/package.json +2 -1
  97. package/src/components/button/Button.stories.tsx +47 -7
  98. package/src/components/button/Button.tsx +137 -14
  99. package/src/components/button/classes.tsx +19 -11
  100. package/src/components/camera/Camera.tsx +1 -7
  101. package/src/components/card/Card.stories.tsx +10 -10
  102. package/src/components/contextMenu/contextMenu.tsx +3 -5
  103. package/src/components/datePicker/DatePicker.tsx +0 -4
  104. package/src/components/dropdownMenu/DropdownMenu.tsx +7 -6
  105. package/src/components/emojiPicker/EmojiPicker.tsx +4 -4
  106. package/src/components/horizontalList/HorizontalList.stories.tsx +0 -2
  107. package/src/components/horizontalList/HorizontalList.tsx +0 -1
  108. package/src/components/icon/Icon.tsx +9 -0
  109. package/src/components/icon/IconLoadingContext.tsx +7 -0
  110. package/src/components/imageUploader/ImageUploader.tsx +0 -1
  111. package/src/components/particles/ParticleLayer.stories.tsx +2 -2
  112. package/src/components/select/Select.tsx +11 -9
  113. package/src/components/spinner/Spinner.tsx +1 -1
  114. package/src/themes.stories.tsx +39 -12
  115. package/src/uno/colors.ts +1 -0
  116. package/src/uno/shadows.ts +11 -8
@@ -1,14 +1,17 @@
1
1
  // @unocss-include
2
2
  export function getShadows(hard = false) {
3
+ const opacity1 = `calc(var(--un-shadow-opacity,0.1)*(1 + var(--global-shadow-spread,1) * 0.5)*2)`;
4
+ const opacity2 = `calc(var(--un-shadow-opacity,0.1)*(1 + var(--global-shadow-spread,1) * 0.5))`;
5
+ const opacity3 = `calc(var(--un-shadow-opacity,0.1)*(1 + var(--global-shadow-spread,1) * 0.5)/2)`;
3
6
  return {
4
- sm: `var(--un-shadow-inset) 0 calc(0px * var(--v-shadow-y-mult,1)) calc(1px * var(--global-shadow-spread,1)) 0 rgb(from var(--un-shadow-color) r g b / calc((var(--un-shadow-opacity,0.1)*2))),
5
- var(--un-shadow-inset) 0 calc(1px * var(--v-shadow-y-mult,1)) calc(2px * var(--global-shadow-spread,1)) 0 rgb(from var(--un-shadow-color) r g b / calc((var(--un-shadow-opacity,0.1)*2)))`,
6
- md: `var(--un-shadow-inset) 0 calc(4px * var(--v-shadow-y-mult,1)) calc(8px * var(--global-shadow-spread,1)) -1px rgb(from var(--un-shadow-color) r g b / calc((var(--un-shadow-opacity,0.1)))),
7
- var(--un-shadow-inset) 0 calc(2px * var(--v-shadow-y-mult,1)) calc(4px * var(--global-shadow-spread,1)) -1px rgb(from var(--un-shadow-color) r g b / calc((var(--un-shadow-opacity,0.1)*2)))`,
8
- lg: `var(--un-shadow-inset) 0 calc(8px * var(--v-shadow-y-mult,1)) calc(16px * var(--global-shadow-spread,1)) 0px rgb(from var(--un-shadow-color) r g b / calc((var(--un-shadow-opacity,0.1)))),
9
- var(--un-shadow-inset) 0 calc(5px * var(--v-shadow-y-mult,1)) calc(10px * var(--global-shadow-spread,1)) 0px rgb(from var(--un-shadow-color) r g b / calc((var(--un-shadow-opacity,0.1))))`,
10
- xl: `var(--un-shadow-inset) 0 calc(20px * var(--v-shadow-y-mult,1)) calc(40px * (0.1 + var(--global-shadow-spread,1))) -0px rgb(from var(--un-shadow-color) r g b / calc((var(--un-shadow-opacity,0.1)))),
11
- var(--un-shadow-inset) 0 calc(15px * var(--v-shadow-y-mult,1)) calc(30px * (0.05 + var(--global-shadow-spread,1))) -6px rgb(from var(--un-shadow-color) r g b / calc(0.5*(var(--un-shadow-opacity,0.1))))`,
7
+ sm: `var(--un-shadow-inset) 0 calc(0px * var(--v-shadow-y-mult,1)) calc(1px * var(--global-shadow-spread,1)) 0 rgb(from var(--un-shadow-color) r g b / ${opacity1}),
8
+ var(--un-shadow-inset) 0 calc(1px * var(--v-shadow-y-mult,1)) calc(2px * var(--global-shadow-spread,1)) 0 rgb(from var(--un-shadow-color) r g b / ${opacity1})`,
9
+ md: `var(--un-shadow-inset) 0 calc(4px * var(--v-shadow-y-mult,1)) calc(8px * var(--global-shadow-spread,1)) -1px rgb(from var(--un-shadow-color) r g b / ${opacity2}),
10
+ var(--un-shadow-inset) 0 calc(2px * var(--v-shadow-y-mult,1)) calc(4px * var(--global-shadow-spread,1)) -1px rgb(from var(--un-shadow-color) r g b / ${opacity1})`,
11
+ lg: `var(--un-shadow-inset) 0 calc(8px * var(--v-shadow-y-mult,1)) calc(16px * var(--global-shadow-spread,1)) 0px rgb(from var(--un-shadow-color) r g b / ${opacity2}),
12
+ var(--un-shadow-inset) 0 calc(5px * var(--v-shadow-y-mult,1)) calc(10px * var(--global-shadow-spread,1)) 0px rgb(from var(--un-shadow-color) r g b / ${opacity2})`,
13
+ xl: `var(--un-shadow-inset) 0 calc(20px * var(--v-shadow-y-mult,1)) calc(40px * (0.1 + var(--global-shadow-spread,1))) -0px rgb(from var(--un-shadow-color) r g b / ${opacity2}),
14
+ var(--un-shadow-inset) 0 calc(15px * var(--v-shadow-y-mult,1)) calc(30px * (0.05 + var(--global-shadow-spread,1))) -6px rgb(from var(--un-shadow-color) r g b / ${opacity3})`,
12
15
  };
13
16
  }
14
17
  //# sourceMappingURL=shadows.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"shadows.js","sourceRoot":"","sources":["../../../src/uno/shadows.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,UAAU,CAAC,IAAI,GAAG,KAAK;IACtC,OAAO;QACN,EAAE,EAAE;6LACuL;QAC3L,EAAE,EAAE;gMAC0L;QAC9L,EAAE,EAAE;8LACwL;QAC5L,EAAE,EAAE;6MACuM;KAC3M,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"shadows.js","sourceRoot":"","sources":["../../../src/uno/shadows.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,UAAU,CAAC,IAAI,GAAG,KAAK;IACtC,MAAM,QAAQ,GAAG,gFAAgF,CAAC;IAClG,MAAM,QAAQ,GAAG,8EAA8E,CAAC;IAChG,MAAM,QAAQ,GAAG,gFAAgF,CAAC;IAClG,OAAO;QACN,EAAE,EAAE,qJAAqJ,QAAQ;uJACZ,QAAQ,GAAG;QAChK,EAAE,EAAE,wJAAwJ,QAAQ;0JACZ,QAAQ,GAAG;QACnK,EAAE,EAAE,wJAAwJ,QAAQ;0JACZ,QAAQ,GAAG;QACnK,EAAE,EAAE,kKAAkK,QAAQ;qKACX,QAAQ,GAAG;KAC9K,CAAC;AACH,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@a-type/ui",
3
- "version": "2.6.1",
3
+ "version": "2.7.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "/dist",
@@ -68,6 +68,7 @@
68
68
  "date-fns": "^4.1.0",
69
69
  "formik": "^2.4.6",
70
70
  "frimousse": "^0.2.0",
71
+ "motion": "^12.23.6",
71
72
  "pluralize": "^8.0.0",
72
73
  "react-hot-toast": "^2.4.1",
73
74
  "valtio": "^2.1.4"
@@ -1,5 +1,6 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react';
2
2
  import { useState } from 'react';
3
+ import { Box } from '../box/Box.js';
3
4
  import { Icon } from '../icon/index.js';
4
5
  import { Button } from './Button.js';
5
6
  import { ConfirmedButton } from './ConfirmedButton.js';
@@ -13,6 +14,12 @@ const meta = {
13
14
  },
14
15
  args: {
15
16
  children: 'Button',
17
+ loading: false,
18
+ color: 'default',
19
+ size: 'default',
20
+ visuallyDisabled: false,
21
+ disabled: false,
22
+ visuallyFocused: false,
16
23
  },
17
24
  } satisfies Meta<typeof Button>;
18
25
 
@@ -20,7 +27,37 @@ export default meta;
20
27
 
21
28
  type Story = StoryObj<typeof Button>;
22
29
 
23
- export const Default: Story = {};
30
+ export const Default: Story = {
31
+ render: (args) => {
32
+ return (
33
+ <Box gap items="center">
34
+ <Button {...args} />
35
+ <Button {...args} color="primary">
36
+ <Icon name="placeholder" />
37
+ {args.children}
38
+ </Button>
39
+ <Button {...args} size="small" />
40
+ </Box>
41
+ );
42
+ },
43
+ };
44
+
45
+ export const WithIcon: Story = {
46
+ args: {
47
+ children: (
48
+ <>
49
+ <Icon name="placeholder" />
50
+ Iconic
51
+ </>
52
+ ),
53
+ },
54
+ };
55
+
56
+ export const IconOnly: Story = {
57
+ args: {
58
+ children: <Icon name="placeholder" />,
59
+ },
60
+ };
24
61
 
25
62
  export const Toggled: Story = {
26
63
  render: (args) => {
@@ -31,18 +68,20 @@ export const Toggled: Story = {
31
68
  };
32
69
 
33
70
  export const Alignment: Story = {
34
- render() {
71
+ render(args) {
35
72
  return (
36
73
  <div className="col">
37
74
  <div className="row border-default">
38
- <Button size="small">Button</Button>
39
- <Button size="icon-small">
75
+ <Button size="small" {...args}>
76
+ Button
77
+ </Button>
78
+ <Button size="small" {...args}>
40
79
  <Icon name="placeholder" />
41
80
  </Button>
42
81
  </div>
43
82
  <div className="row border-default">
44
- <Button>Button</Button>
45
- <Button size="icon">
83
+ <Button {...args}>Button</Button>
84
+ <Button {...args}>
46
85
  <Icon name="placeholder" />
47
86
  </Button>
48
87
  </div>
@@ -52,12 +91,13 @@ export const Alignment: Story = {
52
91
  };
53
92
 
54
93
  export const ConfirmedButtonDemo: Story = {
55
- render() {
94
+ render(args) {
56
95
  return (
57
96
  <ConfirmedButton
58
97
  confirmText="Are you sure?"
59
98
  confirmTitle="Confirm"
60
99
  onConfirm={() => console.log('confirmed')}
100
+ {...args}
61
101
  >
62
102
  Confirm
63
103
  </ConfirmedButton>
@@ -1,6 +1,17 @@
1
1
  import { Slot } from '@radix-ui/react-slot';
2
2
  import classNames from 'clsx';
3
- import { ButtonHTMLAttributes, memo, Ref } from 'react';
3
+ import { AnimatePresence, motion } from 'motion/react';
4
+ import {
5
+ ButtonHTMLAttributes,
6
+ Children,
7
+ memo,
8
+ Ref,
9
+ useCallback,
10
+ useState,
11
+ } from 'react';
12
+ import { withClassName } from '../../hooks.js';
13
+ import useMergedRef from '../../hooks/useMergedRef.js';
14
+ import { IconLoadingProvider } from '../icon/IconLoadingContext.js';
4
15
  import { Icon } from '../icon/index.js';
5
16
  import { Spinner } from '../spinner/index.js';
6
17
  import { getButtonClassName } from './classes.js';
@@ -16,6 +27,9 @@ export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
16
27
  | 'accent'
17
28
  | 'contrast'
18
29
  | 'unstyled';
30
+ /**
31
+ * icon and icon-small are deprecated.
32
+ */
19
33
  size?: 'default' | 'small' | 'icon' | 'icon-small';
20
34
  toggled?: boolean;
21
35
  toggleMode?: 'color-and-indicator' | 'color' | 'indicator' | 'state-only';
@@ -27,7 +41,7 @@ export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
27
41
  ref?: Ref<HTMLButtonElement>;
28
42
  }
29
43
 
30
- export function Button({
44
+ export function ButtonRoot({
31
45
  className,
32
46
  color,
33
47
  size,
@@ -44,13 +58,21 @@ export function Button({
44
58
  ...props
45
59
  }: ButtonProps) {
46
60
  const Comp = asChild ? Slot : 'button';
61
+
62
+ const isFormSubmitting = false;
63
+ const isSubmitLoading = props.type === 'submit' && isFormSubmitting;
64
+ const isLoading = loading || isSubmitLoading;
65
+
66
+ const finalRef = useMergedRef(useAnnotateWithChildParts(), ref);
67
+
47
68
  const buttonProps = {
48
- ref: ref,
69
+ ref: finalRef,
49
70
  ...props,
50
- disabled: disabled || loading,
71
+ disabled: disabled || isLoading,
51
72
  'data-disabled': visuallyDisabled,
52
73
  'data-focus': visuallyFocused,
53
74
  'data-size': size,
75
+ 'data-loading': isLoading,
54
76
  tabIndex: visuallyDisabled ? -1 : undefined,
55
77
  className: classNames(
56
78
  getButtonClassName({
@@ -69,25 +91,68 @@ export function Button({
69
91
  buttonProps['aria-pressed'] = !!toggled;
70
92
  }
71
93
 
94
+ // for asChild, no need to do the rest of this stuff.
72
95
  if (asChild) {
73
96
  // avoid rendering loading spinner with asChild
74
97
  return <Comp {...buttonProps}>{children}</Comp>;
75
98
  }
76
99
 
100
+ // wrap and inspect children
101
+ let hasLabel = false;
102
+ let hasIcon = false;
103
+ const wrappedChildren = Children.toArray(children).map((child, index) => {
104
+ if (child && typeof child === 'object' && 'type' in child) {
105
+ const isIcon = child.type === Icon || child.type;
106
+ if (isIcon) {
107
+ hasIcon = true;
108
+ return child; // return icon as is
109
+ }
110
+ }
111
+
112
+ hasLabel = true; // mark that we have a label
113
+
114
+ if ((!!child && typeof child === 'string') || typeof child === 'number') {
115
+ return (
116
+ <span key={`text-${index}`} data-auto-wrapped-label className="flex">
117
+ {child}
118
+ </span>
119
+ );
120
+ }
121
+ return child;
122
+ });
123
+
77
124
  return (
78
- <Comp {...buttonProps}>
79
- {loading && <Spinner size={16} className="inline-block w-1em h-1em" />}
80
- {toggled !== undefined &&
81
- (toggleMode === 'indicator' ||
82
- toggleMode === 'color-and-indicator') && (
83
- <ToggleIndicator value={toggled} />
84
- )}
85
- {children}
86
- </Comp>
125
+ <IconLoadingProvider value={isLoading}>
126
+ <Comp
127
+ {...buttonProps}
128
+ data-has-icon={String(hasIcon || isLoading)}
129
+ data-has-label={String(hasLabel)}
130
+ >
131
+ <AnimatePresence>
132
+ {isLoading && !hasIcon && (
133
+ <motion.div
134
+ key="spinner"
135
+ initial={{ width: 0, marginLeft: '-0.5rem' }}
136
+ animate={{ width: 'auto', marginLeft: 0 }}
137
+ exit={{ width: 0, marginLeft: '-0.5rem' }}
138
+ className="flex-shrink-0 inline-block overflow-hidden my-auto flex"
139
+ >
140
+ <Spinner size={15} className="inline-block w-1em h-1em" />
141
+ </motion.div>
142
+ )}
143
+ </AnimatePresence>
144
+ {toggled !== undefined &&
145
+ (toggleMode === 'indicator' ||
146
+ toggleMode === 'color-and-indicator') && (
147
+ <ButtonToggleIndicator value={toggled} />
148
+ )}
149
+ {wrappedChildren}
150
+ </Comp>
151
+ </IconLoadingProvider>
87
152
  );
88
153
  }
89
154
 
90
- const ToggleIndicator = memo(function ToggleIndicator({
155
+ export const ButtonToggleIndicator = memo(function ToggleIndicator({
91
156
  value,
92
157
  }: {
93
158
  value: boolean;
@@ -103,3 +168,61 @@ const ToggleIndicator = memo(function ToggleIndicator({
103
168
  />
104
169
  );
105
170
  });
171
+
172
+ // allows custom icons to trigger icon button behavior
173
+ export const ButtonIcon = withClassName(
174
+ 'div',
175
+ 'icon flex-shrink-0 inline-block',
176
+ );
177
+
178
+ export const Button = Object.assign(ButtonRoot, {
179
+ ToggleIndicator: ButtonToggleIndicator,
180
+ Icon: ButtonIcon,
181
+ });
182
+
183
+ function useAnnotateWithChildParts() {
184
+ const mutationObserver = useState(() => {
185
+ if (typeof window === 'undefined') return null!;
186
+ return new MutationObserver((entries) => {
187
+ applyPartAttributes(entries[0].target as HTMLButtonElement);
188
+ });
189
+ })[0];
190
+
191
+ const ref = useCallback(
192
+ (node: HTMLButtonElement | null) => {
193
+ if (node && mutationObserver) {
194
+ mutationObserver.disconnect();
195
+ mutationObserver.observe(node, { childList: true, subtree: true });
196
+ applyPartAttributes(node);
197
+ } else if (mutationObserver) {
198
+ mutationObserver.disconnect();
199
+ }
200
+ },
201
+ [mutationObserver],
202
+ );
203
+
204
+ return ref;
205
+ }
206
+
207
+ function applyPartAttributes(button: HTMLButtonElement) {
208
+ // each child node that's not an icon counts as a label
209
+ const registry = {
210
+ icon: 0,
211
+ label: 0,
212
+ };
213
+ button.childNodes.forEach((child) => {
214
+ if (!(child instanceof HTMLElement || child instanceof SVGElement)) return;
215
+ if (child.style.display === 'none' || child.style.width === '0') return; // skip hidden elements
216
+ if (
217
+ (child instanceof HTMLElement || child instanceof SVGElement) &&
218
+ child.classList.contains('icon')
219
+ ) {
220
+ registry.icon++;
221
+ } else {
222
+ registry.label++;
223
+ }
224
+ });
225
+ button.setAttribute('data-has-icon', String(registry.icon > 0));
226
+ button.setAttribute('data-has-label', String(registry.label > 0));
227
+ button.setAttribute('data-icon-count', String(registry.icon));
228
+ }
@@ -1,9 +1,16 @@
1
- import classNames from 'clsx';
2
- import type { ButtonProps } from './Button.jsx';
1
+ import clsx from 'clsx';
2
+ import { type ButtonProps } from './Button.jsx';
3
+
4
+ const sizeMap = {
5
+ default: 'default',
6
+ small: 'small',
7
+ icon: 'default',
8
+ 'icon-small': 'small',
9
+ } satisfies Record<string, 'default' | 'small'>;
3
10
 
4
11
  export function getButtonClassName({
5
12
  color,
6
- size,
13
+ size: rawSize,
7
14
  toggleable,
8
15
  align,
9
16
  }: {
@@ -12,8 +19,10 @@ export function getButtonClassName({
12
19
  toggleable?: boolean;
13
20
  align?: ButtonProps['align'];
14
21
  }) {
15
- return classNames(
16
- 'layer-components:(px-4 py-2 bg-[var(--bg-neutral,var(--bg))] [--webkit-tap-highlight-color:transparent] [line-height:1] text-size-md font-inherit border-solid border-thin border-transparent rounded-lg cursor-pointer font-bold flex flex-row gap-1 items-center relative overflow-visible select-none all:transition duration-200 whitespace-nowrap ring-bg)',
22
+ const size = sizeMap[rawSize ?? 'default'];
23
+
24
+ return clsx(
25
+ 'layer-components:(px-4 py-2 bg-[var(--bg-neutral,var(--bg))] [--webkit-tap-highlight-color:transparent] [line-height:1] text-size-md font-inherit border-solid border-thin border-transparent rounded-lg cursor-pointer font-bold flex flex-row gap-sm items-center relative overflow-visible select-none all:transition duration-200 whitespace-nowrap ring-bg)',
17
26
  'layer-components:hover:(bg-[var(--bg)] bg-darken-1 ring-4)',
18
27
  'layer-components:focus:outline-off',
19
28
  'layer-components:focus-visible:(bg-[var(--bg)] outline-off bg-darken-1 ring-6)',
@@ -37,7 +46,7 @@ export function getButtonClassName({
37
46
  const colors = {
38
47
  primary: `layer-variants:[&.btn-color-primary]:([--bg:var(--color-primary)] shadow-sm color-black border-primary-dark)`,
39
48
  accent: `layer-variants:[&.btn-color-accent]:([--bg-neutral:var(--color-accent-wash)] [--bg:var(--color-accent-light)] shadow-sm color-black border-accent-dark)`,
40
- default: `layer-variants:[&.btn-color-default]:([--bg-neutral:var(--color-white)] [--bg:var(--color-gray-light)] shadow-sm color-black border-gray-dark)`,
49
+ default: `layer-variants:[&.btn-color-default]:([--bg-neutral:var(--color-white)] [--bg:var(--color-gray)] shadow-sm color-black border-gray-dark focus-visible:bg-lighten-1 hover:bg-lighten-1)`,
41
50
  ghost: `layer-variants:[&.btn-color-ghost]:([--bg-neutral:transparent] [--bg:oklch(from_var(--color-gray)_l_c_h/50%)] color-dark-blend)`,
42
51
  destructive: `layer-variants:[&.btn-color-destructive]:([--bg:var(--color-attention)] shadow-sm border-attention-dark color-black)`,
43
52
  ghostDestructive: `layer-variants:[&.btn-color-ghostDestructive]:([--bg-neutral:transparent] [--bg:var(--color-attention-light)] color-attention-dark hover:(color-black) focus-visible:(color-black))`,
@@ -48,11 +57,10 @@ const colors = {
48
57
  export const buttonColorClasses = colors;
49
58
 
50
59
  const sizes = {
51
- default: '',
52
- small: 'layer-variants:[&.size-small]:(px-4 py-1 text-sm rounded-md)',
53
- icon: 'layer-variants:[&.size-icon]:(p-2.35 text-sm rounded-lg)',
54
- 'icon-small':
55
- 'layer-variants:[&.size-icon-small]:(p-2 text-xs rounded-lg -m-y-0.5)',
60
+ default:
61
+ 'layer-variants:[&[data-has-icon=true][data-has-label=false]]:(p-2.35 text-sm rounded-lg)',
62
+ small:
63
+ 'layer-variants:[&.size-small]:(px-4 py-1 text-sm rounded-md) layer-variants:[&.size-small[data-has-icon=true][data-has-label=false]]:(p-2 text-xs rounded-lg -m-y-0.5)',
56
64
  };
57
65
 
58
66
  const toggledClass =
@@ -244,7 +244,6 @@ export const CameraDeviceSelector = (props: CameraDeviceSelectorProps) => {
244
244
  if (devices.length === 2) {
245
245
  return (
246
246
  <Button
247
- size="icon"
248
247
  color="ghost"
249
248
  className="absolute bottom-2 left-2 color-white"
250
249
  onClick={swapCamera}
@@ -260,11 +259,7 @@ export const CameraDeviceSelector = (props: CameraDeviceSelectorProps) => {
260
259
  onValueChange={selectDeviceId}
261
260
  >
262
261
  <Select.Trigger asChild>
263
- <Button
264
- size="icon"
265
- color="ghost"
266
- className="absolute bottom-2 left-2 color-white"
267
- >
262
+ <Button color="ghost" className="absolute bottom-2 left-2 color-white">
268
263
  <Icon name="refresh" />
269
264
  </Button>
270
265
  </Select.Trigger>
@@ -284,7 +279,6 @@ export const CameraFullscreenButton = (props: ButtonProps) => {
284
279
  return (
285
280
  <Button
286
281
  {...props}
287
- size="icon"
288
282
  color="ghost"
289
283
  className="absolute top-2 right-2 color-white"
290
284
  onClick={() => setFullscreen(!fullscreen)}
@@ -46,12 +46,12 @@ export const Default: Story = {
46
46
  <CardFooter>
47
47
  <CardActions>
48
48
  <Button size="small">Button</Button>
49
- <Button size="icon-small" color="primary">
49
+ <Button size="small" color="primary">
50
50
  <Icon name="placeholder" />
51
51
  </Button>
52
52
  </CardActions>
53
53
  <CardMenu>
54
- <Button size="icon-small" color="ghost">
54
+ <Button size="small" color="ghost">
55
55
  <Icon name="dots" />
56
56
  </Button>
57
57
  </CardMenu>
@@ -68,7 +68,7 @@ export const Default: Story = {
68
68
  <CardFooter>
69
69
  <CardActions>
70
70
  <Button size="small">Button</Button>
71
- <Button size="icon-small" color="ghost">
71
+ <Button size="small" color="ghost">
72
72
  <Icon name="placeholder" />
73
73
  </Button>
74
74
  </CardActions>
@@ -89,7 +89,7 @@ export const Compact: Story = {
89
89
  <CardFooter>
90
90
  <CardActions>
91
91
  <Button size="small">Button</Button>
92
- <Button size="icon-small" color="ghost">
92
+ <Button size="small" color="ghost">
93
93
  <Icon name="placeholder" />
94
94
  </Button>
95
95
  </CardActions>
@@ -108,7 +108,7 @@ export const NonInteractive: Story = {
108
108
  <CardFooter>
109
109
  <CardActions>
110
110
  <Button size="small">Button</Button>
111
- <Button size="icon" color="ghost">
111
+ <Button color="ghost">
112
112
  <Icon name="placeholder" />
113
113
  </Button>
114
114
  </CardActions>
@@ -129,7 +129,7 @@ export const AsChild: Story = {
129
129
  <CardFooter>
130
130
  <CardActions>
131
131
  <Button size="small">Button</Button>
132
- <Button size="icon" color="ghost">
132
+ <Button color="ghost">
133
133
  <Icon name="placeholder" />
134
134
  </Button>
135
135
  </CardActions>
@@ -150,7 +150,7 @@ export const AsChildNonInteractive: Story = {
150
150
  <CardFooter>
151
151
  <CardActions>
152
152
  <Button size="small">Button</Button>
153
- <Button size="icon" color="ghost">
153
+ <Button color="ghost">
154
154
  <Icon name="placeholder" />
155
155
  </Button>
156
156
  </CardActions>
@@ -249,7 +249,7 @@ export const VisuallyFocused: Story = {
249
249
  <CardFooter>
250
250
  <CardActions>
251
251
  <Button size="small">Button</Button>
252
- <Button size="icon" color="ghost">
252
+ <Button color="ghost">
253
253
  <Icon name="placeholder" />
254
254
  </Button>
255
255
  </CardActions>
@@ -270,7 +270,7 @@ export const CardsInBox: Story = {
270
270
  <CardFooter>
271
271
  <CardActions>
272
272
  <Button size="small">Button</Button>
273
- <Button size="icon" color="ghost">
273
+ <Button color="ghost">
274
274
  <Icon name="placeholder" />
275
275
  </Button>
276
276
  </CardActions>
@@ -284,7 +284,7 @@ export const CardsInBox: Story = {
284
284
  <CardFooter>
285
285
  <CardActions>
286
286
  <Button size="small">Button</Button>
287
- <Button size="icon" color="ghost">
287
+ <Button color="ghost">
288
288
  <Icon name="placeholder" />
289
289
  </Button>
290
290
  </CardActions>
@@ -19,7 +19,7 @@ export const ContextMenuContent = function Content({
19
19
  <BoxContext.Provider value={{ spacingScale: 1 }}>
20
20
  <ContextMenuPrimitive.Content
21
21
  className={classNames(
22
- 'layer-components:(min-w-120px bg-white rounded-sm overflow-hidden p-1 border-gray-dark border shadow-md z-menu)',
22
+ 'layer-components:(min-w-120px bg-white rounded-md overflow-hidden border-gray-dark border shadow-md z-menu)',
23
23
  'layer-components:transform-origin-[var(--radix-context-menu-transform-origin)]',
24
24
  'layer-components:[&[data-state=open]]:animate-popover-in',
25
25
  'layer-components:[&[data-state=closed]]:animate-popover-out',
@@ -47,10 +47,8 @@ export const ContextMenuArrow = withClassName(
47
47
 
48
48
  export const ContextMenuItem = withClassName(
49
49
  ContextMenuPrimitive.Item,
50
- 'layer-components:(flex items-center py-1 px-2 relative pl-25px select-none outline-none cursor-pointer rounded-sm)',
51
- 'layer-components:[&:first-of-type]:rounded-t-md',
52
- 'layer-components:[&:last-of-type]:rounded-b-md',
53
- 'layer-components:(hover:bg-gray-light [&[data-highlighted=true]]:bg-gray-light [&[data-disabled=true]]:(opacity-50 cursor-default) disabled:(opacity-50 cursor-default))',
50
+ 'layer-components:(flex items-center py-1 px-2 relative pl-25px select-none outline-none cursor-pointer)',
51
+ 'layer-components:(hover:(bg-gray bg-lighten-3) [&[data-highlighted=true]]:(bg-gray bg-lighten-3) [&[data-disabled=true]]:(opacity-50 cursor-default) disabled:(opacity-50 cursor-default))',
54
52
  );
55
53
 
56
54
  export const ContextMenuTrigger = withClassName(
@@ -40,7 +40,6 @@ export function DatePicker({
40
40
  >
41
41
  <MonthRow>
42
42
  <MonthButton
43
- size="icon"
44
43
  color="ghost"
45
44
  onClick={() =>
46
45
  setDisplay((cur) => ({
@@ -53,7 +52,6 @@ export function DatePicker({
53
52
  </MonthButton>
54
53
  <MonthLabel>{monthLabel}</MonthLabel>
55
54
  <MonthButton
56
- size="icon"
57
55
  color="ghost"
58
56
  onClick={() =>
59
57
  setDisplay((cur) => ({
@@ -143,7 +141,6 @@ export function DateRangePicker({
143
141
  >
144
142
  <RangeLayout>
145
143
  <MonthButton
146
- size="icon"
147
144
  color="ghost"
148
145
  className="[grid-area:prevMonth]"
149
146
  onClick={() =>
@@ -160,7 +157,6 @@ export function DateRangePicker({
160
157
  {nextMonthLabel}
161
158
  </MonthLabel>
162
159
  <MonthButton
163
- size="icon"
164
160
  color="ghost"
165
161
  className="[grid-area:nextMonth]"
166
162
  onClick={() =>
@@ -15,7 +15,7 @@ const StyledContent = withClassName(
15
15
  </BoxContext.Provider>
16
16
  );
17
17
  },
18
- 'layer-components:(min-w-220px bg-white z-menu shadow-lg rounded-sm border border-gray-dark)',
18
+ 'layer-components:(min-w-220px bg-white z-menu shadow-lg rounded-md border border-gray-dark)',
19
19
  'layer-components:transform-origin-[var(--radix-dropdown-menu-transform-origin)]',
20
20
  'layer-components:[&[data-state=open]]:animate-popover-in',
21
21
  'layer-components:[&[data-state=closed]]:animate-popover-out',
@@ -25,8 +25,9 @@ const StyledContent = withClassName(
25
25
  );
26
26
  const itemClassName = classNames(
27
27
  'layer-components:(text-md leading-4 color-black flex items-center pr-4 pl-8 py-2 relative text-left select-none cursor-pointer)',
28
- 'layer-components:[&[data-disabled]]:(color-black pointer-events-none)',
29
- 'layer-components:focus-visible:(bg-gray-light bg-darken-0.5 color-black)',
28
+ 'layer-components:[&[data-disabled]]:(color-gray-dark pointer-events-none)',
29
+ 'layer-components:focus-visible:(bg-gray bg-lighten-3 color-black)',
30
+ 'layer-components:hover:(bg-gray bg-lighten-3 color-black)',
30
31
  'layer-components:focus:outline-none',
31
32
  );
32
33
  const StyledItemBase = withClassName(DropdownMenuPrimitive.Item, itemClassName);
@@ -46,7 +47,7 @@ const StyledItem = ({
46
47
  {...props}
47
48
  className={clsx(
48
49
  color === 'destructive' &&
49
- 'layer-variants:(color-attention-dark hover:bg-attention-wash focus-visible:bg-attention-wash)',
50
+ 'layer-variants:(color-attention-dark hover:bg-attention-light focus-visible:bg-attention-light)',
50
51
  className,
51
52
  )}
52
53
  ref={forwardedRef}
@@ -111,14 +112,14 @@ export const DropdownMenuContent = ({
111
112
  return (
112
113
  <StyledPortal forceMount={forceMount}>
113
114
  <StyledContent {...props}>
114
- <div className="overflow-hidden rounded-lg">{children}</div>
115
+ <div className="overflow-hidden rounded-md">{children}</div>
115
116
  <StyledArrow />
116
117
  </StyledContent>
117
118
  </StyledPortal>
118
119
  );
119
120
  };
120
121
 
121
- export const DropdownMenuItemRightSlot = withClassName('div', 'ml-auto');
122
+ export const DropdownMenuItemRightSlot = withClassName('div', 'ml-auto pl-md');
122
123
 
123
124
  export const DropdownMenu = Object.assign(DropdownMenuRoot, {
124
125
  Content: DropdownMenuContent,
@@ -71,11 +71,11 @@ export const EmojiPickerEmoji = withClassName(
71
71
  color="ghost"
72
72
  toggled={p.emoji.isActive}
73
73
  toggleMode="color"
74
- size="icon-small"
74
+ size="small"
75
75
  aria-label={p.emoji.label}
76
76
  className="text-lg p-xs"
77
77
  >
78
- {p.emoji.emoji}
78
+ <Button.Icon>{p.emoji.emoji}</Button.Icon>
79
79
  </Button>
80
80
  ),
81
81
  '',
@@ -122,12 +122,12 @@ export const EmojiPickerSkinToneSelector = (props: BoxProps) => {
122
122
  color="ghost"
123
123
  toggled={option.skinTone === skinTone}
124
124
  toggleMode="color"
125
- size="icon-small"
125
+ size="small"
126
126
  aria-label={`Skin tone ${option}`}
127
127
  className="text-md p-xs"
128
128
  onClick={() => setSkinTone(option.skinTone)}
129
129
  >
130
- {option.emoji}
130
+ <Button.Icon>{option.emoji}</Button.Icon>
131
131
  </Button>
132
132
  ))}
133
133
  </Box>
@@ -36,7 +36,6 @@ const meta = {
36
36
  <Button size="small">Twenty three</Button>
37
37
  <Button size="small">Twenty four</Button>
38
38
  <Button
39
- size="icon"
40
39
  color="primary"
41
40
  className="sticky right-2 bottom-2 flex-shrink-0 shadow-sm ml-auto"
42
41
  >
@@ -113,7 +112,6 @@ export const CantOpen: Story = {
113
112
  </>
114
113
  )}
115
114
  <Button
116
- size="icon"
117
115
  color="primary"
118
116
  className="sticky right-2 bottom-2 flex-shrink-0 shadow-sm ml-auto"
119
117
  >