From 910b671ce6fbd7c974be3eca933239f309235a97 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Thu, 10 Aug 2023 17:26:50 +0200 Subject: [PATCH 1/2] Improve Icon component --- .../src/new/IconButton/IconButton.stories.tsx | 14 ++++++ .../src/new/IconButton/IconButton.tsx | 46 +++++++++++++++---- 2 files changed, 52 insertions(+), 8 deletions(-) diff --git a/code/ui/components/src/new/IconButton/IconButton.stories.tsx b/code/ui/components/src/new/IconButton/IconButton.stories.tsx index 4d4b82cf7e22..4e239b9fb1b3 100644 --- a/code/ui/components/src/new/IconButton/IconButton.stories.tsx +++ b/code/ui/components/src/new/IconButton/IconButton.stories.tsx @@ -52,6 +52,20 @@ export const Disabled: Story = { }, }; +export const Animated: Story = { + args: { + ...Base.args, + icon: 'FaceHappy', + }, + render: () => ( +
+ + + +
+ ), +}; + export const WithHref: Story = { render: () => (
diff --git a/code/ui/components/src/new/IconButton/IconButton.tsx b/code/ui/components/src/new/IconButton/IconButton.tsx index ae20fa9e38ad..18385bf3609d 100644 --- a/code/ui/components/src/new/IconButton/IconButton.tsx +++ b/code/ui/components/src/new/IconButton/IconButton.tsx @@ -1,32 +1,53 @@ -import React, { forwardRef } from 'react'; +import type { SyntheticEvent } from 'react'; +import React, { forwardRef, useEffect, useState } from 'react'; import { styled } from '@storybook/theming'; import { darken, lighten, rgba, transparentize } from 'polished'; import type { Icons } from '@storybook/icons'; import type { PropsOf } from '../utils/types'; import { Icon } from '../Icon/Icon'; -interface ButtonProps { +interface IconButtonProps { icon: Icons; as?: T; size?: 'small' | 'medium'; variant?: 'solid' | 'outline' | 'ghost'; - onClick?: () => void; + onClick?: (event: SyntheticEvent) => void; disabled?: boolean; active?: boolean; + onClickAnimation?: 'none' | 'rotate360' | 'glow' | 'jiggle'; } export const IconButton: { ( - props: ButtonProps & Omit, keyof ButtonProps> + props: IconButtonProps & Omit, keyof IconButtonProps> ): JSX.Element; displayName?: string; } = forwardRef( - ({ as, icon = 'FaceHappy', ...props }: ButtonProps, ref: React.Ref) => { + ( + { as, icon = 'FaceHappy', onClickAnimation = 'none', onClick, ...props }: IconButtonProps, + ref: React.Ref + ) => { const LocalIcon = Icon[icon]; + const [isAnimating, setIsAnimating] = useState(false); + + const handleClick = (event: SyntheticEvent) => { + if (onClick) onClick(event); + if (onClickAnimation === 'none') return; + setIsAnimating(true); + }; + + useEffect(() => { + const timer = setTimeout(() => { + if (isAnimating) setIsAnimating(false); + }, 1000); + return () => clearTimeout(timer); + }, [isAnimating]); return ( - - {icon && } + + + + ); } @@ -34,7 +55,7 @@ export const IconButton: { IconButton.displayName = 'IconButton'; -const StyledButton = styled.button>( +const StyledButton = styled.button>( ({ theme, variant = 'solid', size = 'medium', disabled = false, active = false }) => ({ border: 0, cursor: disabled ? 'not-allowed' : 'pointer', @@ -109,3 +130,12 @@ const StyledButton = styled.button>( }, }) ); + +const IconWrapper = styled.div<{ + isAnimating: boolean; + animation: IconButtonProps['onClickAnimation']; +}>(({ theme, isAnimating, animation }) => ({ + width: 14, + height: 14, + animation: isAnimating && animation !== 'none' && `${theme.animation[animation]} 1000ms ease-out`, +})); From d964d59de7c4830a4f876746092c33ec0aa7412d Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Thu, 10 Aug 2023 17:27:18 +0200 Subject: [PATCH 2/2] Added the new Toolbar component --- code/ui/components/package.json | 1 + code/ui/components/src/experimental.ts | 1 + .../src/new/Toolbar/Toolbar.stories.tsx | 117 +++++++++++++++++ .../ui/components/src/new/Toolbar/Toolbar.tsx | 83 ++++++++++++ code/ui/manager/src/globals/exports.ts | 10 +- code/yarn.lock | 123 ++++++++++++++++++ 6 files changed, 334 insertions(+), 1 deletion(-) create mode 100644 code/ui/components/src/new/Toolbar/Toolbar.stories.tsx create mode 100644 code/ui/components/src/new/Toolbar/Toolbar.tsx diff --git a/code/ui/components/package.json b/code/ui/components/package.json index b46cd262b0a7..b5e51b7b6de7 100644 --- a/code/ui/components/package.json +++ b/code/ui/components/package.json @@ -68,6 +68,7 @@ }, "dependencies": { "@radix-ui/react-select": "^1.2.2", + "@radix-ui/react-toolbar": "^1.0.4", "@storybook/client-logger": "workspace:*", "@storybook/csf": "^0.1.0", "@storybook/global": "^5.0.0", diff --git a/code/ui/components/src/experimental.ts b/code/ui/components/src/experimental.ts index 7280862f230f..a210f26c37a6 100644 --- a/code/ui/components/src/experimental.ts +++ b/code/ui/components/src/experimental.ts @@ -13,3 +13,4 @@ export { Select } from './new/Select/Select'; export { Link } from './new/Link/Link'; export { Icon } from './new/Icon/Icon'; export { IconButton } from './new/IconButton/IconButton'; +export { Toolbar } from './new/Toolbar/Toolbar'; diff --git a/code/ui/components/src/new/Toolbar/Toolbar.stories.tsx b/code/ui/components/src/new/Toolbar/Toolbar.stories.tsx new file mode 100644 index 000000000000..d50a8f2bab23 --- /dev/null +++ b/code/ui/components/src/new/Toolbar/Toolbar.stories.tsx @@ -0,0 +1,117 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import React from 'react'; + +import { Toolbar } from './Toolbar'; +import { IconButton } from '../IconButton/IconButton'; +import { Button } from '../Button/Button'; + +const meta: Meta = { + title: 'Toolbar', + component: Toolbar.Root, + tags: ['autodocs'], +}; + +export default meta; +type Story = StoryObj; + +export const Base: Story = { + args: { + hasPadding: true, + borderTop: false, + borderBottom: true, + }, + render: (_, { args }) => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ), +}; + +export const NoMargin: Story = { + args: { + ...Base.args, + hasPadding: false, + }, + render: Base.render, +}; + +export const BorderTop: Story = { + args: { + ...Base.args, + borderTop: true, + borderBottom: false, + }, + render: Base.render, +}; + +export const BorderBottom: Story = { + args: { + ...Base.args, + borderTop: false, + borderBottom: true, + }, + render: Base.render, +}; + +export const BorderTopBottom: Story = { + args: { + ...Base.args, + borderTop: true, + borderBottom: true, + }, + render: Base.render, +}; diff --git a/code/ui/components/src/new/Toolbar/Toolbar.tsx b/code/ui/components/src/new/Toolbar/Toolbar.tsx new file mode 100644 index 000000000000..827011ba06fa --- /dev/null +++ b/code/ui/components/src/new/Toolbar/Toolbar.tsx @@ -0,0 +1,83 @@ +import type { ComponentPropsWithoutRef, ElementRef } from 'react'; +import React, { forwardRef } from 'react'; +import * as ToolbarPrimitive from '@radix-ui/react-toolbar'; +import { styled } from '@storybook/theming'; + +interface RootProps extends ComponentPropsWithoutRef { + hasPadding?: boolean; + borderBottom?: boolean; + borderTop?: boolean; +} + +const ToolbarRoot = forwardRef, RootProps>( + ({ className, children, ...props }, ref) => ( + + {children} + + ) +); +ToolbarRoot.displayName = ToolbarPrimitive.Root.displayName; + +const ToolbarSeparator = React.forwardRef< + ElementRef, + ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ); +ToolbarSeparator.displayName = ToolbarPrimitive.Separator.displayName; + +const ToolbarToggleGroup = React.forwardRef< + ElementRef, + ToolbarPrimitive.ToolbarToggleGroupSingleProps | ToolbarPrimitive.ToolbarToggleGroupMultipleProps +>(({ className, ...props }, ref) => ); +ToolbarToggleGroup.displayName = ToolbarPrimitive.ToggleGroup.displayName; + +const ToolbarToggleItem = React.forwardRef< + ElementRef, + ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ); +ToolbarToggleItem.displayName = ToolbarPrimitive.ToggleItem.displayName; + +const StyledRoot = styled(ToolbarPrimitive.Root)( + ({ theme, hasPadding = true, borderBottom = true, borderTop = false }) => ({ + display: 'flex', + padding: hasPadding ? '0 10px' : 0, + justifyContent: 'space-between', + height: 40, + borderBottom: borderBottom ? `1px solid ${theme.appBorderColor}` : 'none', + borderTop: borderTop ? `1px solid ${theme.appBorderColor}` : 'none', + boxSizing: 'border-box', + backgroundColor: theme.barBg, + }) +); + +const StyledSeparator = styled(ToolbarPrimitive.Separator)(({ theme }) => ({ + width: 1, + height: 20, + backgroundColor: theme.appBorderColor, +})); + +const StyledToggleGroup = styled(ToolbarPrimitive.ToggleGroup)({ + display: 'flex', + gap: 5, + alignItems: 'center', +}); + +const Left = styled.div({ + display: 'flex', + gap: 5, + alignItems: 'center', +}); + +const Right = styled.div({ + display: 'flex', + gap: 5, + alignItems: 'center', +}); + +export const Toolbar = { + Root: ToolbarRoot, + Left, + Right, + ToogleGroup: ToolbarToggleGroup, + ToggleItem: ToolbarToggleItem, + Separator: ToolbarSeparator, +}; diff --git a/code/ui/manager/src/globals/exports.ts b/code/ui/manager/src/globals/exports.ts index 6ad6218c0fff..793165aaf103 100644 --- a/code/ui/manager/src/globals/exports.ts +++ b/code/ui/manager/src/globals/exports.ts @@ -114,7 +114,15 @@ export default { 'resetComponents', 'withReset', ], - '@storybook/components/experimental': ['Button', 'Icon', 'IconButton', 'Input', 'Link', 'Select'], + '@storybook/components/experimental': [ + 'Button', + 'Icon', + 'IconButton', + 'Input', + 'Link', + 'Select', + 'Toolbar', + ], '@storybook/channels': [ 'Channel', 'PostMessageTransport', diff --git a/code/yarn.lock b/code/yarn.lock index 2aff024414fb..3d8fd9362ec7 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -5204,6 +5204,34 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-roving-focus@npm:1.0.4": + version: 1.0.4 + resolution: "@radix-ui/react-roving-focus@npm:1.0.4" + dependencies: + "@babel/runtime": ^7.13.10 + "@radix-ui/primitive": 1.0.1 + "@radix-ui/react-collection": 1.0.3 + "@radix-ui/react-compose-refs": 1.0.1 + "@radix-ui/react-context": 1.0.1 + "@radix-ui/react-direction": 1.0.1 + "@radix-ui/react-id": 1.0.1 + "@radix-ui/react-primitive": 1.0.3 + "@radix-ui/react-use-callback-ref": 1.0.1 + "@radix-ui/react-use-controllable-state": 1.0.1 + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 61e3ddfd1647e64fba855434ff41e8e7ba707244fe8841f78c450fbdce525383b64259279475615d030dbf1625cbffd8eeebee72d91bf6978794f5dbcf887fc0 + languageName: node + linkType: hard + "@radix-ui/react-select@npm:^1.2.2": version: 1.2.2 resolution: "@radix-ui/react-select@npm:1.2.2" @@ -5244,6 +5272,26 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-separator@npm:1.0.3": + version: 1.0.3 + resolution: "@radix-ui/react-separator@npm:1.0.3" + dependencies: + "@babel/runtime": ^7.13.10 + "@radix-ui/react-primitive": 1.0.3 + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 87bcde47343f2bc4439a0dc34381f557905d9b3c1e8c5a0d32ceea62a8ef84f3abf671c5cb29309fc87759ad41d39af619ba546cf54109d64c8746e3ca683de3 + languageName: node + linkType: hard + "@radix-ui/react-slot@npm:1.0.2": version: 1.0.2 resolution: "@radix-ui/react-slot@npm:1.0.2" @@ -5260,6 +5308,80 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-toggle-group@npm:1.0.4": + version: 1.0.4 + resolution: "@radix-ui/react-toggle-group@npm:1.0.4" + dependencies: + "@babel/runtime": ^7.13.10 + "@radix-ui/primitive": 1.0.1 + "@radix-ui/react-context": 1.0.1 + "@radix-ui/react-direction": 1.0.1 + "@radix-ui/react-primitive": 1.0.3 + "@radix-ui/react-roving-focus": 1.0.4 + "@radix-ui/react-toggle": 1.0.3 + "@radix-ui/react-use-controllable-state": 1.0.1 + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 4f4761965022759ac0950ac026029b64049e1f18ef07a01ddde788b7606efcb262c9ae3a418de0c0756bf7285182ed0d268502c6f17ba86d2ff27eee5507bbf7 + languageName: node + linkType: hard + +"@radix-ui/react-toggle@npm:1.0.3": + version: 1.0.3 + resolution: "@radix-ui/react-toggle@npm:1.0.3" + dependencies: + "@babel/runtime": ^7.13.10 + "@radix-ui/primitive": 1.0.1 + "@radix-ui/react-primitive": 1.0.3 + "@radix-ui/react-use-controllable-state": 1.0.1 + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 9b487dad213ea7e70b0aa205e7c6f790a6f2bf394c39912e22dbe003403fd0d24a41c2efd31695fc31ab7bac286f28253dbb2fc5202cacd572ebf909f1fdc86c + languageName: node + linkType: hard + +"@radix-ui/react-toolbar@npm:^1.0.4": + version: 1.0.4 + resolution: "@radix-ui/react-toolbar@npm:1.0.4" + dependencies: + "@babel/runtime": ^7.13.10 + "@radix-ui/primitive": 1.0.1 + "@radix-ui/react-context": 1.0.1 + "@radix-ui/react-direction": 1.0.1 + "@radix-ui/react-primitive": 1.0.3 + "@radix-ui/react-roving-focus": 1.0.4 + "@radix-ui/react-separator": 1.0.3 + "@radix-ui/react-toggle-group": 1.0.4 + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 3ed7ebe22ef2e8369e08bb59776671a7b8c413628249c338b8db86b4b9ac40127b4201d5bd4a9c23ea1fd21464769b4fa427d3ebcda3a7fcdbd45b256b5a753a + languageName: node + linkType: hard + "@radix-ui/react-use-callback-ref@npm:1.0.1": version: 1.0.1 resolution: "@radix-ui/react-use-callback-ref@npm:1.0.1" @@ -6545,6 +6667,7 @@ __metadata: dependencies: "@popperjs/core": ^2.6.0 "@radix-ui/react-select": ^1.2.2 + "@radix-ui/react-toolbar": ^1.0.4 "@storybook/client-logger": "workspace:*" "@storybook/csf": ^0.1.0 "@storybook/global": ^5.0.0