Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: pluralsh/design-system
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v3.69.2
Choose a base ref
...
head repository: pluralsh/design-system
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: main
Choose a head ref

Commits on Sep 30, 2024

  1. Copy the full SHA
    4397bd2 View commit details
  2. Copy the full SHA
    a3e5720 View commit details

Commits on Oct 2, 2024

  1. Copy the full SHA
    b353792 View commit details

Commits on Oct 4, 2024

  1. Copy the full SHA
    a185b03 View commit details
  2. Copy the full SHA
    e5dd70c View commit details

Commits on Oct 7, 2024

  1. Copy the full SHA
    a308e8d View commit details
  2. feat: upgrade storybook and vite (#651)

    Co-authored-by: Jake Laderman <jsladerman@gmail.com>
    floreks and jsladerman authored Oct 7, 2024
    Copy the full SHA
    9314941 View commit details
  3. Copy the full SHA
    07545a7 View commit details
  4. Copy the full SHA
    01f01a8 View commit details

Commits on Oct 8, 2024

  1. Copy the full SHA
    bcb96a8 View commit details
  2. Copy the full SHA
    22cc7ad View commit details

Commits on Oct 9, 2024

  1. Copy the full SHA
    60da505 View commit details

Commits on Oct 11, 2024

  1. Copy the full SHA
    1bcf8e1 View commit details

Commits on Oct 18, 2024

  1. Copy the full SHA
    e603004 View commit details

Commits on Oct 22, 2024

  1. Copy the full SHA
    1d0e141 View commit details

Commits on Oct 31, 2024

  1. Copy the full SHA
    20e8cc2 View commit details

Commits on Nov 6, 2024

  1. Copy the full SHA
    49069a7 View commit details

Commits on Nov 20, 2024

  1. Copy the full SHA
    fb05211 View commit details

Commits on Nov 21, 2024

  1. Copy the full SHA
    10258af View commit details
  2. Copy the full SHA
    737b0f9 View commit details

Commits on Nov 26, 2024

  1. Copy the full SHA
    9b66c98 View commit details

Commits on Nov 28, 2024

  1. Copy the full SHA
    74ef657 View commit details

Commits on Nov 29, 2024

  1. Copy the full SHA
    230efca View commit details
  2. Copy the full SHA
    663d763 View commit details

Commits on Dec 5, 2024

  1. Copy the full SHA
    dfe5fb4 View commit details

Commits on Dec 13, 2024

  1. feat: new icons (#670)

    jsladerman authored Dec 13, 2024
    Copy the full SHA
    97e007f View commit details

Commits on Dec 19, 2024

  1. Copy the full SHA
    ff257cf View commit details

Commits on Jan 4, 2025

  1. Copy the full SHA
    44d3c60 View commit details

Commits on Jan 6, 2025

  1. Copy the full SHA
    67f3d1b View commit details
Showing with 8,921 additions and 11,140 deletions.
  1. +13 −4 .storybook/main.ts
  2. +1 −1 .storybook/manager.ts
  3. +36 −8 .storybook/preview.ts
  4. +4 −4 .storybook/theme.ts
  5. +47 −40 package.json
  6. +10 −4 src/ThemeDecorator.tsx
  7. +25 −40 src/components/Accordion.tsx
  8. +22 −34 src/components/AppIcon.tsx
  9. +1 −0 src/components/AppList.tsx
  10. +7 −7 src/components/ArrowScroll.tsx
  11. +26 −29 src/components/Banner.tsx
  12. +30 −33 src/components/Breadcrumbs.tsx
  13. +1 −8 src/components/Button.tsx
  14. +141 −141 src/components/Callout.tsx
  15. +119 −63 src/components/Card.tsx
  16. +9 −18 src/components/Carousel.tsx
  17. +138 −0 src/components/CatalogCard.tsx
  18. +28 −32 src/components/Checkbox.tsx
  19. +2 −1 src/components/Checklist.tsx
  20. +1 −1 src/components/ChecklistFooter.tsx
  21. +5 −4 src/components/ChecklistItem.tsx
  22. +43 −44 src/components/Chip.tsx
  23. +36 −45 src/components/ChipList.tsx
  24. +50 −56 src/components/Code.tsx
  25. +7 −8 src/components/Codeline.tsx
  26. +6 −26 src/components/ComboBox.tsx
  27. +15 −17 src/components/ContentCard.tsx
  28. +4 −4 src/components/Date.tsx
  29. +1 −1 src/components/DateField.tsx
  30. +0 −169 src/components/DatePicker.tsx
  31. +6 −13 src/components/Divider.tsx
  32. +11 −18 src/components/EmptyState.tsx
  33. +68 −40 src/components/Flex.tsx
  34. +19 −27 src/components/Flyover.tsx
  35. +16 −26 src/components/FormField.tsx
  36. +3 −10 src/components/FormTitle.tsx
  37. +10 −9 src/components/Highlight.tsx
  38. +63 −71 src/components/IconFrame.tsx
  39. +16 −18 src/components/InlineCode.tsx
  40. +130 −129 src/components/Input.tsx
  41. +169 −175 src/components/Input2.tsx
  42. +48 −45 src/components/Layer.tsx
  43. +0 −2 src/components/LightDarkSwitch.tsx
  44. +10 −9 src/components/ListBox.tsx
  45. +35 −25 src/components/ListBoxItem.tsx
  46. +56 −56 src/components/ListBoxItemChipList.tsx
  47. +114 −107 src/components/LoadingSpinner.tsx
  48. +8 −10 src/components/LoopingLogo.tsx
  49. +78 −41 src/components/Markdown.tsx
  50. +10 −0 src/components/Menu.tsx
  51. +10 −0 src/components/MenuItem.tsx
  52. +22 −37 src/components/Modal.tsx
  53. +20 −16 src/components/ModalWrapper.tsx
  54. +17 −27 src/components/PageCard.tsx
  55. +12 −6 src/components/PageTitle.tsx
  56. +30 −37 src/components/Radio.tsx
  57. +18 −22 src/components/RadioGroup.tsx
  58. +1 −1 src/components/ReactAriaPopover.tsx
  59. +92 −82 src/components/RepositoryCard.tsx
  60. +10 −16 src/components/RepositoryChip.tsx
  61. +52 −59 src/components/Select.tsx
  62. +7 −8 src/components/SelectComboShared.tsx
  63. +44 −37 src/components/SelectItem.tsx
  64. +11 −10 src/components/SetInert.tsx
  65. +6 −11 src/components/Sidebar.tsx
  66. +1 −1 src/components/SidebarExpandWrapper.tsx
  67. +25 −33 src/components/SidebarItem.tsx
  68. +3 −10 src/components/SidebarSection.tsx
  69. +38 −23 src/components/Sidecar.tsx
  70. +1 −1 src/components/Slider.tsx
  71. +2 −4 src/components/Spinner.tsx
  72. +7 −8 src/components/StackCard.tsx
  73. +12 −16 src/components/Stepper.tsx
  74. +63 −58 src/components/SubTab.tsx
  75. +4 −6 src/components/Switch.tsx
  76. +34 −30 src/components/Tab.tsx
  77. +22 −29 src/components/TabList.tsx
  78. +65 −69 src/components/TabPanel.tsx
  79. +0 −888 src/components/Table.tsx
  80. +28 −38 src/components/TagMultiSelect.tsx
  81. +25 −38 src/components/TextSwitch.tsx
  82. +4 −9 src/components/TipCarousel.tsx
  83. +66 −62 src/components/Toast.tsx
  84. +12 −5 src/components/Tooltip.tsx
  85. +27 −58 src/components/TreeNavigation.tsx
  86. +10 −9 src/components/ValidatedInput.tsx
  87. +4 −2 src/components/contexts/BreadcrumbsContext.tsx
  88. +11 −4 src/components/contexts/ColorModeProvider.tsx
  89. +3 −11 src/components/contexts/NavigationContext.tsx
  90. +19 −0 src/components/icons/AiSparkleFilledIcon.tsx
  91. +17 −0 src/components/icons/AiSparkleOutlineIcon.tsx
  92. +53 −0 src/components/icons/CatalogIcon.tsx
  93. +16 −0 src/components/icons/ChatFilledIcon.tsx
  94. 0 src/components/icons/{ChatIcon.tsx → ChatOutlineIcon.tsx}
  95. +55 −0 src/components/icons/CollapseListIcon.tsx
  96. +28 −0 src/components/icons/CostManagementIcon.tsx
  97. +115 −0 src/components/icons/CpuIcon.tsx
  98. +3 −3 src/components/icons/EKSIcon.tsx
  99. +55 −0 src/components/icons/ExpandListIcon.tsx
  100. +0 −9 src/components/icons/FastReverseIcon.tsx
  101. +5 −5 src/components/icons/GKEIcon.tsx
  102. 0 src/components/icons/{PushPinIcon.tsx → PushPinFilledIcon.tsx}
  103. +30 −0 src/components/icons/PushPinOutlineIcon.tsx
  104. +48 −0 src/components/icons/RamIcon.tsx
  105. +17 −0 src/components/icons/ShrinkIcon.tsx
  106. +1 −1 src/components/icons/SourcererIcon.tsx
  107. +0 −17 src/components/icons/TerraformIcon.tsx
  108. +7 −8 src/components/icons/TerraformLogoIcon.tsx
  109. +0 −9 src/components/icons/ThumbsDownIcon.tsx
  110. +16 −0 src/components/icons/VirtualCluster.tsx
  111. +10 −14 src/components/icons/createIcon.tsx
  112. +1 −1 src/components/icons/logo/PluralLogoFull.tsx
  113. +2 −0 src/components/icons/logo/PluralLogoMark.tsx
  114. +2 −0 src/components/icons/logo/PluralLogoWord.tsx
  115. +1 −1 src/components/icons/plural-animated/PluralLogomarkBottomLeft.tsx
  116. +2 −0 src/components/icons/plural-animated/PluralLogomarkBottomRight.tsx
  117. +2 −0 src/components/icons/plural-animated/PluralLogomarkDot.tsx
  118. +2 −0 src/components/icons/plural-animated/PluralLogomarkTopLeft.tsx
  119. +1 −1 src/components/icons/plural-animated/PluralLogomarkTopRight.tsx
  120. +0 −94 src/components/pricingcalculator/PricingCalculator.tsx
  121. +0 −167 src/components/pricingcalculator/PricingCalculatorExtended.tsx
  122. +0 −34 src/components/pricingcalculator/controls/AppsControl.tsx
  123. +0 −37 src/components/pricingcalculator/controls/ClustersControl.tsx
  124. +0 −50 src/components/pricingcalculator/controls/Control.tsx
  125. +0 −65 src/components/pricingcalculator/controls/ProvidersControl.tsx
  126. +0 −55 src/components/pricingcalculator/controls/UsersControl.tsx
  127. +0 −40 src/components/pricingcalculator/costs/Cost.tsx
  128. +0 −33 src/components/pricingcalculator/costs/Costs.tsx
  129. +0 −63 src/components/pricingcalculator/costs/TotalCost.tsx
  130. +0 −160 src/components/pricingcalculator/misc.tsx
  131. +92 −0 src/components/table/FillerRows.tsx
  132. +29 −0 src/components/table/Skeleton.tsx
  133. +28 −0 src/components/table/SortIndicator.tsx
  134. +13 −0 src/components/table/T.tsx
  135. +540 −0 src/components/table/Table.tsx
  136. +16 −0 src/components/table/Tbody.tsx
  137. +90 −0 src/components/table/Td.tsx
  138. +78 −0 src/components/table/Th.tsx
  139. +19 −0 src/components/table/Thead.tsx
  140. +39 −0 src/components/table/Tr.tsx
  141. +89 −0 src/components/table/colors.ts
  142. +72 −0 src/components/table/hooks.ts
  143. +3 −1 src/components/wizard/Installer.tsx
  144. +1 −0 src/components/wizard/Picker.tsx
  145. +1 −1 src/components/wizard/Step.tsx
  146. +1 −1 src/components/wizard/Stepper.tsx
  147. +14 −19 src/components/wizard/Wizard.tsx
  148. +2 −2 src/hooks/useFloatingCornerScale.tsx
  149. +2 −2 src/hooks/useFloatingDropdown.tsx
  150. +1 −1 src/hooks/usePrevious.ts
  151. +2 −7 src/hooks/useRefResizeObserver.tsx
  152. +2 −2 src/hooks/useResizeObserver.ts
  153. +18 −9 src/icons.ts
  154. +37 −9 src/index.ts
  155. +6 −13 src/markdoc/components/Blockquote.tsx
  156. +9 −13 src/markdoc/components/Callout.tsx
  157. +4 −4 src/markdoc/components/Figure.tsx
  158. +7 −4 src/markdoc/components/List.tsx
  159. +3 −7 src/markdoc/components/Tab.tsx
  160. +1 −1 src/markdoc/components/Tabs.tsx
  161. +2 −2 src/markdoc/nodes/table.markdoc.ts
  162. +0 −11 src/markdoc/tags/calculator.markdoc.ts
  163. +0 −1 src/markdoc/tags/index.ts
  164. +5 −5 src/stories/Banner.stories.tsx
  165. +1 −1 src/stories/ButtonGroup.stories.tsx
  166. +46 −7 src/stories/Card.stories.tsx
  167. +64 −0 src/stories/CatalogCard.stories.tsx
  168. +6 −7 src/stories/Chip.stories.tsx
  169. +3 −3 src/stories/ClusterTagsTemplate.tsx
  170. +1 −1 src/stories/CodeEditor.stories.tsx
  171. +1 −1 src/stories/ComboBox.stories.tsx
  172. +0 −70 src/stories/DatePicker.stories.tsx
  173. +4 −0 src/stories/Markdown.stories.tsx
  174. +5 −18 src/stories/NavigationContextStub.tsx
  175. +0 −17 src/stories/PricingCalculator.stories.tsx
  176. +0 −20 src/stories/PricingCalculatorExtended.stories.tsx
  177. +28 −27 src/stories/Select.stories.tsx
  178. +1 −1 src/stories/Stepper.stories.tsx
  179. +53 −58 src/stories/TabList.stories.tsx
  180. +33 −4 src/stories/Table.stories.tsx
  181. +8 −1 src/stories/TagMultiselectTemplate.tsx
  182. +2 −2 src/stories/TextSwitch.stories.tsx
  183. +2 −1 src/stories/Tooltip.stories.tsx
  184. +1 −1 src/stories/Wizard.stories.tsx
  185. +11 −9 src/stories/_SemanticSystem.stories.tsx
  186. +29 −29 src/theme.tsx
  187. +3 −1 src/theme/boxShadows.ts
  188. +1 −0 src/theme/zIndexes.ts
  189. +2 −1 src/types/react-table.d.ts
  190. +5 −1 src/types/styled.d.ts
  191. +1 −1 src/utils/useBimodalSelectState.ts
  192. +39 −2 vite.config.ts
  193. +4,320 −6,420 yarn.lock
17 changes: 13 additions & 4 deletions .storybook/main.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
import { type StorybookConfig } from '@storybook/react-vite'

const config: StorybookConfig = {
stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
export default {
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],

addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-interactions',
],

core: {
builder: '@storybook/builder-vite',
},

framework: '@storybook/react-vite',
}

export default config
// TODO: Enable if we need autodocs. Causes a CJS warning in vite
// typescript: {
// reactDocgen: 'react-docgen-typescript',
// },
} as StorybookConfig
2 changes: 1 addition & 1 deletion .storybook/manager.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { addons } from '@storybook/addons'
import { addons } from '@storybook/manager-api'

import theme from './theme'

44 changes: 36 additions & 8 deletions .storybook/preview.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,73 @@
import * as jest from 'jest-mock'

import { type Preview } from '@storybook/react'
import { fn } from '@storybook/test'
import { themes } from '@storybook/theming'

import { DEFAULT_COLOR_MODE } from '../src/theme'
import themeDecorator from '../src/ThemeDecorator'
import { COLOR_MODES, DEFAULT_COLOR_MODE } from '../src/theme'

// @ts-expect-error
window.jest = jest
// Copied from https://github.com/storybookjs/storybook/blob/v8.2.5/code/core/src/theming/utils.ts
const { window: globalWindow } = global

export const getPreferredColorScheme = () => {
if (!globalWindow || !globalWindow.matchMedia) return 'light'

const isDarkThemePreferred = globalWindow.matchMedia(
'(prefers-color-scheme: dark)'
).matches

if (isDarkThemePreferred) return 'dark'

return 'light'
}

const preview: Preview = {
parameters: {
layout: 'fullscreen',
actions: { argTypesRegex: '^on[A-Z].*' },
actions: { onClick: fn },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
docs: {
theme: themes.dark,
},
options: {
storySort: {
order: ['Semantic System', '*'],
},
},
},

globalTypes: {
theme: {
name: 'Toggle theme',
description: 'Global theme for components',
defaultValue: DEFAULT_COLOR_MODE,
toolbar: {
// The label to show for this toolbar item
title: 'Theme',
icon: 'circlehollow',
// Array of plain string values or MenuItem shape (see below)
items: COLOR_MODES,
items: [
{ value: 'light', icon: 'circlehollow', title: 'Light' },
{ value: 'dark', icon: 'circle', title: 'Dark' },
],
// Change title based on selected value
dynamicTitle: true,
showName: true,
},
},
},

initialGlobals: {
theme: DEFAULT_COLOR_MODE,
},

decorators: [themeDecorator],

// TODO: Enable if we need autodocs
// tags: ['autodocs'],
}

export default preview
8 changes: 4 additions & 4 deletions .storybook/theme.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { create } from '@storybook/theming/create'
import { create } from '@storybook/theming'

export default create({
base: 'dark',
@@ -27,9 +27,9 @@ export default create({
textMutedColor: 'grey',

// Toolbar default and active colors
barTextColor: 'white',
barSelectedColor: '#293EFF',
barBg: '#2A2E37',
barTextColor: '#73828C',
barSelectedColor: '#73828C',
barBg: '#1E2229',

// Form colors
inputBg: '#1E2229',
87 changes: 47 additions & 40 deletions package.json
Original file line number Diff line number Diff line change
@@ -2,18 +2,26 @@
"name": "@pluralsh/design-system",
"version": "0.1.0",
"description": "Pluralsh Design System",
"main": "dist/index.js",
"main": "./dist/index.cjs.js",
"module": "./dist/index.es.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.es.js",
"require": "./dist/index.cjs.js",
"types": "./dist/index.d.ts"
}
},
"files": [
"dist/**/*",
"src/**/*"
"dist"
],
"scripts": {
"start": "storybook dev -p 6006 -s public",
"build:storybook": "yarn clean && storybook build -s public && yarn build:fix:storybook",
"start": "storybook dev -p 6006",
"build:storybook": "yarn build && storybook build && yarn build:fix:storybook",
"build:fix:storybook": "perl -pi -w -e 's/%40/@/g;' storybook-static/index.html",
"storybook:serve-static": "yarn build:storybook && http-server storybook-static",
"build": "npx tsc --declaration",
"build:watch": "npx tsc --declaration --watch",
"build": "rimraf dist && tsc --noEmit && vite build",
"build:watch": "rimraf dist && vite build --watch",
"clean": "rimraf storybook-static dist",
"test": "vitest --run",
"test:watch": "vitest",
@@ -41,20 +49,16 @@
"@react-hooks-library/core": "0.6.0",
"@react-spring/web": "9.7.3",
"@react-stately/utils": "3.9.0",
"@react-types/shared": "3.22.0",
"@tanstack/match-sorter-utils": "8.8.4",
"@tanstack/react-table": "8.10.7",
"@tanstack/react-table": "8.20.5",
"@tanstack/react-virtual": "3.0.1",
"@types/chroma-js": "2.4.3",
"@types/lodash-es": "4.17.12",
"babel-plugin-styled-components": "2.1.4",
"chroma-js": "2.4.2",
"classnames": "2.3.2",
"dayjs": "1.11.13",
"highlight.js": "11.9.0",
"honorable-recipe-mapper": "0.2.0",
"immer": "10.0.3",
"lodash-es": "4.17.21",
"moment": "2.29.4",
"react-animate-height": "3.2.3",
"react-aria": "3.34.3",
"react-embed": "3.7.0",
@@ -65,7 +69,6 @@
"rehype-raw": "7.0.0",
"remark-gfm": "4.0.0",
"resize-observer-polyfill": "1.5.1",
"styled-container-query": "1.3.5",
"type-fest": "4.8.3",
"use-immer": "0.9.0",
"usehooks-ts": "2.9.1"
@@ -77,34 +80,37 @@
"@emotion/react": "11.11.1",
"@emotion/styled": "11.11.0",
"@pluralsh/eslint-config-typescript": "2.5.147",
"@storybook/addon-actions": "7.6.5",
"@storybook/addon-docs": "7.6.5",
"@storybook/addon-essentials": "7.6.5",
"@storybook/addon-interactions": "7.6.5",
"@storybook/addon-links": "7.6.5",
"@storybook/addons": "7.6.17",
"@storybook/builder-vite": "7.6.5",
"@storybook/node-logger": "7.6.5",
"@storybook/react": "7.6.5",
"@storybook/react-vite": "7.6.5",
"@storybook/testing-library": "0.2.2",
"@react-types/shared": "3.22.0",
"@storybook/addon-actions": "8.3.5",
"@storybook/addon-docs": "8.3.5",
"@storybook/addon-essentials": "8.3.5",
"@storybook/addon-interactions": "8.3.5",
"@storybook/addon-links": "8.3.5",
"@storybook/builder-vite": "8.3.5",
"@storybook/node-logger": "8.3.5",
"@storybook/react": "8.3.5",
"@storybook/react-vite": "8.3.5",
"@storybook/theming": "8.3.5",
"@testing-library/jest-dom": "5.17.0",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@types/react-transition-group": "4.4.10",
"@types/styled-components": "5.1.30",
"@types/chroma-js": "2.4.3",
"@types/lodash-es": "4.17.12",
"@types/react": "19.0.2",
"@types/react-dom": "19.0.2",
"@types/react-transition-group": "4.4.12",
"@typescript-eslint/eslint-plugin": "6.14.0",
"@typescript-eslint/parser": "6.14.0",
"@vitejs/plugin-react": "4.3.2",
"@vitest/coverage-v8": "1.0.4",
"@vitest/ui": "1.0.4",
"babel-loader": "9.1.3",
"babel-plugin-styled-components": "2.1.4",
"conventional-changelog-conventionalcommits": "6.1.0",
"eslint": "8.55.0",
"eslint-config-prettier": "9.1.0",
"eslint-plugin-import": "2.29.1",
"eslint-plugin-import-newlines": "1.3.4",
"eslint-plugin-jsx-a11y": "6.8.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-prettier": "5.1.3",
"eslint-plugin-react": "7.33.2",
"eslint-plugin-react-hooks": "4.6.0",
"eslint-plugin-storybook": "0.6.15",
@@ -119,25 +125,26 @@
"lint-staged": "15.2.0",
"npm-run-all": "4.1.5",
"prettier": "3.0.3",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react": "19.0.0",
"react-dom": "19.0.0",
"react-transition-group": "4.4.5",
"rimraf": "5.0.5",
"storybook": "7.6.5",
"styled-components": "5.3.11",
"typescript": "^5.4.5",
"vite": "5.0.10",
"vitest": "1.0.4"
"storybook": "8.3.5",
"styled-components": "6.1.13",
"typescript": "5.6.2",
"vite": "5.4.8",
"vite-plugin-dts": "4.3.0",
"vitest": "2.1.2"
},
"peerDependencies": {
"@emotion/react": ">=11.11.0",
"@emotion/styled": ">=11.11.0",
"honorable": ">=1.0.0-beta.17",
"honorable-theme-default": ">=1.0.0-beta.5",
"react": ">=18.3.1",
"react-dom": ">=18.3.1",
"react": ">=19.0.0",
"react-dom": ">=19.0.0",
"react-transition-group": ">=4.4.5",
"styled-components": ">=5.3.11"
"styled-components": ">=6.1.13"
},
"packageManager": "yarn@3.3.1",
"resolutions": {
14 changes: 10 additions & 4 deletions src/ThemeDecorator.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { type ComponentType, useEffect } from 'react'
import { type ComponentType, type FC, useEffect } from 'react'
import {
CssBaseline,
Div,
ThemeProvider as HonorableThemeProvider,
type ThemeProviderProps,
} from 'honorable'
import { ThemeProvider as StyledThemeProvider } from 'styled-components'

@@ -16,6 +17,11 @@ import {
} from './theme'
import StyledCss from './GlobalStyle'

// workarounds for broken type from honorable
const TypedHonorableThemeProvider =
HonorableThemeProvider as FC<ThemeProviderProps>
const TypedCssBaseline = CssBaseline as any

function ThemeDecorator(Story: ComponentType, context: any) {
const colorMode = useThemeColorMode()

@@ -28,15 +34,15 @@ function ThemeDecorator(Story: ComponentType, context: any) {
const styledTheme = colorMode === 'light' ? styledThemeLight : styledThemeDark

return (
<HonorableThemeProvider theme={honorableTheme}>
<TypedHonorableThemeProvider theme={honorableTheme}>
<StyledThemeProvider theme={styledTheme}>
<CssBaseline />
<TypedCssBaseline />
<StyledCss />
<Div padding="xlarge">
<Story />
</Div>
</StyledThemeProvider>
</HonorableThemeProvider>
</TypedHonorableThemeProvider>
)
}

65 changes: 25 additions & 40 deletions src/components/Accordion.tsx
Original file line number Diff line number Diff line change
@@ -3,8 +3,6 @@ import {
type ComponentProps,
type ReactElement,
type ReactNode,
type Ref,
forwardRef,
useId,
} from 'react'

@@ -22,21 +20,17 @@ import Card from './Card'
export type AccordionProps = ComponentProps<typeof RadixAccordion.Root> &
ComponentProps<typeof Card>

function AccordionRef(
{
children,
...props
}: {
children?:
| ReactElement<typeof AccordionItem>
| ReactElement<typeof AccordionItem>[]
collapsible?: boolean
} & AccordionProps,
ref: Ref<HTMLDivElement>
) {
function Accordion({
children,
...props
}: {
children?:
| ReactElement<typeof AccordionItem>
| ReactElement<typeof AccordionItem>[]
collapsible?: boolean
} & AccordionProps) {
return (
<RadixAccordion.Root
ref={ref}
asChild
collapsible={props.collapsible ?? true}
{...props}
@@ -46,34 +40,28 @@ function AccordionRef(
)
}

function AccordionItemRef(
{
value,
padding = 'relaxed',
paddingArea = 'all',
caret = 'right',
trigger,
children,
...props
}: {
value?: string
padding?: 'none' | 'compact' | 'relaxed'
paddingArea?: 'trigger-only' | 'all'
caret?: 'none' | 'left' | 'right'
trigger: ReactNode
children: ReactNode
} & Omit<ComponentProps<typeof RadixAccordion.Item>, 'value'>,
ref: Ref<HTMLDivElement>
) {
function AccordionItem({
value,
padding = 'relaxed',
paddingArea = 'all',
caret = 'right',
trigger,
children,
...props
}: {
value?: string
padding?: 'none' | 'compact' | 'relaxed'
paddingArea?: 'trigger-only' | 'all'
caret?: 'none' | 'left' | 'right'
trigger: ReactNode
children: ReactNode
} & Omit<ComponentProps<typeof RadixAccordion.Item>, 'value'>) {
const theme = useTheme()
const paddingSize = getPaddingSize(theme, padding)
// if value is not provided, use a random persisted id
const defaultValue = useId()

return (
<ItemSC
// @ts-ignore, this is the sorta thing React 19 will be nice for
ref={ref}
value={value ?? defaultValue}
{...props}
>
@@ -208,8 +196,5 @@ const ContentSC = styled(RadixAccordion.Content)`
}
`

const Accordion = forwardRef(AccordionRef)
const AccordionItem = forwardRef(AccordionItemRef)

export default Accordion
export { AccordionItem }
56 changes: 22 additions & 34 deletions src/components/AppIcon.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
import {
type ComponentProps,
type ReactElement,
type Ref,
cloneElement,
forwardRef,
} from 'react'
import { type ComponentProps, type ReactElement, cloneElement } from 'react'
import { last } from 'lodash-es'

import styled, { type DefaultTheme, useTheme } from 'styled-components'
@@ -36,7 +30,7 @@ type AppIconProps = {
hue?: AppIconHue
clickable?: boolean
url?: string
icon?: ReactElement
icon?: ReactElement<any>
alt?: string
name?: string
initials?: string
@@ -52,7 +46,7 @@ const parentFillLevelToHue = {
} as const satisfies Record<FillLevel, AppIconHue>

const sizeToWidth = {
xxsmall: 32,
xxsmall: 36,
xsmall: 48,
small: 64,
medium: 96,
@@ -61,7 +55,7 @@ const sizeToWidth = {
} as const satisfies Record<AppIconSize, number>

const sizeToIconWidth = {
xxsmall: 16,
xxsmall: 20,
xsmall: 32,
small: 48,
medium: 64,
@@ -108,11 +102,11 @@ export function toInitials(name: string) {
}

const AppIconSC = styled.div<{
$color: ValueOf<typeof hueToColor>
$borderColor: ValueOf<typeof hueToBorderColor>
$hasBorder: boolean
$boxSize: number
$clickable: boolean
$color?: ValueOf<typeof hueToColor>
$borderColor?: ValueOf<typeof hueToBorderColor>
$hasBorder?: boolean
$boxSize?: number
$clickable?: boolean
}>(({ theme, $color, $borderColor, $hasBorder, $boxSize, $clickable }) => ({
display: 'flex',
alignItems: 'center',
@@ -151,22 +145,19 @@ const ImgSC = styled.img<{
objectFit: 'cover',
}))

function AppIconRef(
{
size = 'medium',
spacing = 'padding',
hue,
clickable = false,
url,
icon = null,
alt,
name,
initials,
onClose,
...props
}: AppIconProps & ComponentProps<typeof AppIconSC>,
ref: Ref<any>
) {
function AppIcon({
size = 'medium',
spacing = 'padding',
hue,
clickable = false,
url,
icon = null,
alt,
name,
initials,
onClose,
...props
}: AppIconProps & ComponentProps<typeof AppIconSC>) {
const theme = useTheme()
const parentFillLevel = useFillLevel()

@@ -198,7 +189,6 @@ function AppIconRef(
>
{url ? (
<ImgSC
ref={ref}
src={url}
alt={alt}
$iconWidth={iconWidth}
@@ -214,7 +204,5 @@ function AppIconRef(
)
}

const AppIcon = forwardRef(AppIconRef)

export default AppIcon
export type { AppIconProps }
1 change: 1 addition & 0 deletions src/components/AppList.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import styled from 'styled-components'
import {
type Dispatch,
type JSX,
type ReactNode,
createRef,
useCallback,
14 changes: 7 additions & 7 deletions src/components/ArrowScroll.tsx
Original file line number Diff line number Diff line change
@@ -22,10 +22,10 @@ const ComponentWrapperSC = styled.div({
},
})
const ArrowWrapperSC = styled.div<{
$direction: 'left' | 'right'
$direction?: 'left' | 'right'
}>(({ theme, $direction }) => ({
color: theme.colors['icon-light'],
zIndex: theme.zIndexes.modal,
zIndex: theme.zIndexes.modal - 1,
position: 'absolute',
top: 0,
bottom: 0,
@@ -116,11 +116,11 @@ function ArrowScroll({ children, ...props }: { children?: any }) {
onScroll: checkScroll,
ref: (node: HTMLElement) => {
containerRef.current = node
if (children.ref) {
if (typeof children.ref === 'function') {
children.ref(node)
} else if (children.ref) {
children.ref.current = node
if (children?.props?.ref) {
if (typeof children.props.ref === 'function') {
children.props.ref(node)
} else if (children.props.ref) {
children.props.ref.current = node
}
}
},
55 changes: 26 additions & 29 deletions src/components/Banner.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { type ReactNode, type Ref, forwardRef } from 'react'
import { Div, Flex, type FlexProps, Span, type SpanProps } from 'honorable'
import { type ReactNode } from 'react'
import styled from 'styled-components'

import { type ColorKey, type SeverityExt, sanitizeSeverity } from '../types'

import { FillLevelProvider } from './contexts/FillLevelContext'
import ErrorIcon from './icons/ErrorIcon'
import IconFrame from './IconFrame'
import CheckRoundedIcon from './icons/CheckRoundedIcon'
import CloseIcon from './icons/CloseIcon'
import type createIcon from './icons/createIcon'
import ErrorIcon from './icons/ErrorIcon'
import InfoIcon from './icons/InfoIcon'
import WarningIcon from './icons/WarningIcon'
import CheckRoundedIcon from './icons/CheckRoundedIcon'
import type createIcon from './icons/createIcon'
import IconFrame from './IconFrame'

export const BANNER_SEVERITIES = [
'info',
@@ -23,7 +23,7 @@ export const BANNER_SEVERITIES = [
type BannerSeverity = Extract<SeverityExt, (typeof BANNER_SEVERITIES)[number]>
const DEFAULT_SEVERITY: BannerSeverity = 'success'

type BannerProps = FlexProps & {
export type BannerProps = FlexProps & {
severity?: BannerSeverity | 'error'
heading?: ReactNode
action?: ReactNode
@@ -53,7 +53,7 @@ const severityToIcon: Record<BannerSeverity, ReturnType<typeof createIcon>> = {
success: CheckRoundedIcon,
}

const BannerOuter = styled.div<{
const BannerOuter: any = styled.div<{
$borderColorKey: ColorKey
$fullWidth?: boolean
}>(({ $borderColorKey, $fullWidth, theme }) => ({
@@ -110,27 +110,20 @@ const Content = styled.p<{ $hasHeading: boolean }>(
})
)

const CloseButton = styled(IconFrame).attrs({
size: 'medium',
clickable: true,
icon: <CloseIcon />,
})(({ theme }) => ({
const CloseButton = styled(IconFrame)(({ theme }) => ({
marginLeft: theme.spacing.medium,
}))

function BannerRef(
{
heading,
action,
actionProps,
children,
severity = 'success',
fullWidth = false,
onClose,
...props
}: BannerProps,
ref: Ref<any>
) {
function Banner({
heading,
action,
actionProps,
children,
severity = 'success',
fullWidth = false,
onClose,
...props
}: BannerProps) {
const finalSeverity = sanitizeSeverity(severity, {
allowList: BANNER_SEVERITIES,
default: DEFAULT_SEVERITY,
@@ -142,7 +135,6 @@ function BannerRef(

const content = (
<BannerOuter
ref={ref}
$borderColorKey={borderColorKey}
$fullWidth={fullWidth}
as={Flex}
@@ -167,13 +159,18 @@ function BannerRef(
</div>
</BannerInner>
<Div flexGrow={1} />
{typeof onClose === 'function' && <CloseButton onClick={onClose} />}
{typeof onClose === 'function' && (
<CloseButton
size="medium"
clickable
icon={<CloseIcon />}
onClick={onClose}
/>
)}
</BannerOuter>
)

return <FillLevelProvider value={3}>{content}</FillLevelProvider>
}

const Banner = forwardRef(BannerRef)

export default Banner
63 changes: 30 additions & 33 deletions src/components/Breadcrumbs.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {
type MutableRefObject,
type ComponentProps,
type ReactNode,
forwardRef,
type RefObject,
useCallback,
useContext,
useEffect,
@@ -103,15 +103,13 @@ const CrumbLinkText = styled.span(({ theme }) => ({
},
}))

const CrumbSelectTriggerUnstyled = forwardRef<any, any>(
(
{
className,
isOpen: _isOpen,
...props
}: { className?: string; isOpen?: boolean },
ref
) => (
function CrumbSelectTriggerUnstyled({
ref,
className,
isOpen: _isOpen,
...props
}: { className?: string; isOpen?: boolean } & ComponentProps<'div'>) {
return (
<div
className={className}
ref={ref}
@@ -120,7 +118,7 @@ const CrumbSelectTriggerUnstyled = forwardRef<any, any>(
...
</div>
)
)
}

const CrumbSelectTrigger = styled(CrumbSelectTriggerUnstyled)(({ theme }) => ({
...theme.partials.text.caption,
@@ -213,18 +211,17 @@ const CrumbListSC = styled.ol<{
: {}),
}))

function CrumbListRef(
{
breadcrumbs,
maxLength,
visibleListId,
}: {
breadcrumbs: Breadcrumb[]
maxLength: number
visibleListId?: string
},
ref: MutableRefObject<any>
) {
function CrumbList({
ref,
breadcrumbs,
maxLength,
visibleListId,
}: {
ref?: RefObject<any>
breadcrumbs: Breadcrumb[]
maxLength: number
visibleListId?: string
}) {
const id = useId()

if (breadcrumbs?.length < 1) {
@@ -281,8 +278,6 @@ function CrumbListRef(
)
}

const CrumbList = forwardRef(CrumbListRef)

const transitionStyles = {
entering: { opacity: 0, height: 0 },
entered: { opacity: 1 },
@@ -313,10 +308,10 @@ export function DynamicBreadcrumbs({
wrapperRef: transitionRef,
...props
}: BreadcrumbPropsBase & {
wrapperRef?: MutableRefObject<HTMLDivElement>
wrapperRef?: RefObject<HTMLDivElement>
style: any
}) {
const wrapperRef = useRef<HTMLDivElement | undefined>()
const wrapperRef = useRef<HTMLDivElement | undefined>(undefined)
const [visibleListId, setVisibleListId] = useState<string>('')
const children: ReactNode[] = []

@@ -341,9 +336,11 @@ export function DynamicBreadcrumbs({

const refitCrumbList = useCallback(
({ width: wrapperWidth }: { width: number }) => {
const lists = Array.from(
wrapperRef?.current?.querySelectorAll(`[${CRUMB_LIST_ATTR}]`)
)
const lists = wrapperRef?.current
? Array.from(
wrapperRef.current.querySelectorAll(`[${CRUMB_LIST_ATTR}]`)
)
: []
const { id } = lists.reduce(
(prev, next) => {
const prevWidth = prev.width
@@ -403,10 +400,10 @@ export function Breadcrumbs({
collapsible = true,
breadcrumbs: propsCrumbs,
...props
}: BreadcrumbsProps & NavProps) {
}: BreadcrumbsProps & Omit<NavProps, 'ref'>) {
const contextCrumbs = useContext(BreadcrumbsContext)?.breadcrumbs
const breadcrumbs = propsCrumbs || contextCrumbs
const nodeRef = useRef<HTMLDivElement>()
const nodeRef = useRef<HTMLDivElement>(undefined)

if (!breadcrumbs) {
throw Error(
9 changes: 1 addition & 8 deletions src/components/Button.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Button as HonorableButton } from 'honorable'
import type { ButtonProps as HonorableButtonProps } from 'honorable'
import { keyframes } from '@emotion/react'
import { type MutableRefObject, forwardRef } from 'react'

export type ButtonProps = HonorableButtonProps & { pulse?: boolean }

@@ -11,13 +10,9 @@ const pulseKeyframes = keyframes`
100% { box-shadow: 0 0 7px 2px #fff1; }
`

function ButtonRef(
{ pulse = false, ...props }: ButtonProps,
ref: MutableRefObject<any>
) {
function Button({ pulse = false, ...props }: ButtonProps) {
return (
<HonorableButton
ref={ref}
animationIterationCount="infinite"
animationDuration="4s"
animationName={pulse ? pulseKeyframes : undefined}
@@ -29,6 +24,4 @@ function ButtonRef(
)
}

const Button = forwardRef(ButtonRef)

export default Button
282 changes: 141 additions & 141 deletions src/components/Callout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import classNames from 'classnames'
import { type Dispatch, type PropsWithChildren, forwardRef, useId } from 'react'
import {
type Dispatch,
type PropsWithChildren,
type RefObject,
useId,
} from 'react'
import styled, { useTheme } from 'styled-components'

import { Flex } from 'honorable'
@@ -79,6 +84,7 @@ const sizeToIconSize: Record<CalloutSize, number> = {
}

export type CalloutProps = PropsWithChildren<{
ref?: RefObject<HTMLDivElement>
title?: string
severity?: CalloutSeverity
size?: CalloutSize
@@ -104,158 +110,152 @@ export function CalloutButton(props: ButtonProps) {
)
}

const Callout = forwardRef<HTMLDivElement, CalloutProps>(
(
{
title,
severity = DEFAULT_SEVERITY,
size = 'full',
expandable = false,
expanded: expandedProp,
defaultExpanded = false,
onExpand: onExpandProp,
closeable = false,
closed = false,
onClose,
fillLevel,
className,
buttonProps,
children,
id,
},
ref
) => {
if (expandable && closeable) {
throw new Error(
'Callout component cannot be expandable and closable at the same time'
)
}
const generatedId = useId()
function Callout({
ref,
title,
severity = DEFAULT_SEVERITY,
size = 'full',
expandable = false,
expanded: expandedProp,
defaultExpanded = false,
onExpand: onExpandProp,
closeable = false,
closed = false,
onClose,
fillLevel,
className,
buttonProps,
children,
id,
}: CalloutProps) {
if (expandable && closeable) {
throw new Error(
'Callout component cannot be expandable and closable at the same time'
)
}
const generatedId = useId()

id = id || generatedId
const {
triggerProps,
contentProps,
isOpen: expanded,
} = useDisclosure({
defaultOpen: defaultExpanded,
isOpen: expandedProp,
onOpenChange: onExpandProp,
id,
})
id = id || generatedId
const {
triggerProps,
contentProps,
isOpen: expanded,
} = useDisclosure({
defaultOpen: defaultExpanded,
isOpen: expandedProp,
onOpenChange: onExpandProp,
id,
})

severity = sanitizeSeverity(severity, {
default: DEFAULT_SEVERITY,
allowList: CALLOUT_SEVERITIES,
})
const theme = useTheme()
severity = sanitizeSeverity(severity, {
default: DEFAULT_SEVERITY,
allowList: CALLOUT_SEVERITIES,
})
const theme = useTheme()

const text = severityToText[severity]
const iconColor = theme.colors[severityToIconColorKey[severity]]
const borderColorKey = severityToBorderColorKey[severity]
const Icon = severityToIcon[severity]
const parentFillLevel = useFillLevel()
const text = severityToText[severity]
const iconColor = theme.colors[severityToIconColorKey[severity]]
const borderColorKey = severityToBorderColorKey[severity]
const Icon = severityToIcon[severity]
const parentFillLevel = useFillLevel()

fillLevel = toFillLevel(
Math.max(
2,
isFillLevel(fillLevel) && fillLevel >= 0
? fillLevel
: parentFillLevel + 1
)
fillLevel = toFillLevel(
Math.max(
2,
isFillLevel(fillLevel) && fillLevel >= 0 ? fillLevel : parentFillLevel + 1
)
)

let iconTopMargin = size === 'full' ? 0 : 2
let iconTopMargin = size === 'full' ? 0 : 2

if (title) {
iconTopMargin += 2
}
if (title) {
iconTopMargin += 2
}

if (closed) {
return null
}
if (closed) {
return null
}

return (
<FillLevelProvider value={fillLevel}>
<CalloutSC
className={`${className} ${classNames({ expandable })}`}
$borderColorKey={borderColorKey}
$fillLevel={fillLevel}
$size={size}
$expanded={expanded}
ref={ref}
{...(expandable && !expanded ? triggerProps : {})}
return (
<FillLevelProvider value={fillLevel}>
<CalloutSC
className={`${className} ${classNames({ expandable })}`}
$borderColorKey={borderColorKey}
$fillLevel={fillLevel}
$size={size}
$expanded={expanded}
ref={ref}
{...(expandable && !expanded ? triggerProps : {})}
>
<div className="icon">
<Icon
marginTop={iconTopMargin}
size={sizeToIconSize[size]}
color={iconColor}
display="flex"
/>
</div>
<div
className="content"
{...(expandable ? contentProps : {})}
>
<div className="icon">
<Icon
marginTop={iconTopMargin}
size={sizeToIconSize[size]}
color={iconColor}
display="flex"
/>
</div>
<div
className="content"
{...(expandable ? contentProps : {})}
<h6 className={classNames({ visuallyHidden: !title, expandable })}>
<span className="visuallyHidden">{`${text}: `}</span>
{title}
</h6>
<AnimateHeight
contentClassName={classNames('body', {
bodyWithTitle: !!title && !!children,
})}
duration={300}
height={
(expandable && expanded) || !expandable
? 'auto'
: size === 'compact'
? theme.spacing.xsmall
: theme.spacing.medium
}
>
<div className="children">{children}</div>
{buttonProps && (
<div className="buttonArea">
<CalloutButton {...buttonProps} />
</div>
)}
</AnimateHeight>
</div>
{(expandable || closeable) && (
<Flex
grow={1}
justify="flex-end"
>
<h6 className={classNames({ visuallyHidden: !title, expandable })}>
<span className="visuallyHidden">{`${text}: `}</span>
{title}
</h6>
<AnimateHeight
contentClassName={classNames('body', {
bodyWithTitle: !!title && !!children,
})}
duration={300}
height={
(expandable && expanded) || !expandable
? 'auto'
: size === 'compact'
? theme.spacing.xsmall
: theme.spacing.medium
<IconFrame
textValue=""
display="flex"
size="small"
clickable
{...(closeable && onClose
? {
onClick: () => {
onClose(!closed)
},
}
: {})}
{...(expandable && expanded ? triggerProps : {})}
icon={
expandable ? (
<CaretDownIcon className="expandIcon" />
) : (
<CloseIcon />
)
}
>
<div className="children">{children}</div>
{buttonProps && (
<div className="buttonArea">
<CalloutButton {...buttonProps} />
</div>
)}
</AnimateHeight>
</div>
{(expandable || closeable) && (
<Flex
grow={1}
justify="flex-end"
>
<IconFrame
textValue=""
display="flex"
size="small"
clickable
{...(closeable && onClose
? {
onClick: () => {
onClose(!closed)
},
}
: {})}
{...(expandable && expanded ? triggerProps : {})}
icon={
expandable ? (
<CaretDownIcon className="expandIcon" />
) : (
<CloseIcon />
)
}
/>
</Flex>
)}
</CalloutSC>
</FillLevelProvider>
)
}
)
/>
</Flex>
)}
</CalloutSC>
</FillLevelProvider>
)
}

const CalloutSC = styled.div<{
$borderColorKey: string
182 changes: 119 additions & 63 deletions src/components/Card.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
import chroma from 'chroma-js'
import { Div, type DivProps } from 'honorable'
import { forwardRef } from 'react'
import styled, { type DefaultTheme } from 'styled-components'
import { memoize } from 'lodash-es'
import { type ComponentProps, type ReactNode } from 'react'
import styled, { type DefaultTheme } from 'styled-components'

import { type Severity, type SeverityExt, sanitizeSeverity } from '../types'

import {
type FillLevel,
FillLevelProvider,
isFillLevel,
toFillLevel,
useFillLevel,
} from './contexts/FillLevelContext'

const HUES = ['default', 'lighter', 'lightest'] as const
import WrapWithIf from './WrapWithIf'

const CARD_SEVERITIES = [
'info',
@@ -27,19 +25,22 @@ const CARD_SEVERITIES = [

type CornerSize = 'medium' | 'large'
type CardFillLevel = Exclude<FillLevel, 0>
type CardHue = (typeof HUES)[number]
type CardSeverity = Extract<SeverityExt, (typeof CARD_SEVERITIES)[number]>

type BaseCardProps = {
/** @deprecated Colors set by `FillLevelContext`. If you need to override context, use `fillLevel` */
hue?: CardHue
/** Used to override a fill level set by `FillLevelContext` */
fillLevel?: FillLevel
cornerSize?: CornerSize
clickable?: boolean
disabled?: boolean
selected?: boolean
severity?: SeverityExt
header?: {
size?: 'medium' | 'large'
content?: ReactNode
headerProps?: ComponentProps<'div'>
outerProps?: ComponentProps<'div'>
}
}

type CardProps = DivProps & BaseCardProps
@@ -65,35 +66,21 @@ const fillToNeutralHoverBgC = {
3: 'fill-three-hover',
} as const satisfies Record<FillLevel, keyof DefaultTheme['colors']>

const hueToFill = {
default: 1,
lighter: 2,
lightest: 3,
} as const satisfies Record<CardHue, CardFillLevel>

const fillToNeutralSelectedBgC = {
0: 'fill-one-selected',
1: 'fill-one-selected',
2: 'fill-two-selected',
3: 'fill-three-selected',
} as const satisfies Record<FillLevel, keyof DefaultTheme['colors']>

export function useDecideFillLevel({
hue,
fillLevel,
}: {
hue?: CardHue
fillLevel?: number
}) {
export function useDecideFillLevel({ fillLevel }: { fillLevel?: number }) {
const parentFillLevel = useFillLevel()

if (isFillLevel(fillLevel)) {
return toFillLevel(Math.max(1, fillLevel)) as CardFillLevel
}

return isFillLevel(hueToFill[hue])
? hueToFill[hue]
: (toFillLevel(parentFillLevel + 1) as CardFillLevel)
return (
typeof fillLevel === 'number'
? toFillLevel(Math.max(1, fillLevel))
: toFillLevel(parentFillLevel + 1)
) as CardFillLevel
}

export const getFillToLightBgC = memoize(
@@ -151,26 +138,66 @@ const getBgColor = ({
return fillToLightBgC[severity][fillLevel]
}

const HeaderSC = styled.div<{
$fillLevel: CardFillLevel
$selected: boolean
$size: 'medium' | 'large'
$cornerSize: CornerSize
}>(
({
theme,
$fillLevel: fillLevel,
$selected: selected,
$size: size,
$cornerSize: cornerSize,
}) => ({
...theme.partials.text.overline,
flexShrink: 0,
display: 'flex',
alignItems: 'center',
color: theme.colors['text-xlight'],
border: `1px solid ${theme.colors[fillToNeutralBorderC[fillLevel]]}`,
borderBottom: 'none',
borderRadius: `${theme.borderRadiuses[cornerSize]}px ${theme.borderRadiuses[cornerSize]}px 0 0`,
backgroundColor: selected
? theme.colors[fillToNeutralSelectedBgC[fillLevel]]
: getBgColor({ theme, fillLevel }),
height: size === 'large' ? 48 : 40,
padding: `0 ${theme.spacing.medium}px`,
overflow: 'hidden',
})
)

const CardSC = styled(Div)<{
$hasHeader: boolean
$fillLevel: CardFillLevel
$cornerSize: CornerSize
$severity: Severity
$selected: boolean
$clickable: boolean
disabled: boolean
$disabled: boolean
}>(
({
theme,
$hasHeader,
$fillLevel: fillLevel,
$cornerSize: cornerSize,
$severity: severity,
$selected: selected,
$clickable: clickable,
disabled,
$disabled: disabled,
}) => ({
...theme.partials.reset.button,
border: `1px solid ${theme.colors[fillToNeutralBorderC[fillLevel]]}`,
borderRadius: theme.borderRadiuses[cornerSize],
border: `1px solid ${
theme.colors[
fillToNeutralBorderC[
$hasHeader ? toFillLevel(fillLevel + 1) : fillLevel
]
]
}`,
borderRadius: $hasHeader
? `0 0 ${theme.borderRadiuses[cornerSize]}px ${theme.borderRadiuses[cornerSize]}px`
: theme.borderRadiuses[cornerSize],
backgroundColor: selected
? theme.colors[fillToNeutralSelectedBgC[fillLevel]]
: getBgColor({ theme, fillLevel }),
@@ -188,55 +215,84 @@ const CardSC = styled(Div)<{
!disabled &&
!selected &&
severity === 'neutral' && {
':hover': {
'&:hover': {
backgroundColor: theme.colors[fillToNeutralHoverBgC[fillLevel]],
},
}),
...theme.partials.scrollBar({ fillLevel }),
})
)

const Card = forwardRef(
(
{
cornerSize = 'large',
hue, // Deprecated, prefer fillLevel
severity = 'neutral',
fillLevel,
selected = false,
clickable = false,
disabled = false,
...props
}: CardProps,
ref
) => {
fillLevel = useDecideFillLevel({ hue, fillLevel })
const cardSeverity = sanitizeSeverity(severity, {
allowList: CARD_SEVERITIES,
default: 'neutral',
})

return (
<FillLevelProvider value={fillLevel}>
const OuterWrapSC = styled.div({
display: 'flex',
flexDirection: 'column',
overflow: 'hidden',
width: '100%',
height: '100%',
})

function Card({
ref,
header,
cornerSize = 'large',
severity = 'neutral',
fillLevel,
selected = false,
clickable = false,
disabled = false,
children,
...props
}: CardProps) {
const hasHeader = !!header
const { size, content: headerContent, headerProps, outerProps } = header ?? {}

const mainFillLevel = useDecideFillLevel({ fillLevel })
const headerFillLevel = useDecideFillLevel({ fillLevel: mainFillLevel + 1 })

const cardSeverity = sanitizeSeverity(severity, {
allowList: CARD_SEVERITIES,
default: 'neutral',
})

return (
<FillLevelProvider value={mainFillLevel}>
<WrapWithIf
condition={hasHeader}
wrapper={<OuterWrapSC {...outerProps} />}
>
{header && (
<HeaderSC
$fillLevel={headerFillLevel}
$selected={selected}
$size={size}
$cornerSize={cornerSize}
{...headerProps}
>
{headerContent}
</HeaderSC>
)}
<CardSC
ref={ref}
$cornerSize={cornerSize}
$fillLevel={fillLevel}
$fillLevel={mainFillLevel}
$severity={cardSeverity}
$selected={selected}
$clickable={clickable}
$hasHeader={hasHeader}
{...(clickable && {
forwardedAs: 'button',
type: 'button',
'data-clickable': 'true',
})}
disabled={clickable && disabled}
$disabled={clickable && disabled}
{...props}
/>
</FillLevelProvider>
)
}
)
>
{children}
</CardSC>
</WrapWithIf>
</FillLevelProvider>
)
}

export default Card
export type { BaseCardProps, CardProps, CornerSize, CardHue, CardFillLevel }
export type { BaseCardProps, CardFillLevel, CardProps, CornerSize }
27 changes: 9 additions & 18 deletions src/components/Carousel.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
import { keyframes } from '@emotion/react'
import { Div, type DivProps, Flex } from 'honorable'
import {
Children,
type ReactElement,
type Ref,
forwardRef,
useEffect,
useState,
} from 'react'
import { Children, type ReactElement, useEffect, useState } from 'react'
import { CSSTransition } from 'react-transition-group'
import { keyframes } from '@emotion/react'

type DotProps = DivProps & {
active?: boolean
@@ -93,10 +86,11 @@ const transitionStyles = {
},
}

function CarouselRef(
{ autoAdvanceTime = 10000, children, ...props }: CarouselProps,
ref: Ref<any>
) {
function Carousel({
autoAdvanceTime = 10000,
children,
...props
}: CarouselProps) {
const [activeIndex, setActiveIndex] = useState(0)

useEffect(() => {
@@ -115,14 +109,13 @@ function CarouselRef(

return (
<Div
ref={ref}
backgroundColor="fill-one"
border="1px solid border"
borderRadius="medium"
{...props}
>
<Flex overflow="hidden">
{Children.map(children, (child: ReactElement, i: number) => (
{Children.map(children, (child: ReactElement<any>, i: number) => (
<Flex
width="100%"
flexShrink={0}
@@ -152,7 +145,7 @@ function CarouselRef(
marginBottom="medium"
justifyContent="center"
>
{Children.map(children, (_child: ReactElement, i: number) => (
{Children.map(children, (_child: ReactElement<any>, i: number) => (
<Dot
active={activeIndex === i}
onClick={() => {
@@ -165,6 +158,4 @@ function CarouselRef(
)
}

const Carousel = forwardRef(CarouselRef)

export default Carousel
138 changes: 138 additions & 0 deletions src/components/CatalogCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import { useTheme } from 'styled-components'

import { PrQueueIcon } from '../icons'

import Chip from './Chip'
import Card, { type CardProps } from './Card'
import AppIcon from './AppIcon'
import ChipList from './ChipList'
import Flex from './Flex'

type CatalogCardProps = CardProps & {
name?: string
author?: string
category?: string
description?: string
imageUrl?: string
tags?: string[]
}

function CatalogCard({
name,
author,
category,
description,
imageUrl,
tags = [],
...props
}: CatalogCardProps) {
const theme = useTheme()

return (
<Card
clickable
style={{
flexDirection: 'column',
padding: theme.spacing.medium,
maxWidth: 456,
minWidth: 256,
width: '100%',
}}
{...props}
>
<Flex
height="100%"
align="flex-start"
gap="large"
>
<Flex
flexGrow={1}
direction="column"
height="100%"
overflow="hidden"
>
<Flex align="center">
<AppIcon
size="xxsmall"
url={imageUrl}
icon={<PrQueueIcon />}
/>
<Flex
direction="row"
marginLeft={theme.spacing.small}
width="100%"
align="flex-start"
justify="space-between"
>
<Flex direction="column">
<div
style={{
...theme.partials.text.body2Bold,
color: theme.colors.text,
}}
>
{name}
</div>
<div
style={{
...theme.partials.text.caption,
color: theme.colors['text-xlight'],
}}
>
by {author}
</div>
</Flex>
</Flex>
</Flex>
{description && (
<p
style={{
...theme.partials.text.body2,
margin: 0,
marginTop: theme.spacing.small,
color: theme.colors['text-light'],
display: '-webkit-box',
WebkitLineClamp: '2',
WebkitBoxOrient: 'vertical',
overflow: 'hidden',
}}
>
{description}
</p>
)}
<div style={{ flexGrow: 1 }} />
{(category || tags?.length > 0) && (
<Flex
alignItems="start"
gap="small"
justifyContent={category ? 'space-between' : 'end'}
marginTop={theme.spacing.medium}
>
{!!category && (
<Chip
size="small"
border="none"
fillLevel={3}
truncateWidth={70}
>
{category}
</Chip>
)}
<ChipList
size="small"
border="none"
fillLevel={3}
values={tags ?? []}
limit={1}
truncateWidth={70}
emptyState={null}
/>
</Flex>
)}
</Flex>
</Flex>
</Card>
)
}

export default CatalogCard
60 changes: 28 additions & 32 deletions src/components/Checkbox.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { type MutableRefObject, forwardRef, memo, useId, useRef } from 'react'
import { type InputProps, Label } from 'honorable'
import classNames from 'classnames'
import styled from 'styled-components'
import { useToggleState } from 'react-stately'
import { type InputProps, Label } from 'honorable'
import { memo, useId, useRef } from 'react'
import { VisuallyHidden, useCheckbox, useFocusRing } from 'react-aria'
import { useToggleState } from 'react-stately'
import styled from 'styled-components'

const CheckedIcon = memo(({ small }: { small: boolean }) => {
const width = small ? 16 : 24
@@ -68,7 +68,7 @@ const HonorableLabelStyled = styled(Label)<{
: theme.colors['text-light'],
cursor: $disabled ? 'not-allowed' : 'pointer',
margin: 0,
':focus': {
'&:focus': {
outline: 'none',
},
'.box': {
@@ -78,19 +78,19 @@ const HonorableLabelStyled = styled(Label)<{
...($isFocusVisible
? { ...theme.partials.focus.outline, border: 'none' }
: {}),
'::before, .icon': {
'&::before, .icon': {
position: 'absolute',
content: '""',
top: 0,
left: 0,
width: '100%',
height: '100%',
},
'::before, &': {
'&::before, &': {
borderRadius: theme.borderRadiuses.medium,
},
/* before for the border */
'::before': {
'&::before': {
zIndex: 0,
border: theme.borders.input,
...($disabled
@@ -114,7 +114,7 @@ const HonorableLabelStyled = styled(Label)<{
},
...(!$disabled
? {
':hover': {
'&:hover': {
color: theme.colors.text,
'.box': {
backgroundColor: theme.colors['action-input-hover'],
@@ -129,7 +129,7 @@ const HonorableLabelStyled = styled(Label)<{
backgroundColor: theme.colors['action-primary'],
},
},
':hover.checked, :hover.indeterminate': {
'&:hover.checked, &:hover.indeterminate': {
'.box::before': {
border: 'none',
backgroundColor: theme.colors['action-primary-hover'],
@@ -148,34 +148,31 @@ export type CheckboxProps = {
defaultSelected?: boolean
onChange?: (e: { target: { checked: boolean } }) => any
onFocusChange?: (isFocused: boolean) => void
tabIndex?: number | string
tabIndex?: number
} & Omit<InputProps, 'onChange'>

function Checkbox(
{
small,
onChange,
checked: checkedProp,
indeterminate,
disabled,
defaultSelected,
onFocus,
onBlur,
onFocusChange,
onKeyDown,
onKeyUp,
tabIndex,
...props
}: CheckboxProps,
ref: MutableRefObject<any>
) {
function Checkbox({
small,
onChange,
checked: checkedProp,
indeterminate,
disabled,
defaultSelected,
onFocus,
onBlur,
onFocusChange,
onKeyDown,
onKeyUp,
tabIndex,
...props
}: CheckboxProps) {
const toggleStateProps = {
...(checkedProp !== undefined ? { isSelected: checkedProp } : {}),
defaultSelected: !!defaultSelected,
}
const labelId = useId()
const toggleState = useToggleState(toggleStateProps)
const inputRef = useRef<any>()
const inputRef = useRef<any>(undefined)
const { isFocusVisible, focusProps } = useFocusRing()
const { inputProps } = useCheckbox(
{
@@ -204,7 +201,6 @@ function Checkbox(
<HonorableLabelStyled
htmlFor={inputProps.id}
id={labelId}
ref={ref}
className={classNames({
checked: toggleState.isSelected,
indeterminate,
@@ -238,4 +234,4 @@ function Checkbox(
)
}

export default forwardRef(Checkbox)
export default Checkbox
3 changes: 2 additions & 1 deletion src/components/Checklist.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
type ComponentPropsWithRef,
type Dispatch,
type JSX,
type ReactElement,
useCallback,
useEffect,
@@ -79,7 +80,7 @@ type ChecklistProps = ComponentPropsWithRef<'div'> & {
footerChildren:
| ReactElement<ChecklistFooterProps>
| ReactElement<ChecklistFooterProps>[]
completeChildren: ReactElement
completeChildren: ReactElement<any>
children: ReactElement<ChecklistItemProps>[]
}

2 changes: 1 addition & 1 deletion src/components/ChecklistFooter.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type ComponentPropsWithRef } from 'react'
import { type ComponentPropsWithRef, type JSX } from 'react'
import styled from 'styled-components'

const ChecklistFooter = styled(ChecklistFooterUnstyled)(({ theme }) => ({
9 changes: 5 additions & 4 deletions src/components/ChecklistItem.tsx
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@ import { useKeyboard } from 'react-aria'
import {
type ComponentPropsWithRef,
type Dispatch,
type JSX,
type ReactElement,
useEffect,
useRef,
@@ -27,10 +28,10 @@ const ChecklistItemInner = styled(ChecklistItemInnerUnstyled)(
alignItems: 'center',
color: selected ? theme.colors.text : theme.colors['text-light'],
cursor: 'pointer',
':hover': {
'&:hover': {
background: theme.colors['fill-two-hover'],
},
':focus': {
'&:focus': {
outline: `${theme.colors['border-outline-focused']} solid 1px`,
},

@@ -145,7 +146,7 @@ const ChecklistItemInner = styled(ChecklistItemInnerUnstyled)(
)

type ChecklistItemProps = ComponentPropsWithRef<'div'> & {
children?: ReactElement | ReactElement[] | string
children?: ReactElement<any> | ReactElement<any>[] | string
title: string
}

@@ -183,7 +184,7 @@ function ChecklistItemInnerUnstyled({
onFocusChange,
...props
}: ChecklistItemInnerProps): JSX.Element {
const headerRef = useRef<HTMLDivElement>()
const headerRef = useRef<HTMLDivElement>(undefined)
const { keyboardProps } = useKeyboard({
onKeyDown: (e) => {
switch (e.key) {
87 changes: 43 additions & 44 deletions src/components/Chip.tsx
Original file line number Diff line number Diff line change
@@ -1,42 +1,38 @@
import { type FlexProps } from 'honorable'
import {
type ComponentProps,
type ComponentPropsWithRef,
type ReactElement,
type Ref,
forwardRef,
} from 'react'
import styled, {
type DefaultTheme,
type StyledComponent,
useTheme,
} from 'styled-components'
import styled, { type DefaultTheme, useTheme } from 'styled-components'

import { type SEVERITIES } from '../types'

import { Spinner } from './Spinner'
import Card, {
type BaseCardProps,
type CardFillLevel,
useDecideFillLevel,
} from './Card'
import CloseIcon from './icons/CloseIcon'
import { Spinner } from './Spinner'
import Tooltip from './Tooltip'

export const CHIP_CLOSE_ATTR_KEY = 'data-close-button' as const
const SIZES = ['small', 'medium', 'large'] as const

type ChipSize = (typeof SIZES)[number]
type ChipSeverity = (typeof SEVERITIES)[number]
export type ChipSize = (typeof SIZES)[number]
export type ChipSeverity = (typeof SEVERITIES)[number]

export type ChipProps = Omit<FlexProps, 'size'> &
BaseCardProps & {
size?: ChipSize
condensed?: boolean
severity?: ChipSeverity
icon?: ReactElement
inactive?: boolean
icon?: ReactElement<any>
loading?: boolean
closeButton?: boolean
closeButtonProps?: ComponentProps<StyledComponent<'button', DefaultTheme>>
closeButtonProps?: ComponentPropsWithRef<'div'>
clickable?: boolean
truncateWidth?: number
truncateEdge?: 'start' | 'end'
@@ -76,15 +72,26 @@ const sizeToCloseHeight = {
const ChipCardSC = styled(Card)<{
$size: ChipSize
$severity: ChipSeverity
$inactive: boolean
$truncateWidth?: number
$truncateEdge?: 'start' | 'end'
$condensed?: boolean
}>(({ $size, $severity, $truncateWidth, $truncateEdge, $condensed, theme }) => {
const textColor =
theme.colors[severityToColor[$severity]] || theme.colors.text
}>(({
$size,
$severity,
$inactive,
$truncateWidth,
$truncateEdge,
$condensed,
theme,
}) => {
const textColor = $inactive
? theme.colors['text-xlight']
: theme.colors[severityToColor[$severity]] ?? theme.colors.text

return {
'&&': {
backgroundColor: $inactive ? 'transparent' : undefined,
padding: `${$size === 'large' ? 6 : theme.spacing.xxxsmall}px ${
$size === 'large' && $condensed
? 6
@@ -161,48 +168,43 @@ const CloseButtonSC = styled.button<{
},
}))

function ChipRef(
{
children,
size = 'medium',
condensed = false,
severity = 'neutral',
truncateWidth,
truncateEdge,
hue,
fillLevel,
loading = false,
icon,
closeButton,
closeButtonProps,
clickable,
disabled,
tooltip,
tooltipProps,
as,
...props
}: ChipProps & { as?: ComponentProps<typeof ChipCardSC>['forwardedAs'] },
ref: Ref<any>
) {
fillLevel = useDecideFillLevel({ hue, fillLevel })
function Chip({
children,
size = 'medium',
condensed = false,
severity = 'neutral',
inactive = false,
truncateWidth,
truncateEdge,
fillLevel,
loading = false,
icon,
closeButton,
closeButtonProps,
clickable,
disabled,
tooltip,
tooltipProps,
...props
}: ChipProps) {
fillLevel = useDecideFillLevel({ fillLevel })
const theme = useTheme()

const iconCol = severityToIconColor[severity] || 'icon-default'

let content = (
<ChipCardSC
ref={ref}
severity={severity}
cornerSize="medium"
fillLevel={fillLevel}
clickable={clickable}
disabled={clickable && disabled}
$inactive={inactive}
$size={size}
$condensed={condensed}
$severity={severity}
$truncateWidth={truncateWidth}
$truncateEdge={truncateEdge}
{...(as ? { forwardedAs: as } : {})}
{...props}
>
{loading && (
@@ -224,7 +226,6 @@ function ChipRef(
<CloseButtonSC
disabled={disabled}
$fillLevel={fillLevel}
$severity={severity}
{...{
[CHIP_CLOSE_ATTR_KEY]: '',
}}
@@ -257,6 +258,4 @@ function ChipRef(
return content
}

const Chip = forwardRef(ChipRef)

export default Chip
81 changes: 36 additions & 45 deletions src/components/ChipList.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import { Flex, Span } from 'honorable'
import isEmpty from 'lodash-es/isEmpty'
import {
type ComponentProps,
type Dispatch,
type JSX,
type ReactElement,
useState,
useCallback,
} from 'react'

import { HamburgerMenuCollapseIcon } from '../icons'

import Chip, { type ChipProps } from './Chip'
import Flex from './Flex'

type TransformFn<TValue> = (
value: TValue
@@ -32,55 +31,47 @@ function ChipList<TValue = string>({
onClickCondition,
onClick,
...props
}: ChipListProps<TValue>): ReactElement {
const [collapsed, setCollapsed] = useState(true)
}: ChipListProps<TValue>): ReactElement<any> {
const chip = useCallback(
(v: TValue, i: number) => {
const clickable = onClickCondition?.(v) ?? false

return (
<Chip
key={(v as any).key || i}
clickable={clickable}
onClick={() => clickable && onClick(v)}
{...props}
>
{transformValue ? transformValue(v) : `${v}`}
</Chip>
)
},
[onClick, onClickCondition, props, transformValue]
)

return (
<Flex
gap="xsmall"
wrap
wrap="wrap"
>
{isEmpty(values) &&
(emptyState !== undefined ? (
emptyState
) : (
<Span body2>There is nothing to display here.</Span>
))}
{values.slice(0, collapsed ? limit : undefined).map((v, i) => {
const clickable = onClickCondition?.(v) ?? false

return (
<Chip
key={(v as any).key || i}
clickable={clickable}
onClick={() => clickable && onClick(v)}
{...props}
>
{transformValue ? transformValue(v) : `${v}`}
</Chip>
)
})}
(emptyState !== undefined
? emptyState
: 'There is nothing to display here.')}
{values.slice(0, limit).map(chip)}
{values.length > limit && (
<>
{collapsed && (
<Chip
onClick={() => setCollapsed(false)}
{...props}
clickable
>
{`+${values.length - limit}`}
</Chip>
)}
{!collapsed && (
<Chip
onClick={() => setCollapsed(true)}
{...props}
clickable
<Chip
{...props}
tooltip={
<Flex
gap="xsmall"
wrap="wrap"
>
<HamburgerMenuCollapseIcon />
</Chip>
)}
</>
{values.slice(limit, values.length).map(chip)}
</Flex>
}
>{`+${values.length - limit}`}</Chip>
)}
</Flex>
)
106 changes: 50 additions & 56 deletions src/components/Code.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,38 @@
import { Button, Div, Flex } from 'honorable'
import {
type ComponentProps,
type MutableRefObject,
type PropsWithChildren,
type ReactNode,
type RefObject,
createContext,
forwardRef,
useCallback,
useContext,
useEffect,
useMemo,
useRef,
useState,
} from 'react'
import { Button, Div, Flex } from 'honorable'
import styled, { useTheme } from 'styled-components'

import useResizeObserver from '../hooks/useResizeObserver'

import CopyIcon from './icons/CopyIcon'
import Card, { type CardProps } from './Card'
import CheckIcon from './icons/CheckIcon'
import Highlight from './Highlight'
import { ListBoxItem } from './ListBoxItem'
import { Select } from './Select'
import SubTab from './SubTab'
import { TabList, type TabListStateProps } from './TabList'
import TabPanel from './TabPanel'
import {
type FillLevel,
FillLevelProvider,
toFillLevel,
useFillLevel,
} from './contexts/FillLevelContext'
import FileIcon from './icons/FileIcon'
import { TabList, type TabListStateProps } from './TabList'
import { SubTab } from './SubTab'
import TabPanel from './TabPanel'
import { Select } from './Select'
import { ListBoxItem } from './ListBoxItem'
import CheckIcon from './icons/CheckIcon'
import CopyIcon from './icons/CopyIcon'
import DropdownArrowIcon from './icons/DropdownArrowIcon'
import FileIcon from './icons/FileIcon'

type CodeProps = Omit<CardProps, 'children'> & {
children?: string
@@ -51,7 +49,7 @@ type TabInterfaceT = 'tabs' | 'dropdown'
type TabsContext = {
tabInterface: TabInterfaceT
setTabInterface: (arg: TabInterfaceT) => void
tabStateRef?: MutableRefObject<any>
tabStateRef?: RefObject<any>
selectedKey?: string
onSelectionChange?: any
} & Pick<CodeProps, 'tabs'>
@@ -124,12 +122,12 @@ function CopyButtonBase({
</Button>
)
}
const CopyButton = styled(CopyButtonBase)<{ verticallyCenter: boolean }>(
({ verticallyCenter, theme }) => ({
const CopyButton = styled(CopyButtonBase)<{ $verticallyCenter: boolean }>(
({ $verticallyCenter, theme }) => ({
position: 'absolute',
right: theme.spacing.medium,
top: verticallyCenter ? '50%' : theme.spacing.medium,
transform: verticallyCenter ? 'translateY(-50%)' : 'none',
top: $verticallyCenter ? '50%' : theme.spacing.medium,
transform: $verticallyCenter ? 'translateY(-50%)' : 'none',
boxShadow: theme.boxShadows.slight,
})
)
@@ -163,28 +161,26 @@ const TabsWrap = styled.div<{ $isDisabled: boolean }>(
})
)

const TabsDropdownButton = styled(
forwardRef<any, any>((props, ref) => {
const fillLevel = useFillLevel()
const theme = useTheme()

return (
<Button
ref={ref}
small
tertiary
endIcon={<DropdownArrowIcon className="dropdownIcon" />}
{...{
'&, &:hover, &:focus, &:focus-visible': {
backgroundColor:
theme.colors[`fill-${fillLevel > 2 ? 'three' : 'two'}-selected`],
},
}}
{...props}
/>
)
})
)<{ isOpen?: boolean }>(({ isOpen = false, theme }) => ({
const TabsDropdownButton = styled(({ ref, ...props }) => {
const fillLevel = useFillLevel()
const theme = useTheme()

return (
<Button
ref={ref}
small
tertiary
endIcon={<DropdownArrowIcon className="dropdownIcon" />}
{...{
'&, &:hover, &:focus, &:focus-visible': {
backgroundColor:
theme.colors[`fill-${fillLevel > 2 ? 'three' : 'two'}-selected`],
},
}}
{...props}
/>
)
})<{ $isOpen?: boolean }>(({ $isOpen: isOpen = false, theme }) => ({
'.dropdownIcon': {
transform: isOpen ? 'scaleY(-1)' : 'scaleY(1)',
transition: 'transform 0.1s ease',
@@ -202,8 +198,8 @@ function CodeTabs() {
selectedKey,
onSelectionChange,
} = useContext(TabsContext)
const tabsRef = useRef<HTMLDivElement>()
const tabsWrapRef = useRef<HTMLDivElement>()
const tabsRef = useRef<HTMLDivElement>(undefined)
const tabsWrapRef = useRef<HTMLDivElement>(undefined)
const tabListStateProps: TabListStateProps = {
keyboardActivation: 'manual',
orientation: 'horizontal',
@@ -334,7 +330,7 @@ function CodeContent({
<CopyButton
copied={copied}
handleCopy={handleCopy}
verticallyCenter={!multiLine}
$verticallyCenter={!multiLine}
/>
<Div
paddingHorizontal="medium"
@@ -346,21 +342,19 @@ function CodeContent({
)
}

function CodeRef(
{
children,
language,
showLineNumbers,
showHeader,
tabs,
title,
onSelectedTabChange,
...props
}: CodeProps,
ref: RefObject<any>
) {
function CodeUnstyled({
ref,
children,
language,
showLineNumbers,
showHeader,
tabs,
title,
onSelectedTabChange,
...props
}: CodeProps) {
const parentFillLevel = useFillLevel()
const tabStateRef = useRef()
const tabStateRef = useRef(undefined)
const [selectedTabKey, setSelectedTabKey] = useState<string>(
tabs?.[0]?.key || ''
)
@@ -478,7 +472,7 @@ function CodeRef(
)
}

const Code = styled(forwardRef(CodeRef))((_) => ({
const Code = styled(CodeUnstyled)((_) => ({
[`${CopyButton}`]: {
opacity: 0,
pointerEvents: 'none',
15 changes: 7 additions & 8 deletions src/components/Codeline.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type Ref, forwardRef, useCallback, useEffect, useState } from 'react'
import { useCallback, useEffect, useState } from 'react'
import { type CssProps, Div, Flex, type FlexProps } from 'honorable'
import { useTheme } from 'styled-components'

@@ -11,10 +11,12 @@ type CodelineProps = FlexProps & {
onCopyClick?: (text: string) => Promise<void>
}

function CodelineRef(
{ children, displayText, onCopyClick, ...props }: CodelineProps,
ref: Ref<any>
) {
function Codeline({
children,
displayText,
onCopyClick,
...props
}: CodelineProps) {
const [copied, setCopied] = useState(false)
const theme = useTheme()

@@ -40,7 +42,6 @@ function CodelineRef(

return (
<Flex
ref={ref}
border="1px solid border-input"
borderRadius="medium"
{...props}
@@ -106,6 +107,4 @@ function CodelineRef(
)
}

const Codeline = forwardRef(CodelineRef)

export default Codeline
32 changes: 6 additions & 26 deletions src/components/ComboBox.tsx
Original file line number Diff line number Diff line change
@@ -52,8 +52,8 @@ type ComboBoxProps = Exclude<ComboBoxInputProps, 'children'> & {
| ReactElement<ListBoxItemBaseProps>[]
dropdownHeaderFixed?: ReactNode
dropdownFooterFixed?: ReactNode
dropdownHeader?: ReactElement
dropdownFooter?: ReactElement
dropdownHeader?: ReactElement<any>
dropdownFooter?: ReactElement<any>
onHeaderClick?: () => unknown
onFooterClick?: () => unknown
startIcon?: ReactNode
@@ -76,29 +76,13 @@ type ComboBoxProps = Exclude<ComboBoxInputProps, 'children'> & {
'onLoadMore' | 'isLoading' | 'validationState' | 'placeholder'
>

export const ComboBoxInputInner = styled.div<{ isOpen: boolean }>(
({ theme, isOpen }) => ({
'.arrow': {
transition: 'transform 0.1s ease',
display: 'flex',
marginLeft: theme.spacing.medium,
alignItems: 'center',
...(isOpen
? {
transform: 'scaleY(-100%)',
}
: {}),
},
})
)

type ComboBoxInputProps = {
showArrow?: boolean
isOpen?: boolean
outerInputProps?: InputProps
onInputClick?: MouseEventHandler
inputRef?: RefObject<HTMLInputElement>
buttonRef?: RefObject<HTMLDivElement>
inputRef?: RefObject<HTMLInputElement | null>
buttonRef?: RefObject<HTMLDivElement | null>
buttonProps?: AriaButtonProps
loading?: boolean
hasChips?: boolean
@@ -126,7 +110,6 @@ function OpenButton({
buttonProps,
...props
}: HTMLAttributes<HTMLDivElement> & {
isOpen?: boolean
buttonRef: RefObject<any>
buttonProps: AriaButtonProps
}) {
@@ -173,7 +156,6 @@ function ComboBoxInput({
buttonProps,
showArrow = true,
hasChips = false,
isOpen,
onInputClick,
loading,
...props
@@ -202,7 +184,6 @@ function ComboBoxInput({
dropdownButton={
showArrow ? (
<OpenButton
isOpen={isOpen}
buttonRef={buttonRef}
buttonProps={buttonProps}
/>
@@ -485,7 +466,7 @@ function ComboBox({
onClick={onChipClick}
closeButtonProps={{
onClick: () => {
onDeleteChip?.(chipProps?.key)
onDeleteChip?.(chipProps?.key?.toString())
},
'aria-label': `Remove ${chipProps.key}`,
}}
@@ -500,7 +481,7 @@ function ComboBox({
...(onDeleteChipProp
? {
onDeleteInputContent: () =>
onDeleteChipProp?.(chips?.[chips.length - 1]?.key),
onDeleteChipProp?.(chips?.[chips.length - 1]?.key?.toString()),
}
: {}),
...outerInputProps,
@@ -538,7 +519,6 @@ function ComboBox({
prefix={prefix}
titleContent={titleContent}
showClearButton={showClearButton}
setIsOpen={setIsOpen}
startIcon={startIcon}
endIcon={endIcon}
outerInputProps={outerInputProps}
32 changes: 15 additions & 17 deletions src/components/ContentCard.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,30 @@
import { Div, type DivProps } from 'honorable'
import { forwardRef } from 'react'
import { useTheme } from 'styled-components'

import Card, { type CardProps } from './Card'

export type ContentCardProps = CardProps & {
innerProps?: DivProps
} & DivProps
function ContentCard({ ref, children, ...props }: CardProps) {
const theme = useTheme()

const ContentCard = forwardRef<HTMLDivElement, ContentCardProps>(
({ children, innerProps, ...props }, ref) => (
return (
<Card
ref={ref}
overflowY="auto"
paddingHorizontal="xlarge"
padding={`0 ${theme.spacing.xlarge}px`}
{...props}
>
<Div
width="100%"
marginLeft="auto"
marginRight="auto"
paddingVertical="xlarge"
maxWidth={608}
{...innerProps}
<div
css={{
width: '100%',
marginLeft: 'auto',
marginRight: 'auto',
padding: `${theme.spacing.xlarge}px 0`,
maxWidth: 608,
}}
>
{children}
</Div>
</div>
</Card>
)
)
}

export default ContentCard
8 changes: 4 additions & 4 deletions src/components/Date.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Span } from 'honorable'

import moment from 'moment'
import dayjs from 'dayjs'
import styled from 'styled-components'

const Wrap = styled.div({
@@ -19,13 +19,13 @@ const T = styled.span(({ theme }) => ({
whiteSpace: 'nowrap',
}))

export default function Date({ date }: { date: moment.MomentInput }) {
export default function Date({ date }: { date: dayjs.ConfigType }) {
if (!date) return <Span>n/a</Span>

return (
<Wrap>
<D>{moment(date).format('ll')}</D>
<T>{moment(date).format('h:mm A')}</T>
<D>{dayjs(date).format('MMM D, YYYY')}</D>
<T>{dayjs(date).format('h:mm A')}</T>
</Wrap>
)
}
2 changes: 1 addition & 1 deletion src/components/DateField.tsx
Original file line number Diff line number Diff line change
@@ -35,7 +35,7 @@ const DateSegmentSC = styled.div(({ theme }) => {
},

textTransform: 'uppercase',
':focus-visible': {
'&:focus-visible': {
background: theme.colors['fill-three-selected'],
borderRadius: theme.borderRadiuses.medium,
},
169 changes: 0 additions & 169 deletions src/components/DatePicker.tsx

This file was deleted.

19 changes: 6 additions & 13 deletions src/components/Divider.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { type Ref, forwardRef } from 'react'
import { Div, Flex, type FlexProps, P } from 'honorable'

type DividerProps = FlexProps & {
@@ -7,18 +6,14 @@ type DividerProps = FlexProps & {
backgroundColor?: string
}

function DividerRef(
{
text,
color = 'text-light',
backgroundColor = 'text-light',
...props
}: DividerProps,
ref: Ref<any>
) {
function Divider({
text,
color = 'text-light',
backgroundColor = 'text-light',
...props
}: DividerProps) {
return (
<Flex
ref={ref}
align="center"
{...props}
>
@@ -48,6 +43,4 @@ function DividerRef(
)
}

const Divider = forwardRef(DividerRef)

export default Divider
29 changes: 11 additions & 18 deletions src/components/EmptyState.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
import {
type ComponentProps,
type ReactElement,
type Ref,
forwardRef,
} from 'react'
import { type ComponentProps, type ReactElement } from 'react'
import styled from 'styled-components'

type EmptyStateProps = ComponentProps<typeof EmptyStateSC> & {
export type EmptyStateProps = ComponentProps<typeof EmptyStateSC> & {
message: string
description?: string
icon?: ReactElement
icon?: ReactElement<any>
}

const EmptyStateSC = styled.div(({ theme }) => ({
@@ -34,15 +29,15 @@ const IconSC = styled.div(({ theme }) => ({
marginBottom: theme.spacing.large,
}))

function EmptyStateRef(
{ message, description, icon = null, children, ...props }: EmptyStateProps,
ref: Ref<any>
) {
function EmptyState({
message,
description,
icon = null,
children,
...props
}: EmptyStateProps) {
return (
<EmptyStateSC
ref={ref}
{...props}
>
<EmptyStateSC {...props}>
{icon && <IconSC>{icon}</IconSC>}
<MessageSC>{message}</MessageSC>
{description && <DescriptionSC>{description}</DescriptionSC>}
@@ -51,6 +46,4 @@ function EmptyStateRef(
)
}

const EmptyState = forwardRef(EmptyStateRef)

export default EmptyState
108 changes: 68 additions & 40 deletions src/components/Flex.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
// drop-in replacement for anywhere 'honorable' Flex is used

import {
type CSSProperties,
type ReactNode,
type Ref,
forwardRef,
memo,
} from 'react'
import { useTheme } from 'styled-components'
import { type CSSProperties, type ReactNode, type RefObject, memo } from 'react'
import styled, { type DefaultTheme } from 'styled-components'

type FlexBaseProps = {
/**
@@ -45,50 +39,84 @@ type FlexBaseProps = {
| 'space-around'
| 'space-evenly'

gap?: string

gap?: keyof DefaultTheme['spacing']
padding?: keyof DefaultTheme['spacing']
ref?: RefObject<HTMLDivElement>
children?: ReactNode
}

type FlexProps = Omit<CSSProperties, keyof FlexBaseProps> & FlexBaseProps

function FlexRef(props: FlexProps, ref: Ref<any>) {
const {
direction,
wrap,
basis,
grow,
shrink,
align,
justify,
gap,
children,
...otherProps
} = props
const theme = useTheme()
export type FlexProps = Omit<CSSProperties, keyof FlexBaseProps> & FlexBaseProps

function BaseFlex({
ref,
direction,
wrap,
basis,
grow,
shrink,
align,
justify,
gap,
padding,
children,
...otherProps
}: FlexProps) {
return (
<div
<FlexSC
ref={ref}
style={{
display: 'flex',
flexDirection: direction,
flexWrap: typeof wrap === 'boolean' ? 'wrap' : wrap,
flexBasis: basis,
flexGrow: typeof grow === 'boolean' ? 1 : grow,
flexShrink: typeof shrink === 'boolean' ? 1 : shrink,
alignItems: align,
justifyContent: justify,
gap: (theme.spacing as any)[gap] || 0,
...otherProps,
{...{
$direction: direction,
$wrap: wrap,
$basis: basis,
$grow: grow,
$shrink: shrink,
$align: align,
$justify: justify,
$gap: gap,
$padding: padding,
}}
css={{ ...otherProps }}
>
{children}
</div>
</FlexSC>
)
}

const BaseFlex = forwardRef(FlexRef)
const FlexSC = styled.div<{
$direction?: FlexProps['direction']
$wrap?: FlexProps['wrap']
$basis?: FlexProps['basis']
$grow?: FlexProps['grow']
$shrink?: FlexProps['shrink']
$align?: FlexProps['align']
$justify?: FlexProps['justify']
$gap?: FlexProps['gap']
$padding?: FlexProps['padding']
}>(
({
theme,
$direction,
$wrap,
$basis,
$grow,
$shrink,
$align,
$justify,
$gap,
$padding,
}) => ({
display: 'flex',
flexDirection: $direction,
flexWrap: typeof $wrap === 'boolean' ? 'wrap' : $wrap,
flexBasis: $basis,
flexGrow: typeof $grow === 'boolean' ? 1 : $grow,
flexShrink: typeof $shrink === 'boolean' ? 1 : $shrink,
alignItems: $align,
justifyContent: $justify,
gap: theme.spacing[$gap] || 0,
padding: theme.spacing[$padding] || 0,
})
)

const Flex = memo(BaseFlex)

46 changes: 19 additions & 27 deletions src/components/Flyover.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
import {
type ComponentPropsWithoutRef,
type ReactNode,
type Ref,
forwardRef,
useCallback,
} from 'react'
import { type ComponentPropsWithRef, type ReactNode, useCallback } from 'react'

import styled from 'styled-components'

@@ -26,23 +20,21 @@ type FlyoverProps = {
header?: ReactNode
scrollable?: boolean
width?: string | number
minWidth?: string | number
minWidth?: number
children?: ReactNode
} & ComponentPropsWithoutRef<'div'>

function FlyoverRef(
{
open = false,
onClose,
header,
scrollable = true,
width = '40%',
minWidth = 570,
children,
...props
}: FlyoverProps,
ref: Ref<any>
) {
} & ComponentPropsWithRef<'div'>

function Flyover({
ref,
open = false,
onClose,
header,
scrollable = true,
width = '40%',
minWidth = 570,
children,
...props
}: FlyoverProps) {
const triggerClose = useCallback(
(open: boolean) => {
if (!open) onClose?.()
@@ -92,11 +84,11 @@ function FlyoverRef(

const ModalWrapperSC = styled(ModalWrapper)<{
$width: string | number
$minWidth: string | number
$minWidth: number
}>(({ $width, $minWidth }) => ({
height: '100%',
width: $width,
minWidth: $minWidth,
minWidth: `min(100vw, ${$minWidth}px)`,
'@keyframes slideIn': {
from: { transform: 'translateX(100%)', opacity: 0 },
to: { transform: 'translateX(0)', opacity: 1 },
@@ -115,6 +107,8 @@ const ModalWrapperSC = styled(ModalWrapper)<{

const FlyoverWrapperSC = styled.div(({ theme }) => ({
backgroundColor: theme.colors['fill-zero'],
borderLeft: theme.borders.default,
boxShadow: theme.boxShadows.modal,
height: '100%',
display: 'flex',
flexDirection: 'column',
@@ -150,6 +144,4 @@ const FlyoverHeaderSC = styled.h1(({ theme }) => ({
color: theme.colors.text,
}))

const Flyover = forwardRef(FlyoverRef)

export default Flyover
42 changes: 16 additions & 26 deletions src/components/FormField.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import { type AriaLabelingProps, type DOMProps } from '@react-types/shared'
import { Div, type DivProps, Flex, Label, P } from 'honorable'
import { isNil } from 'lodash-es'
import {
type LabelHTMLAttributes,
type PropsWithChildren,
type ReactNode,
type Ref,
createContext,
forwardRef,
useContext,
useMemo,
} from 'react'
import { isNil } from 'lodash-es'

import { useLabel } from 'react-aria'

@@ -44,23 +42,20 @@ export function useFormField() {
return context
}

function FormFieldRef(
{
children,
label,
labelProps = {},
labellingProps = {},
caption,
hint,
error,
length,
maxLength,
required,
small,
...props
}: FormFieldProps,
ref: Ref<any>
) {
function FormField({
children,
label,
labelProps = {},
labellingProps = {},
caption,
hint,
error,
length,
maxLength,
required,
small,
...props
}: FormFieldProps) {
const hasLabel = label || required
const hasTopContent = hasLabel || caption
const hasBottomContent = !isNil(hint) || typeof maxLength === 'number'
@@ -84,10 +79,7 @@ function FormFieldRef(
)

const content = (
<Div
ref={ref}
{...props}
>
<Div {...props}>
{hasTopContent && (
<Flex
align="center"
@@ -168,6 +160,4 @@ function FormFieldRef(
)
}

const FormField = forwardRef(FormFieldRef)

export default FormField
13 changes: 3 additions & 10 deletions src/components/FormTitle.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
import { type FlexProps, H3, P } from 'honorable'
import { type Ref, forwardRef } from 'react'
import { H3, type H3Props, P } from 'honorable'

type FormTitleProps = FlexProps & {
type FormTitleProps = H3Props & {
title?: string
message?: string
}

function FormTitleRef(
{ title, message, ...props }: FormTitleProps,
ref: Ref<any>
) {
function FormTitle({ title, message, ...props }: FormTitleProps) {
return (
<H3
ref={ref}
body1
bold
color="text"
@@ -30,6 +25,4 @@ function FormTitleRef(
)
}

const FormTitle = forwardRef(FormTitleRef)

export default FormTitle
19 changes: 10 additions & 9 deletions src/components/Highlight.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import {
type ComponentPropsWithoutRef,
type Ref,
forwardRef,
type RefObject,
useEffect,
useMemo,
useRef,
@@ -153,16 +152,20 @@ type HighlightProps = Omit<ComponentPropsWithoutRef<'pre'>, 'children'> & {
language?: string
showLineNumbers?: boolean
children: string
ref?: RefObject<HTMLDivElement>
}

function HighlightRef(
{ language, children, showLineNumbers, ...props }: HighlightProps,
ref: Ref<any>
) {
function Highlight({
language,
children,
showLineNumbers,
ref,
...props
}: HighlightProps) {
if (typeof children !== 'string') {
throw new Error('Highlight component expects a string as its children')
}
const codeRef = useRef()
const codeRef = useRef(undefined)

const lines = useMemo(() => children.split(/\r?\n/), [children])

@@ -195,6 +198,4 @@ function HighlightRef(
)
}

const Highlight = forwardRef(HighlightRef)

export default Highlight
134 changes: 63 additions & 71 deletions src/components/IconFrame.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
import { ButtonBase, Flex } from 'honorable'
import {
type ComponentProps,
type ReactElement,
type ReactNode,
cloneElement,
forwardRef,
} from 'react'
import { ButtonBase, Flex, type FlexProps } from 'honorable'
import { type ReactElement, type ReactNode, cloneElement } from 'react'
import styled from 'styled-components'

import { type styledTheme } from '../theme'
@@ -75,7 +69,7 @@ const sizeToIconSize: Record<Size, number> = {
xsmall: 8,
small: 16,
medium: 16,
large: 24,
large: 16,
xlarge: 24,
}

@@ -91,14 +85,16 @@ type IconFrameProps = {
clickable?: boolean
disabled?: boolean
textValue?: string
icon: ReactElement
icon: ReactElement<any>
size?: Size
tooltip?: boolean | ReactNode
tooltipProps?: Partial<TooltipProps>
type?: Type
selected?: boolean
background?: SemanticColorKey
} & Omit<ComponentProps<typeof IconFrameSC>, 'size'>
} & FlexProps & {
as?: any
}

const IconFrameSC = styled(Flex)<{
$type: Type
@@ -148,69 +144,65 @@ const IconFrameSC = styled(Flex)<{
...($type === 'floating' ? { boxShadow: theme.boxShadows.slight } : {}),
}))

const IconFrame = forwardRef(
(
{
icon,
size = 'medium',
textValue = '',
clickable = false,
disabled = false,
selected = false,
tooltip,
tooltipProps,
type = 'tertiary',
background,
as,
...props
}: IconFrameProps,
ref
) => {
icon = cloneElement(icon, { size: sizeToIconSize[size] })
if (tooltip && typeof tooltip === 'boolean') {
tooltip = textValue
}
const forwardedAs = as || (clickable ? ButtonBase : undefined)

let content = (
<IconFrameSC
$clickable={clickable}
$selected={selected}
$type={type}
$size={size}
$background={background}
ref={ref}
aria-label={textValue}
disabled={(clickable && disabled) || undefined}
{...(forwardedAs ? { forwardedAs } : {})}
{...(clickable && {
tabIndex: 0,
role: 'button',
type: 'button',
})}
{...props}
function IconFrame({
ref,
icon,
size = 'medium',
textValue = '',
clickable = false,
disabled = false,
selected = false,
tooltip,
tooltipProps,
type = 'tertiary',
background,
as,
...props
}: IconFrameProps) {
icon = cloneElement(icon, { size: sizeToIconSize[size] })
if (tooltip && typeof tooltip === 'boolean') {
tooltip = textValue
}
const forwardedAs = as || (clickable ? ButtonBase : undefined)

let content = (
<IconFrameSC
$clickable={clickable}
$selected={selected}
$type={type}
$size={size}
$background={background}
ref={ref}
aria-label={textValue}
disabled={(clickable && disabled) || undefined}
{...(forwardedAs ? { forwardedAs } : {})}
{...(clickable && {
tabIndex: 0,
role: 'button',
type: 'button',
})}
{...props}
>
{icon}
</IconFrameSC>
)

if (tooltip) {
content = (
<Tooltip
displayOn="hover"
arrow
placement="top"
label={tooltip}
{...tooltipProps}
>
{icon}
</IconFrameSC>
{content}
</Tooltip>
)

if (tooltip) {
content = (
<Tooltip
displayOn="hover"
arrow
placement="top"
label={tooltip}
{...tooltipProps}
>
{content}
</Tooltip>
)
}

return content
}
)

return content
}

export default IconFrame
export type { IconFrameProps }
Loading