@atlaskit/editor-toolbar 0.2.3 → 0.3.1

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 (109) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/cjs/index.js +15 -7
  3. package/dist/cjs/ui/ColorPalette/Color.compiled.css +25 -0
  4. package/dist/cjs/ui/ColorPalette/Color.js +76 -0
  5. package/dist/cjs/ui/ColorPalette/getColorMessage.js +20 -0
  6. package/dist/cjs/ui/ColorPalette/index.compiled.css +1 -0
  7. package/dist/cjs/ui/ColorPalette/index.js +118 -0
  8. package/dist/cjs/ui/ColorPalette/types.js +5 -0
  9. package/dist/cjs/ui/ColorPalette/utils.js +94 -0
  10. package/dist/cjs/ui/ToolbarButton.compiled.css +1 -13
  11. package/dist/cjs/ui/ToolbarButton.js +2 -6
  12. package/dist/cjs/ui/ToolbarButtonGroup.compiled.css +7 -1
  13. package/dist/cjs/ui/ToolbarButtonGroup.js +17 -5
  14. package/dist/cjs/ui/ToolbarDropdownItem.compiled.css +4 -2
  15. package/dist/cjs/ui/ToolbarDropdownItem.js +1 -1
  16. package/dist/cjs/ui/ToolbarDropdownItemSection.js +7 -10
  17. package/dist/cjs/ui/ToolbarDropdownMenu.compiled.css +1 -0
  18. package/dist/cjs/ui/ToolbarDropdownMenu.js +51 -6
  19. package/dist/cjs/ui/ToolbarDropdownMenuContext.js +40 -0
  20. package/dist/es2019/index.js +2 -1
  21. package/dist/es2019/ui/ColorPalette/Color.compiled.css +25 -0
  22. package/dist/es2019/ui/ColorPalette/Color.js +65 -0
  23. package/dist/es2019/ui/ColorPalette/getColorMessage.js +18 -0
  24. package/dist/es2019/ui/ColorPalette/index.compiled.css +1 -0
  25. package/dist/es2019/ui/ColorPalette/index.js +110 -0
  26. package/dist/es2019/ui/ColorPalette/types.js +1 -0
  27. package/dist/es2019/ui/ColorPalette/utils.js +83 -0
  28. package/dist/es2019/ui/ToolbarButton.compiled.css +1 -13
  29. package/dist/es2019/ui/ToolbarButton.js +2 -6
  30. package/dist/es2019/ui/ToolbarButtonGroup.compiled.css +7 -1
  31. package/dist/es2019/ui/ToolbarButtonGroup.js +14 -3
  32. package/dist/es2019/ui/ToolbarDropdownItem.compiled.css +4 -2
  33. package/dist/es2019/ui/ToolbarDropdownItem.js +1 -1
  34. package/dist/es2019/ui/ToolbarDropdownItemSection.js +7 -10
  35. package/dist/es2019/ui/ToolbarDropdownMenu.compiled.css +1 -0
  36. package/dist/es2019/ui/ToolbarDropdownMenu.js +50 -6
  37. package/dist/es2019/ui/ToolbarDropdownMenuContext.js +26 -0
  38. package/dist/esm/index.js +2 -1
  39. package/dist/esm/ui/ColorPalette/Color.compiled.css +25 -0
  40. package/dist/esm/ui/ColorPalette/Color.js +67 -0
  41. package/dist/esm/ui/ColorPalette/getColorMessage.js +14 -0
  42. package/dist/esm/ui/ColorPalette/index.compiled.css +1 -0
  43. package/dist/esm/ui/ColorPalette/index.js +109 -0
  44. package/dist/esm/ui/ColorPalette/types.js +1 -0
  45. package/dist/esm/ui/ColorPalette/utils.js +84 -0
  46. package/dist/esm/ui/ToolbarButton.compiled.css +1 -13
  47. package/dist/esm/ui/ToolbarButton.js +2 -6
  48. package/dist/esm/ui/ToolbarButtonGroup.compiled.css +7 -1
  49. package/dist/esm/ui/ToolbarButtonGroup.js +14 -3
  50. package/dist/esm/ui/ToolbarDropdownItem.compiled.css +4 -2
  51. package/dist/esm/ui/ToolbarDropdownItem.js +1 -1
  52. package/dist/esm/ui/ToolbarDropdownItemSection.js +7 -10
  53. package/dist/esm/ui/ToolbarDropdownMenu.compiled.css +1 -0
  54. package/dist/esm/ui/ToolbarDropdownMenu.js +49 -6
  55. package/dist/esm/ui/ToolbarDropdownMenuContext.js +31 -0
  56. package/dist/types/index.d.ts +3 -2
  57. package/dist/types/types.d.ts +0 -1
  58. package/dist/types/ui/ColorPalette/Color.d.ts +11 -0
  59. package/dist/types/ui/ColorPalette/getColorMessage.d.ts +8 -0
  60. package/dist/types/ui/ColorPalette/index.d.ts +15 -0
  61. package/dist/types/ui/ColorPalette/types.d.ts +89 -0
  62. package/dist/types/ui/ColorPalette/utils.d.ts +40 -0
  63. package/dist/types/ui/ToolbarButton.d.ts +0 -2
  64. package/dist/types/ui/ToolbarButtonGroup.d.ts +6 -2
  65. package/dist/types/ui/ToolbarDropdownItemSection.d.ts +3 -1
  66. package/dist/types/ui/ToolbarDropdownMenu.d.ts +9 -3
  67. package/dist/types/ui/ToolbarDropdownMenuContext.d.ts +12 -0
  68. package/dist/types/ui/ToolbarTooltip.d.ts +1 -1
  69. package/dist/types-ts4.5/index.d.ts +3 -2
  70. package/dist/types-ts4.5/types.d.ts +0 -1
  71. package/dist/types-ts4.5/ui/ColorPalette/Color.d.ts +11 -0
  72. package/dist/types-ts4.5/ui/ColorPalette/getColorMessage.d.ts +8 -0
  73. package/dist/types-ts4.5/ui/ColorPalette/index.d.ts +15 -0
  74. package/dist/types-ts4.5/ui/ColorPalette/types.d.ts +89 -0
  75. package/dist/types-ts4.5/ui/ColorPalette/utils.d.ts +40 -0
  76. package/dist/types-ts4.5/ui/ToolbarButton.d.ts +0 -2
  77. package/dist/types-ts4.5/ui/ToolbarButtonGroup.d.ts +6 -2
  78. package/dist/types-ts4.5/ui/ToolbarDropdownItemSection.d.ts +3 -1
  79. package/dist/types-ts4.5/ui/ToolbarDropdownMenu.d.ts +9 -3
  80. package/dist/types-ts4.5/ui/ToolbarDropdownMenuContext.d.ts +12 -0
  81. package/dist/types-ts4.5/ui/ToolbarTooltip.d.ts +1 -1
  82. package/examples/toolbar/examples/ExampleManuallyComposedToolbar.tsx +2 -12
  83. package/package.json +5 -3
  84. package/src/index.ts +4 -2
  85. package/src/types.ts +0 -1
  86. package/src/ui/ColorPalette/Color.tsx +118 -0
  87. package/src/ui/ColorPalette/getColorMessage.ts +27 -0
  88. package/src/ui/ColorPalette/index.tsx +125 -0
  89. package/src/ui/ColorPalette/types.ts +96 -0
  90. package/src/ui/ColorPalette/utils.ts +102 -0
  91. package/src/ui/ToolbarButton.tsx +0 -28
  92. package/src/ui/ToolbarButtonGroup.tsx +42 -3
  93. package/src/ui/ToolbarDropdownItem.tsx +4 -2
  94. package/src/ui/ToolbarDropdownItemSection.tsx +13 -11
  95. package/src/ui/ToolbarDropdownMenu.tsx +68 -9
  96. package/src/ui/ToolbarDropdownMenuContext.tsx +44 -0
  97. package/src/ui/ToolbarTooltip.tsx +1 -1
  98. package/dist/cjs/ui/ToolbarDropdownDivider.compiled.css +0 -3
  99. package/dist/cjs/ui/ToolbarDropdownDivider.js +0 -20
  100. package/dist/cjs/ui/ToolbarDropdownItemSection.compiled.css +0 -2
  101. package/dist/es2019/ui/ToolbarDropdownDivider.compiled.css +0 -3
  102. package/dist/es2019/ui/ToolbarDropdownDivider.js +0 -12
  103. package/dist/es2019/ui/ToolbarDropdownItemSection.compiled.css +0 -2
  104. package/dist/esm/ui/ToolbarDropdownDivider.compiled.css +0 -3
  105. package/dist/esm/ui/ToolbarDropdownDivider.js +0 -12
  106. package/dist/esm/ui/ToolbarDropdownItemSection.compiled.css +0 -2
  107. package/dist/types/ui/ToolbarDropdownDivider.d.ts +0 -1
  108. package/dist/types-ts4.5/ui/ToolbarDropdownDivider.d.ts +0 -1
  109. package/src/ui/ToolbarDropdownDivider.tsx +0 -20
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+ interface ToolbarDropdownMenuContextValue {
3
+ openMenu: () => void;
4
+ closeMenu: () => void;
5
+ isOpen: boolean;
6
+ }
7
+ export declare const useToolbarDropdownMenu: () => ToolbarDropdownMenuContextValue;
8
+ interface ToolbarDropdownMenuProviderProps {
9
+ children: React.ReactNode;
10
+ }
11
+ export declare const ToolbarDropdownMenuProvider: ({ children }: ToolbarDropdownMenuProviderProps) => React.JSX.Element;
12
+ export {};
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import type { PositionType } from '@atlaskit/tooltip';
3
3
  type ToolbarTooltipProps = {
4
- content: string;
4
+ content: React.ReactNode;
5
5
  children: React.ReactNode;
6
6
  position?: PositionType;
7
7
  };
@@ -162,16 +162,12 @@ export const ExampleManuallyComposedToolbar = () => {
162
162
  <ToolbarButton
163
163
  iconBefore={<AIChatIcon label="Ask Rovo" />}
164
164
  onClick={onClick('Ask Rovo')}
165
- groupLocation="start"
166
165
  isDisabled={isRovoDisabled}
167
166
  >
168
167
  Ask Rovo
169
168
  </ToolbarButton>
170
169
  </ToolbarTooltip>
171
- <ToolbarDropdownMenu
172
- iconBefore={<MoreItemsIcon label="More formatting" />}
173
- groupLocation="end"
174
- >
170
+ <ToolbarDropdownMenu iconBefore={<MoreItemsIcon label="More formatting" />}>
175
171
  <ToolbarDropdownItemSection>
176
172
  <ToolbarNestedDropdownMenu
177
173
  elemBefore={<AIAdjustLengthIcon label="Adjust length" />}
@@ -380,16 +376,12 @@ export const ExampleManuallyComposedToolbar = () => {
380
376
  formatting.italic && !formatting.bold ? 'Italic' : 'Bold',
381
377
  onToggleFormatting(formatting.italic && !formatting.bold ? 'italic' : 'bold'),
382
378
  )}
383
- groupLocation="start"
384
379
  isSelected={formatting.bold || formatting.italic}
385
380
  isDisabled={formatting.italic ? isItalicDisabled : isBoldDisabled}
386
381
  ariaKeyshortcuts={formatting.italic && !formatting.bold ? '⌘I' : '⌘B'}
387
382
  />
388
383
  </ToolbarTooltip>
389
- <ToolbarDropdownMenu
390
- iconBefore={<MoreItemsIcon label="More formatting" />}
391
- groupLocation="end"
392
- >
384
+ <ToolbarDropdownMenu iconBefore={<MoreItemsIcon label="More formatting" />}>
393
385
  <ToolbarDropdownItemSection>
394
386
  <ToolbarDropdownItem
395
387
  elemBefore={<BoldIcon label="Bold" />}
@@ -471,7 +463,6 @@ export const ExampleManuallyComposedToolbar = () => {
471
463
  listOrAlignment === 'numbered' ? 'Numbered list' : 'Bulleted list',
472
464
  onToggleListOrAlignment(listOrAlignment === 'numbered' ? 'numbered' : 'bulleted'),
473
465
  )}
474
- groupLocation="start"
475
466
  isSelected={listOrAlignment !== 'none'}
476
467
  isDisabled={
477
468
  listOrAlignment === 'numbered' ? isNumberedListDisabled : isBulletedListDisabled
@@ -480,7 +471,6 @@ export const ExampleManuallyComposedToolbar = () => {
480
471
  </ToolbarTooltip>
481
472
  <ToolbarDropdownMenu
482
473
  iconBefore={<MoreItemsIcon label="Lists, indentation and alignment" />}
483
- groupLocation="end"
484
474
  >
485
475
  <ToolbarDropdownItemSection>
486
476
  <ToolbarDropdownItem
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "registry": "https://registry.npmjs.org/"
5
5
  },
6
- "version": "0.2.3",
6
+ "version": "0.3.1",
7
7
  "description": "Common UI for Toolbars across the platform",
8
8
  "atlassian": {
9
9
  "team": "Editor: Jenga",
@@ -36,10 +36,12 @@
36
36
  "@atlaskit/tokens": "^6.0.0",
37
37
  "@atlaskit/tooltip": "^20.4.0",
38
38
  "@babel/runtime": "^7.0.0",
39
- "@compiled/react": "^0.18.3"
39
+ "@compiled/react": "^0.18.3",
40
+ "chromatism": "^2.6.0"
40
41
  },
41
42
  "peerDependencies": {
42
- "react": "^18.2.0"
43
+ "react": "^18.2.0",
44
+ "react-intl-next": "npm:react-intl@^5.18.1"
43
45
  },
44
46
  "devDependencies": {
45
47
  "@af/integration-testing": "workspace:^",
package/src/index.ts CHANGED
@@ -9,8 +9,8 @@ export { ToolbarNestedDropdownMenu } from './ui/ToolbarNestedDropdownMenu';
9
9
  export { ToolbarKeyboardShortcutHint } from './ui/ToolbarKeyboardShortcutHint';
10
10
  export { ToolbarSection } from './ui/ToolbarSection';
11
11
  export { ToolbarTooltip } from './ui/ToolbarTooltip';
12
- export { ToolbarDropdownDivider } from './ui/ToolbarDropdownDivider';
13
12
  export { ToolbarColorSwatch } from './ui/ToolbarColorSwatch';
13
+ export { useToolbarDropdownMenu } from './ui/ToolbarDropdownMenuContext';
14
14
 
15
15
  export { AIAdjustLengthIcon } from './ui/icons/AIAdjustLengthIcon';
16
16
  export { AIChatIcon } from './ui/icons/AIChatIcon';
@@ -58,6 +58,8 @@ export { AlignTextRightIcon } from './ui/icons/AlignTextRightIcon';
58
58
  export { IndentIcon } from './ui/icons/IndentIcon';
59
59
  export { OutdentIcon } from './ui/icons/OutdentIcon';
60
60
 
61
- export type { IconComponent, ToolbarButtonGroupLocation } from './types';
61
+ export { default as ColorPalette } from './ui/ColorPalette';
62
+
63
+ export type { IconComponent } from './types';
62
64
 
63
65
  export { useToolbarUI, ToolbarUIProvider } from './hooks/ui-context';
package/src/types.ts CHANGED
@@ -1,4 +1,3 @@
1
1
  import { type NewCoreIconProps } from '@atlaskit/icon';
2
2
 
3
- export type ToolbarButtonGroupLocation = 'start' | 'middle' | 'end';
4
3
  export type IconComponent = (props: NewCoreIconProps) => JSX.Element;
@@ -0,0 +1,118 @@
1
+ /**
2
+ * @jsxRuntime classic
3
+ * @jsx jsx
4
+ */
5
+ import React, { useCallback, memo } from 'react';
6
+
7
+ import { css, jsx } from '@compiled/react';
8
+
9
+ import EditorDoneIcon from '@atlaskit/icon/core/migration/check-mark--editor-done';
10
+ import { token } from '@atlaskit/tokens';
11
+ import Tooltip from '@atlaskit/tooltip';
12
+
13
+ import type { ColorProps } from './types';
14
+
15
+ const buttonWrapperStyles = css({
16
+ borderColor: 'transparent',
17
+ borderStyle: 'solid',
18
+ borderWidth: '1px',
19
+ display: 'flex',
20
+ alignItems: 'center',
21
+ paddingTop: token('space.025', '2px'),
22
+ paddingRight: token('space.025', '2px'),
23
+ paddingBottom: token('space.025', '2px'),
24
+ paddingLeft: token('space.025', '2px'),
25
+ borderRadius: token('border.radius.100', '4px'),
26
+ '&:focus-within, &:focus, &:hover': {
27
+ borderColor: token('color.border'),
28
+ },
29
+ });
30
+
31
+ const buttonStyles = css({
32
+ height: token('space.300', '26px'),
33
+ width: token('space.300', '26px'),
34
+ backgroundColor: token('color.background.neutral'),
35
+ padding: 0,
36
+ borderRadius: token('border.radius.050', '4px'),
37
+ border: `1px solid ${token('color.border.inverse')}`,
38
+ cursor: 'pointer',
39
+ display: 'block',
40
+ position: 'relative',
41
+ '&:focus': {
42
+ outline: `2px solid ${token('color.border.focused')}`,
43
+ outlineOffset: token('space.025', '2px'),
44
+ },
45
+ });
46
+
47
+ /**
48
+ * Individual color palette item component
49
+ * Displays a single color swatch with tooltip and selection state
50
+ */
51
+ export const Color = memo<ColorProps>(
52
+ ({
53
+ autoFocus,
54
+ tabIndex,
55
+ value,
56
+ label,
57
+ isSelected,
58
+ borderColor,
59
+ checkMarkColor = token('color.icon.inverse', '#FFFFFF'),
60
+ hexToPaletteColor,
61
+ decorator,
62
+ onClick,
63
+ onKeyDown,
64
+ }) => {
65
+ const colorStyle = hexToPaletteColor ? hexToPaletteColor(value) : value;
66
+
67
+ const handleMouseDown = useCallback((e: React.MouseEvent) => {
68
+ e.preventDefault();
69
+ }, []);
70
+
71
+ const handleClick = useCallback(
72
+ (e: React.MouseEvent) => {
73
+ e.preventDefault();
74
+ onClick(value, label);
75
+ },
76
+ [onClick, value, label],
77
+ );
78
+
79
+ const handleKeyDown = useCallback(
80
+ (e: React.KeyboardEvent) => {
81
+ if (!onKeyDown) {
82
+ return;
83
+ }
84
+ e.preventDefault();
85
+ onKeyDown(value, label, e);
86
+ },
87
+ [onKeyDown, value, label],
88
+ );
89
+
90
+ return (
91
+ <Tooltip content={label}>
92
+ <span css={buttonWrapperStyles}>
93
+ <button
94
+ type="button"
95
+ css={buttonStyles}
96
+ aria-label={label}
97
+ role="radio"
98
+ aria-checked={isSelected}
99
+ onClick={handleClick}
100
+ onKeyDown={handleKeyDown}
101
+ onMouseDown={handleMouseDown}
102
+ tabIndex={tabIndex}
103
+ style={{
104
+ backgroundColor: colorStyle || token('color.background.input', '#FFFFFF'),
105
+ border: `1px solid ${borderColor}`,
106
+ }}
107
+ autoFocus={autoFocus}
108
+ >
109
+ {!decorator && isSelected && (
110
+ <EditorDoneIcon LEGACY_primaryColor={checkMarkColor} label="" />
111
+ )}
112
+ {decorator}
113
+ </button>
114
+ </span>
115
+ </Tooltip>
116
+ );
117
+ },
118
+ );
@@ -0,0 +1,27 @@
1
+ import { type MessageDescriptor } from 'react-intl-next';
2
+
3
+ /**
4
+ * Retrieves the appropriate internationalization message for a given color
5
+ * @param messages - Record of color values to message descriptors
6
+ * @param color - The color value to look up
7
+ * @returns The message descriptor or undefined if not found
8
+ */
9
+ export default function getColorMessage(
10
+ messages: Record<string | number, MessageDescriptor>,
11
+ color: string,
12
+ ): MessageDescriptor | undefined {
13
+ const message = messages[color as keyof typeof messages];
14
+
15
+ if (!message) {
16
+ // eslint-disable-next-line no-console
17
+ console.warn(
18
+ `Color palette does not have an internationalization message for color ${color.toUpperCase()}.
19
+ You must add a message description to properly translate this color.
20
+ Using current label as default message.
21
+ This could have happened when someone changed the 'colorPalette' from 'adf-schema' without updating this file.
22
+ `,
23
+ );
24
+ }
25
+
26
+ return message;
27
+ }
@@ -0,0 +1,125 @@
1
+ import React, { useMemo } from 'react';
2
+
3
+ import chromatism from 'chromatism';
4
+ import { useIntl } from 'react-intl-next';
5
+
6
+ import { cssMap } from '@atlaskit/css';
7
+ import { Box } from '@atlaskit/primitives/compiled';
8
+ import { token, useThemeObserver } from '@atlaskit/tokens';
9
+
10
+ import { Color } from './Color';
11
+ import getColorMessage from './getColorMessage';
12
+ import type { ColorPaletteProps } from './types';
13
+ import {
14
+ DEFAULT_COLOR_PICKER_COLUMNS,
15
+ getColorsPerRowFromPalette,
16
+ getTokenCSSVariableValue,
17
+ } from './utils';
18
+
19
+ const styles = cssMap({
20
+ paletteWrapper: {
21
+ display: 'flex',
22
+ },
23
+ });
24
+
25
+ /**
26
+ * For a given color pick the color from a list of colors with
27
+ * the highest contrast
28
+ *
29
+ * @param color color string, supports HEX, RGB, RGBA etc.
30
+ * @param useIconToken boolean, describes if a token should be used for the icon color
31
+ * @return Highest contrast color in pool
32
+ */
33
+ function getCheckMarkColor(color: string, useIconToken: boolean): string {
34
+ const tokenVal = getTokenCSSVariableValue(color);
35
+ const colorValue = !!tokenVal ? tokenVal : color;
36
+
37
+ // eslint-disable-next-line @atlaskit/design-system/ensure-design-token-usage
38
+ const contrastColor = ['#FFFFFF', '#42526E'].sort(
39
+ (a, b) => chromatism.difference(b, colorValue) - chromatism.difference(a, colorValue),
40
+ )[0];
41
+
42
+ if (!useIconToken) {
43
+ return contrastColor;
44
+ }
45
+
46
+ // Use of these token comes from guidance from designers in the Design System team
47
+ // they are only intended for use with text colors (and there are different tokens
48
+ // planned to be used when this extended to be used with other palettes).
49
+ // eslint-disable-next-line @atlaskit/design-system/ensure-design-token-usage
50
+ return contrastColor === '#FFFFFF' ? token('color.icon.inverse') : token('color.icon');
51
+ }
52
+
53
+ /**
54
+ * ColorPalette component for displaying a grid of selectable colors
55
+ *
56
+ * Features:
57
+ * - Responsive grid layout
58
+ * - Keyboard navigation support
59
+ * - Accessibility compliance (ARIA attributes)
60
+ * - Theme-aware tooltips
61
+ * - Design token integration
62
+ * - Customizable color mapping
63
+ */
64
+ const ColorPalette = ({
65
+ cols = DEFAULT_COLOR_PICKER_COLUMNS,
66
+ onClick,
67
+ onKeyDown,
68
+ selectedColor,
69
+ paletteOptions,
70
+ }: ColorPaletteProps) => {
71
+ const { formatMessage } = useIntl();
72
+ const { palette, hexToPaletteColor, paletteColorTooltipMessages } = paletteOptions;
73
+
74
+ const { colorMode: tokenTheme } = useThemeObserver();
75
+ const useIconToken = !!hexToPaletteColor;
76
+
77
+ const colorsPerRow = useMemo(() => {
78
+ return getColorsPerRowFromPalette(palette, cols);
79
+ }, [palette, cols]);
80
+
81
+ return (
82
+ <>
83
+ {colorsPerRow.map((row) => (
84
+ <Box xcss={styles.paletteWrapper} key={`row-first-color-${row[0].value}`} role="radiogroup">
85
+ {row.map(({ value, label, border, message, decorator }) => {
86
+ let tooltipMessage = message;
87
+
88
+ // Override with theme-specific tooltip messages if provided
89
+ if (paletteColorTooltipMessages) {
90
+ if (tokenTheme === 'dark') {
91
+ tooltipMessage = getColorMessage(
92
+ paletteColorTooltipMessages.dark,
93
+ value.toUpperCase(),
94
+ );
95
+ }
96
+ if (tokenTheme === 'light') {
97
+ tooltipMessage = getColorMessage(
98
+ paletteColorTooltipMessages.light,
99
+ value.toUpperCase(),
100
+ );
101
+ }
102
+ }
103
+
104
+ return (
105
+ <Color
106
+ key={value}
107
+ value={value}
108
+ borderColor={border}
109
+ label={tooltipMessage ? formatMessage(tooltipMessage) : label}
110
+ onClick={onClick}
111
+ onKeyDown={onKeyDown}
112
+ isSelected={value === selectedColor}
113
+ checkMarkColor={getCheckMarkColor(value, useIconToken)}
114
+ hexToPaletteColor={hexToPaletteColor}
115
+ decorator={decorator}
116
+ />
117
+ );
118
+ })}
119
+ </Box>
120
+ ))}
121
+ </>
122
+ );
123
+ };
124
+
125
+ export default ColorPalette;
@@ -0,0 +1,96 @@
1
+ import { type ReactElement } from 'react';
2
+
3
+ import { type MessageDescriptor } from 'react-intl-next';
4
+
5
+ /**
6
+ * Represents a single color in the palette
7
+ */
8
+ export interface PaletteColor {
9
+ /** The color value (hex, token, etc.) */
10
+ value: string;
11
+ /** Display label for the color */
12
+ label: string;
13
+ /** Border color for the color swatch */
14
+ border: string;
15
+ /** Optional internationalization message */
16
+ message?: MessageDescriptor;
17
+ /** Optional decorator element to display instead of checkmark */
18
+ decorator?: ReactElement;
19
+ }
20
+
21
+ /**
22
+ * Array of palette colors
23
+ */
24
+ export type Palette = Array<PaletteColor>;
25
+
26
+ /**
27
+ * Tooltip messages for different themes
28
+ */
29
+ export type PaletteTooltipMessages = {
30
+ dark: Record<string, MessageDescriptor>;
31
+ light: Record<string, MessageDescriptor>;
32
+ };
33
+
34
+ /**
35
+ * Configuration options for the color palette
36
+ */
37
+ export interface PaletteOptions {
38
+ /** Array of colors to display */
39
+ palette: PaletteColor[];
40
+ /**
41
+ * Function to convert hex codes to design system tokens
42
+ * Different color palettes may use different mapping functions
43
+ */
44
+ hexToPaletteColor?: (hexColor: string) => string | undefined;
45
+ /**
46
+ * Tooltip messages for different color themes
47
+ * Consumer determines which tooltip messages to use
48
+ */
49
+ paletteColorTooltipMessages?: PaletteTooltipMessages;
50
+ }
51
+
52
+ /**
53
+ * Props for the main ColorPalette component
54
+ */
55
+ export interface ColorPaletteProps {
56
+ /** Currently selected color value */
57
+ selectedColor: string | null;
58
+ /** Callback when a color is clicked */
59
+ onClick: (value: string, label: string) => void;
60
+ /** Optional callback for keyboard navigation */
61
+ onKeyDown?: (value: string, label: string, event: React.KeyboardEvent) => void;
62
+ /** Number of columns in the palette grid */
63
+ cols?: number;
64
+ /** Optional CSS class name */
65
+ className?: string;
66
+ /** Palette configuration options */
67
+ paletteOptions: PaletteOptions;
68
+ }
69
+
70
+ /**
71
+ * Props for individual color palette items
72
+ */
73
+ export interface ColorProps {
74
+ /** The color value */
75
+ value: string;
76
+ /** Display label for accessibility */
77
+ label: string;
78
+ /** Tab index for keyboard navigation */
79
+ tabIndex?: number;
80
+ /** Whether this color is currently selected */
81
+ isSelected?: boolean;
82
+ /** Click handler */
83
+ onClick: (value: string, label: string) => void;
84
+ /** Keyboard event handler */
85
+ onKeyDown?: (value: string, label: string, event: React.KeyboardEvent) => void;
86
+ /** Border color for the swatch */
87
+ borderColor: string;
88
+ /** Color for the checkmark icon */
89
+ checkMarkColor?: string;
90
+ /** Whether to auto-focus this item */
91
+ autoFocus?: boolean;
92
+ /** Function to convert hex to palette color */
93
+ hexToPaletteColor?: (hexColor: string) => string | undefined;
94
+ /** Optional decorator element */
95
+ decorator?: ReactElement;
96
+ }
@@ -0,0 +1,102 @@
1
+ import type { PaletteColor } from './types';
2
+
3
+ /**
4
+ * Default number of columns in the color picker
5
+ */
6
+ export const DEFAULT_COLOR_PICKER_COLUMNS = 7;
7
+
8
+ /**
9
+ * Splits a palette array into rows based on the specified number of columns
10
+ * @param palette - Array of palette colors
11
+ * @param cols - Number of columns per row
12
+ * @returns Array of color rows
13
+ */
14
+ export function getColorsPerRowFromPalette(
15
+ palette: PaletteColor[],
16
+ cols: number = DEFAULT_COLOR_PICKER_COLUMNS,
17
+ ): PaletteColor[][] {
18
+ return palette.reduce((resultArray: PaletteColor[][], item: PaletteColor, index: number) => {
19
+ const chunkIndex = Math.floor(index / cols);
20
+
21
+ resultArray[chunkIndex] = resultArray[chunkIndex] || []; // start a new chunk
22
+ resultArray[chunkIndex].push(item);
23
+
24
+ return resultArray;
25
+ }, []);
26
+ }
27
+
28
+ /**
29
+ * Finds the row and column indices of a selected color in the palette grid
30
+ * @param colorsPerRow - 2D array of colors organized by rows
31
+ * @param selectedColor - The currently selected color value
32
+ * @returns Object containing row and column indices
33
+ */
34
+ export function getSelectedRowAndColumn(
35
+ colorsPerRow: PaletteColor[][],
36
+ selectedColor: string | null,
37
+ ) {
38
+ let selectedRowIndex = -1;
39
+ let selectedColumnIndex = -1;
40
+
41
+ colorsPerRow.forEach((row, rowIndex) => {
42
+ row.forEach(({ value }, columnIndex) => {
43
+ if (value === selectedColor) {
44
+ selectedRowIndex = rowIndex;
45
+ selectedColumnIndex = columnIndex;
46
+ }
47
+ });
48
+ });
49
+
50
+ return {
51
+ selectedRowIndex,
52
+ selectedColumnIndex,
53
+ };
54
+ }
55
+
56
+ /**
57
+ * Finds the row and column indices of a selected color in a flat palette array
58
+ * @param palette - Flat array of palette colors
59
+ * @param selectedColor - The currently selected color value
60
+ * @param cols - Number of columns per row
61
+ * @returns Object containing row and column indices
62
+ */
63
+ export function getSelectedRowAndColumnFromPalette(
64
+ palette: PaletteColor[],
65
+ selectedColor: string | null,
66
+ cols: number = DEFAULT_COLOR_PICKER_COLUMNS,
67
+ ) {
68
+ const colorsPerRow = getColorsPerRowFromPalette(palette, cols);
69
+ return getSelectedRowAndColumn(colorsPerRow, selectedColor);
70
+ }
71
+
72
+ /**
73
+ * Extracts the actual color value from a CSS variable expression
74
+ * Handles both token variables and fallback values
75
+ * @param variableExpression - CSS variable expression (e.g., "var(--ds-background-accent-blue-subtle, #0052CC)")
76
+ * @returns The resolved color value or empty string if not found
77
+ */
78
+ export const getTokenCSSVariableValue = (variableExpression: string): string => {
79
+ // Match CSS variable pattern: var(--variable-name, fallback)
80
+ // Ignored via go/ees005
81
+ // eslint-disable-next-line require-unicode-regexp
82
+ const matcher = variableExpression.match(/var\(([^,\)]+)(,.*)?/);
83
+ if (matcher) {
84
+ const variable = matcher[1].trim();
85
+ const fallback = matcher[2] ? matcher[2].replace(',', '').trim() : '';
86
+
87
+ // Return fallback if we're in a server environment
88
+ if (typeof document === 'undefined') {
89
+ return fallback;
90
+ }
91
+
92
+ // Get the computed value from the document
93
+ const value = window
94
+ .getComputedStyle(document.documentElement)
95
+ .getPropertyValue(variable)
96
+ .trim();
97
+
98
+ return value || fallback;
99
+ }
100
+
101
+ return '';
102
+ };
@@ -6,7 +6,6 @@ import { Pressable } from '@atlaskit/primitives/compiled';
6
6
  import { token } from '@atlaskit/tokens';
7
7
 
8
8
  import { useToolbarUI } from '../hooks/ui-context';
9
- import { type ToolbarButtonGroupLocation } from '../types';
10
9
 
11
10
  const styles = cssMap({
12
11
  button: {
@@ -50,28 +49,6 @@ const styles = cssMap({
50
49
  backgroundColor: token('color.background.selected.pressed'),
51
50
  },
52
51
  },
53
- groupStart: {
54
- borderTopRightRadius: '0px',
55
- borderBottomRightRadius: '0px',
56
- justifyContent: 'flex-end',
57
- paddingLeft: token('space.075'),
58
- paddingRight: token('space.025'),
59
- },
60
- groupMiddle: {
61
- borderTopLeftRadius: '0px',
62
- borderBottomLeftRadius: '0px',
63
- borderTopRightRadius: '0px',
64
- borderBottomRightRadius: '0px',
65
- paddingLeft: token('space.050'),
66
- paddingRight: token('space.050'),
67
- },
68
- groupEnd: {
69
- borderTopLeftRadius: '0px',
70
- borderBottomLeftRadius: '0px',
71
- justifyContent: 'flex-start',
72
- paddingLeft: token('space.025'),
73
- paddingRight: token('space.075'),
74
- },
75
52
  });
76
53
 
77
54
  type ToolbarButtonProps = Partial<TriggerProps> & {
@@ -82,7 +59,6 @@ type ToolbarButtonProps = Partial<TriggerProps> & {
82
59
  onFocus?: (event: React.FocusEvent<HTMLButtonElement>) => void;
83
60
  testId?: string;
84
61
  iconBefore: React.ReactNode;
85
- groupLocation?: ToolbarButtonGroupLocation;
86
62
  isDisabled?: boolean;
87
63
  ariaKeyshortcuts?: string;
88
64
  label?: string;
@@ -102,7 +78,6 @@ export const ToolbarButton = forwardRef(
102
78
  onBlur,
103
79
  onFocus,
104
80
  testId,
105
- groupLocation,
106
81
  isDisabled,
107
82
  ariaKeyshortcuts,
108
83
  label,
@@ -117,9 +92,6 @@ export const ToolbarButton = forwardRef(
117
92
  xcss={cx(
118
93
  styles.button,
119
94
  isDisabled ? styles.disabled : isSelected ? styles.selected : styles.enabled,
120
- groupLocation === 'start' && styles.groupStart,
121
- groupLocation === 'middle' && styles.groupMiddle,
122
- groupLocation === 'end' && styles.groupEnd,
123
95
  )}
124
96
  aria-pressed={isSelected}
125
97
  aria-expanded={ariaExpanded}