From 780973b700f826d1ed4f88aa8621df19ec49f0f3 Mon Sep 17 00:00:00 2001 From: Sergey Pavlyuk Date: Fri, 26 Jan 2024 12:16:37 +0300 Subject: [PATCH 1/8] feat: add slide animation for ActionPanel --- src/components/ActionPanel/ActionPanel.scss | 15 +- src/components/ActionPanel/ActionPanel.tsx | 29 ++- src/components/ActionPanel/hooks.ts | 177 ++++++++++++++++++ .../DashKit/__stories__/DashKitShowcase.tsx | 25 ++- 4 files changed, 239 insertions(+), 7 deletions(-) create mode 100644 src/components/ActionPanel/hooks.ts diff --git a/src/components/ActionPanel/ActionPanel.scss b/src/components/ActionPanel/ActionPanel.scss index 7ee1579..0e2bf41 100644 --- a/src/components/ActionPanel/ActionPanel.scss +++ b/src/components/ActionPanel/ActionPanel.scss @@ -1,16 +1,25 @@ .dashkit-action-panel { background-color: var(--g-color-base-float); - box-shadow: 0 0 15px rgba(0, 0, 0, 0.15); position: fixed; bottom: 20px; display: flex; - border-radius: 8px; + border-radius: 10px; + border: 3px solid var(--g-color-base-brand); padding: 8px; gap: 0; left: 50%; - transform: translateX(-50%); + transform: translateX(-50%) translateY(0); z-index: 1; + &_slide-animation { + will-change: transform; + transition: transform 0.5s ease-in-out; + } + + &_hidden { + transform: translateX(-50%) translateY(calc(100% + 20px)); + } + &__item { height: 68px; width: 98px; diff --git a/src/components/ActionPanel/ActionPanel.tsx b/src/components/ActionPanel/ActionPanel.tsx index 0675410..0a8489a 100644 --- a/src/components/ActionPanel/ActionPanel.tsx +++ b/src/components/ActionPanel/ActionPanel.tsx @@ -2,6 +2,7 @@ import React from 'react'; import {cn} from '../../utils/cn'; +import {useCssTransitionWatcher, useMount} from './hooks'; import './ActionPanel.scss'; export type ActionPanelItem = { @@ -16,13 +17,39 @@ export type ActionPanelItem = { export type ActionPanelProps = { items: ActionPanelItem[]; className?: string; + slideAnimation?: boolean; + hide?: boolean; }; const b = cn('dashkit-action-panel'); export const ActionPanel = (props: ActionPanelProps) => { + const isMounted = useMount(); + const isAnimationEnabled = (props.slideAnimation ?? true) && isMounted; + const isHidden = props.hide ?? !isMounted; + + const ref = React.useRef(null); + const {isReadyToBeRemoved, hiddenState, isPending} = useCssTransitionWatcher({ + isEnabled: isAnimationEnabled, + isHidden, + ref, + }); + + if (isReadyToBeRemoved && isMounted) { + return null; + } + return ( -
+
{props.items.map((item) => { return (
{ + switch (action.type) { + case 'domPending': { + return { + ...state, + domPending: action.payload, + }; + } + + case 'cssTransitionPending': { + return { + ...state, + // if transition started than element is ready + domPending: false, + cssTransitionPending: action.payload, + }; + } + + default: + return state; + } +}; + +export const useAnimationState = ({ + element, + enable, + triggerProperty, +}: { + element: HTMLElement | null; + enable: boolean; + triggerProperty: boolean; +}) => { + const cssTransitionTimeout = React.useRef>(); + const [state, dispatch] = React.useReducer(animationStateReducer, { + domPending: false, + cssTransitionPending: false, + }); + + const transitionHandler = React.useCallback( + (e?: TransitionEvent | null) => { + const payload = e !== null && e?.type !== 'transitionend'; + dispatch({ + type: 'cssTransitionPending', + payload, + }); + + if (payload) { + clearTimeout(cssTransitionTimeout.current); + } + }, + [dispatch, cssTransitionTimeout], + ); + + const setDomPending = React.useCallback( + (payload: boolean) => { + dispatch({ + type: 'domPending', + payload, + }); + }, + [dispatch], + ); + + const propertyHandler = React.useCallback(() => { + if (!enable) { + return; + } + + setDomPending(true); + + // timeout timer for transitionstart if no css transition rule + cssTransitionTimeout.current = setTimeout(() => { + setDomPending(false); + }, 100); + }, [enable, cssTransitionTimeout, setDomPending]); + usePropWatcher(triggerProperty, propertyHandler); + + React.useEffect(() => { + if (!enable) { + element?.removeEventListener('transitionend', transitionHandler); + element?.removeEventListener('transitionrun', transitionHandler); + transitionHandler(null); + return; + } + + element?.addEventListener('transitionend', transitionHandler); + element?.addEventListener('transitionrun', transitionHandler); + }, [enable, element, transitionHandler]); + + return state.cssTransitionPending || state.domPending; +}; + +export function usePropWatcher(value: T, onChange: (value: T) => void): T { + const refState = React.useRef(value); + + React.useLayoutEffect(() => { + if (refState.current !== value) { + refState.current = value; + onChange?.(value); + } + }, [value, onChange]); + + return value; +} + +export const useMount = () => { + const [isMounted, setMountState] = React.useState(false); + + React.useEffect(() => { + setMountState(true); + return () => setMountState(false); + }, [setMountState]); + + return isMounted; +}; + +export const useCssTransitionWatcher = ({ + isHidden, + isEnabled, + ref, +}: { + isHidden: boolean; + isEnabled: boolean; + ref: React.MutableRefObject; +}) => { + const [hiddenState, setHiddenState] = React.useState(isHidden); + const [tansitionPending, startTransition] = React.useTransition(); + const animationPending = useAnimationState({ + element: ref.current, + enable: isEnabled, + triggerProperty: isHidden, + }); + + const onChange = React.useCallback( + (value: boolean) => { + if (isEnabled) { + if (value) { + setHiddenState(value); + } else { + startTransition(() => { + setHiddenState(value); + }); + } + } else { + setHiddenState(value); + } + }, + [isEnabled, setHiddenState], + ); + usePropWatcher(isHidden, onChange); + + const isReadyToBeRemoved = !tansitionPending && !animationPending && hiddenState; + const isPending = animationPending || tansitionPending; + + return { + isPending, + isReadyToBeRemoved, + hiddenState, + }; +}; diff --git a/src/components/DashKit/__stories__/DashKitShowcase.tsx b/src/components/DashKit/__stories__/DashKitShowcase.tsx index 20793da..6f747da 100644 --- a/src/components/DashKit/__stories__/DashKitShowcase.tsx +++ b/src/components/DashKit/__stories__/DashKitShowcase.tsx @@ -35,6 +35,7 @@ type DashKitDemoState = { customControlsActionData: number; showCustomMenu: boolean; enableActionPanel: boolean; + enableActionPanelAnimation: boolean; }; export class DashKitShowcase extends React.Component<{}, DashKitDemoState> { @@ -53,6 +54,7 @@ export class DashKitShowcase extends React.Component<{}, DashKitDemoState> { customControlsActionData: 0, showCustomMenu: true, enableActionPanel: false, + enableActionPanelAnimation: true, }; private dashKitRef = React.createRef(); @@ -170,13 +172,26 @@ export class DashKitShowcase extends React.Component<{}, DashKitDemoState> { ? 'Disable action panel' : 'Enable action panel'} +
{this.state.lastAction} - {Boolean(this.state.enableActionPanel) && ( - - )} + { this.setState({enableActionPanel: !this.state.enableActionPanel}); } + private toggleActionPanelAnimation() { + this.setState({enableActionPanelAnimation: !this.state.enableActionPanelAnimation}); + } + private getActionPanelItems() { return [ { From ba5eec17c8a79c6468f5fb774bc0ac2053a83a13 Mon Sep 17 00:00:00 2001 From: Sergey Pavlyuk Date: Mon, 29 Jan 2024 19:09:41 +0300 Subject: [PATCH 2/8] feat: chartkit editmode animation using CSSTransition --- package-lock.json | 8 +- package.json | 3 +- src/components/ActionPanel/ActionPanel.scss | 27 ++- src/components/ActionPanel/ActionPanel.tsx | 46 ++--- src/components/ActionPanel/hooks.ts | 177 ------------------ .../DashKit/__stories__/DashKitShowcase.tsx | 20 +- src/components/GridItem/GridItem.js | 42 +++-- src/components/GridItem/GridItem.scss | 46 ++++- .../OverlayControls/OverlayControls.tsx | 12 +- 9 files changed, 128 insertions(+), 253 deletions(-) delete mode 100644 src/components/ActionPanel/hooks.ts diff --git a/package-lock.json b/package-lock.json index 3e899e7..f11b2bd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,8 @@ "@gravity-ui/i18n": "^1.0.0", "hashids": "^2.2.8", "immutability-helper": "^3.1.1", - "react-grid-layout": "^1.3.4" + "react-grid-layout": "^1.3.4", + "react-transition-group": "^4.4.5" }, "devDependencies": { "@bem-react/classname": "^1.6.0", @@ -2547,7 +2548,6 @@ }, "node_modules/@babel/runtime": { "version": "7.18.6", - "dev": true, "license": "MIT", "dependencies": { "regenerator-runtime": "^0.13.4" @@ -10507,7 +10507,6 @@ }, "node_modules/csstype": { "version": "3.1.1", - "dev": true, "license": "MIT" }, "node_modules/d": { @@ -11063,7 +11062,6 @@ }, "node_modules/dom-helpers": { "version": "5.2.1", - "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.8.7", @@ -20413,7 +20411,6 @@ }, "node_modules/react-transition-group": { "version": "4.4.5", - "dev": true, "license": "BSD-3-Clause", "dependencies": { "@babel/runtime": "^7.5.5", @@ -20585,7 +20582,6 @@ }, "node_modules/regenerator-runtime": { "version": "0.13.9", - "dev": true, "license": "MIT" }, "node_modules/regenerator-transform": { diff --git a/package.json b/package.json index 96ca683..a309225 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,8 @@ "@gravity-ui/i18n": "^1.0.0", "hashids": "^2.2.8", "immutability-helper": "^3.1.1", - "react-grid-layout": "^1.3.4" + "react-grid-layout": "^1.3.4", + "react-transition-group": "^4.4.5" }, "peerDependencies": { "@bem-react/classname": "^1.6.0", diff --git a/src/components/ActionPanel/ActionPanel.scss b/src/components/ActionPanel/ActionPanel.scss index 0e2bf41..c9848cd 100644 --- a/src/components/ActionPanel/ActionPanel.scss +++ b/src/components/ActionPanel/ActionPanel.scss @@ -1,23 +1,38 @@ .dashkit-action-panel { + $show_panel_transform: translateX(-50%) translateY(0); + $hide_panel_transform: translateX(-50%) translateY(calc(100% + 20px)); + $selector: #{&}; + background-color: var(--g-color-base-float); position: fixed; bottom: 20px; display: flex; border-radius: 10px; - border: 3px solid var(--g-color-base-brand); + border: 2px solid var(--g-color-base-brand); padding: 8px; gap: 0; left: 50%; - transform: translateX(-50%) translateY(0); + transform: $show_panel_transform; z-index: 1; - &_slide-animation { + &-enter { + transform: $hide_panel_transform; will-change: transform; - transition: transform 0.5s ease-in-out; + + &-active { + transform: $show_panel_transform; + transition: transform 300ms ease-in-out; + } } - &_hidden { - transform: translateX(-50%) translateY(calc(100% + 20px)); + &-exit { + transform: $show_panel_transform; + will-change: transform; + + &-active { + transform: $hide_panel_transform; + transition: transform 300ms ease-in-out; + } } &__item { diff --git a/src/components/ActionPanel/ActionPanel.tsx b/src/components/ActionPanel/ActionPanel.tsx index 0a8489a..a92cd6e 100644 --- a/src/components/ActionPanel/ActionPanel.tsx +++ b/src/components/ActionPanel/ActionPanel.tsx @@ -1,8 +1,8 @@ import React from 'react'; +import {CSSTransition} from 'react-transition-group'; import {cn} from '../../utils/cn'; -import {useCssTransitionWatcher, useMount} from './hooks'; import './ActionPanel.scss'; export type ActionPanelItem = { @@ -17,39 +17,17 @@ export type ActionPanelItem = { export type ActionPanelProps = { items: ActionPanelItem[]; className?: string; - slideAnimation?: boolean; - hide?: boolean; + enable?: boolean; }; const b = cn('dashkit-action-panel'); export const ActionPanel = (props: ActionPanelProps) => { - const isMounted = useMount(); - const isAnimationEnabled = (props.slideAnimation ?? true) && isMounted; - const isHidden = props.hide ?? !isMounted; + const isHidden = props.enable; + const nodeRef = React.useRef(null); - const ref = React.useRef(null); - const {isReadyToBeRemoved, hiddenState, isPending} = useCssTransitionWatcher({ - isEnabled: isAnimationEnabled, - isHidden, - ref, - }); - - if (isReadyToBeRemoved && isMounted) { - return null; - } - - return ( -
+ const content = ( +
{props.items.map((item) => { return (
{ })}
); + + return ( + + {content} + + ); }; diff --git a/src/components/ActionPanel/hooks.ts b/src/components/ActionPanel/hooks.ts deleted file mode 100644 index b69bc1c..0000000 --- a/src/components/ActionPanel/hooks.ts +++ /dev/null @@ -1,177 +0,0 @@ -import React from 'react'; - -type AnimationStateType = { - domPending: boolean; - cssTransitionPending: boolean; -}; - -type AnimationStateActions = - | { - type: 'domPending'; - payload: boolean; - } - | { - type: 'cssTransitionPending'; - payload: boolean; - }; - -const animationStateReducer = (state: AnimationStateType, action: AnimationStateActions) => { - switch (action.type) { - case 'domPending': { - return { - ...state, - domPending: action.payload, - }; - } - - case 'cssTransitionPending': { - return { - ...state, - // if transition started than element is ready - domPending: false, - cssTransitionPending: action.payload, - }; - } - - default: - return state; - } -}; - -export const useAnimationState = ({ - element, - enable, - triggerProperty, -}: { - element: HTMLElement | null; - enable: boolean; - triggerProperty: boolean; -}) => { - const cssTransitionTimeout = React.useRef>(); - const [state, dispatch] = React.useReducer(animationStateReducer, { - domPending: false, - cssTransitionPending: false, - }); - - const transitionHandler = React.useCallback( - (e?: TransitionEvent | null) => { - const payload = e !== null && e?.type !== 'transitionend'; - dispatch({ - type: 'cssTransitionPending', - payload, - }); - - if (payload) { - clearTimeout(cssTransitionTimeout.current); - } - }, - [dispatch, cssTransitionTimeout], - ); - - const setDomPending = React.useCallback( - (payload: boolean) => { - dispatch({ - type: 'domPending', - payload, - }); - }, - [dispatch], - ); - - const propertyHandler = React.useCallback(() => { - if (!enable) { - return; - } - - setDomPending(true); - - // timeout timer for transitionstart if no css transition rule - cssTransitionTimeout.current = setTimeout(() => { - setDomPending(false); - }, 100); - }, [enable, cssTransitionTimeout, setDomPending]); - usePropWatcher(triggerProperty, propertyHandler); - - React.useEffect(() => { - if (!enable) { - element?.removeEventListener('transitionend', transitionHandler); - element?.removeEventListener('transitionrun', transitionHandler); - transitionHandler(null); - return; - } - - element?.addEventListener('transitionend', transitionHandler); - element?.addEventListener('transitionrun', transitionHandler); - }, [enable, element, transitionHandler]); - - return state.cssTransitionPending || state.domPending; -}; - -export function usePropWatcher(value: T, onChange: (value: T) => void): T { - const refState = React.useRef(value); - - React.useLayoutEffect(() => { - if (refState.current !== value) { - refState.current = value; - onChange?.(value); - } - }, [value, onChange]); - - return value; -} - -export const useMount = () => { - const [isMounted, setMountState] = React.useState(false); - - React.useEffect(() => { - setMountState(true); - return () => setMountState(false); - }, [setMountState]); - - return isMounted; -}; - -export const useCssTransitionWatcher = ({ - isHidden, - isEnabled, - ref, -}: { - isHidden: boolean; - isEnabled: boolean; - ref: React.MutableRefObject; -}) => { - const [hiddenState, setHiddenState] = React.useState(isHidden); - const [tansitionPending, startTransition] = React.useTransition(); - const animationPending = useAnimationState({ - element: ref.current, - enable: isEnabled, - triggerProperty: isHidden, - }); - - const onChange = React.useCallback( - (value: boolean) => { - if (isEnabled) { - if (value) { - setHiddenState(value); - } else { - startTransition(() => { - setHiddenState(value); - }); - } - } else { - setHiddenState(value); - } - }, - [isEnabled, setHiddenState], - ); - usePropWatcher(isHidden, onChange); - - const isReadyToBeRemoved = !tansitionPending && !animationPending && hiddenState; - const isPending = animationPending || tansitionPending; - - return { - isPending, - isReadyToBeRemoved, - hiddenState, - }; -}; diff --git a/src/components/DashKit/__stories__/DashKitShowcase.tsx b/src/components/DashKit/__stories__/DashKitShowcase.tsx index 6f747da..e7af8c6 100644 --- a/src/components/DashKit/__stories__/DashKitShowcase.tsx +++ b/src/components/DashKit/__stories__/DashKitShowcase.tsx @@ -35,7 +35,6 @@ type DashKitDemoState = { customControlsActionData: number; showCustomMenu: boolean; enableActionPanel: boolean; - enableActionPanelAnimation: boolean; }; export class DashKitShowcase extends React.Component<{}, DashKitDemoState> { @@ -54,7 +53,6 @@ export class DashKitShowcase extends React.Component<{}, DashKitDemoState> { customControlsActionData: 0, showCustomMenu: true, enableActionPanel: false, - enableActionPanelAnimation: true, }; private dashKitRef = React.createRef(); @@ -172,24 +170,12 @@ export class DashKitShowcase extends React.Component<{}, DashKitDemoState> { ? 'Disable action panel' : 'Enable action panel'} -
{this.state.lastAction} { this.setState({enableActionPanel: !this.state.enableActionPanel}); } - private toggleActionPanelAnimation() { - this.setState({enableActionPanelAnimation: !this.state.enableActionPanelAnimation}); - } - private getActionPanelItems() { return [ { diff --git a/src/components/GridItem/GridItem.js b/src/components/GridItem/GridItem.js index 35b6aca..8f42dbe 100644 --- a/src/components/GridItem/GridItem.js +++ b/src/components/GridItem/GridItem.js @@ -1,5 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; +import {CSSTransition} from 'react-transition-group'; import Item from '../Item/Item'; import {DashKitContext} from '../../context/DashKitContext'; @@ -37,26 +38,43 @@ class GridItem extends React.PureComponent { static contextType = DashKitContext; + overlayRef = React.createRef(null); + controlsRef = React.createRef(); + renderOverlay() { const {overlayControls} = this.props; const {editMode} = this.context; - - if (!editMode || this.props.item.data._editActive) { - return null; - } + const isHidden = !editMode || this.props.item.data._editActive; const {item} = this.props; const controls = overlayControls && overlayControls[item.type]; return ( - -
- - + <> + +
+ + + + + ); } diff --git a/src/components/GridItem/GridItem.scss b/src/components/GridItem/GridItem.scss index ce92df2..2f14597 100644 --- a/src/components/GridItem/GridItem.scss +++ b/src/components/GridItem/GridItem.scss @@ -8,8 +8,51 @@ left: 0; right: 0; border-radius: 3px; - background-color: var(--g-color-base-generic); + background-color: var(--g-color-base-float); border: solid 1px rgba(0, 0, 0, 0.1); + opacity: 0.5; + + &-enter { + opacity: 0; + will-change: opacity; + + &-active { + opacity: 0.5; + transition: opacity 300ms ease-in-out; + } + } + + &-exit { + opacity: 0.5; + will-change: opacity; + + &-active { + opacity: 0; + transition: opacity 300ms ease-in-out; + } + } + } + + &__controls { + &-enter { + opacity: 0; + will-change: opacity; + + &-active { + opacity: 1; + transition: opacity 300ms ease-in-out; + } + } + + &-exit { + opacity: 1; + will-change: opacity; + + &-active { + opacity: 0; + transition: opacity 300ms ease-in-out; + } + } } &__item { @@ -21,7 +64,6 @@ border-radius: 3px; &_editMode { - opacity: 0.5; border-color: transparent; pointer-events: none; } diff --git a/src/components/OverlayControls/OverlayControls.tsx b/src/components/OverlayControls/OverlayControls.tsx index 37a2f4b..bd4c858 100644 --- a/src/components/OverlayControls/OverlayControls.tsx +++ b/src/components/OverlayControls/OverlayControls.tsx @@ -64,6 +64,7 @@ interface OverlayControlsProps extends OverlayControlsDefaultProps { configItem: ConfigItem; items?: OverlayControlItem[]; overlayControls?: Record; + forwardRef?: React.ForwardedRef; } type PreparedCopyItemOptionsArg = Pick & { @@ -96,6 +97,7 @@ class OverlayControls extends React.Component { size: 'm', }; context!: React.ContextType; + ref = null; render() { const {items = [], position} = this.props; const hasCustomControlsWithWidgets = items.length > 0; @@ -104,7 +106,11 @@ class OverlayControls extends React.Component { ? this.getCustomControlsWithWidgets() : this.renderControls(); - return
{controls}
; + return ( +
+ {controls} +
+ ); } private renderControlsItem = (item: OverlayControlItem, index: number, length: number) => { const {view, size} = this.props; @@ -376,4 +382,6 @@ class OverlayControls extends React.Component { } } -export default OverlayControls; +export default React.forwardRef((props, ref) => { + return ; +}); From 783432b65668886e0490f756270cc96160b2ed06 Mon Sep 17 00:00:00 2001 From: Sergey Pavlyuk Date: Tue, 30 Jan 2024 13:24:34 +0300 Subject: [PATCH 3/8] feat: added animations and base styling is reverted to previous --- src/components/GridItem/GridItem.js | 5 +++-- src/components/GridItem/GridItem.scss | 9 +++++---- src/components/OverlayControls/OverlayControls.tsx | 7 ++++--- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/components/GridItem/GridItem.js b/src/components/GridItem/GridItem.js index 8f42dbe..c60e652 100644 --- a/src/components/GridItem/GridItem.js +++ b/src/components/GridItem/GridItem.js @@ -39,7 +39,7 @@ class GridItem extends React.PureComponent { static contextType = DashKitContext; overlayRef = React.createRef(null); - controlsRef = React.createRef(); + controlsRef = React.createRef(null); renderOverlay() { const {overlayControls} = this.props; @@ -68,10 +68,11 @@ class GridItem extends React.PureComponent { unmountOnExit > diff --git a/src/components/GridItem/GridItem.scss b/src/components/GridItem/GridItem.scss index 2f14597..6f79e3c 100644 --- a/src/components/GridItem/GridItem.scss +++ b/src/components/GridItem/GridItem.scss @@ -8,22 +8,22 @@ left: 0; right: 0; border-radius: 3px; - background-color: var(--g-color-base-float); + background-color: var(--g-color-base-generic); border: solid 1px rgba(0, 0, 0, 0.1); - opacity: 0.5; + // opacity: 0.5; &-enter { opacity: 0; will-change: opacity; &-active { - opacity: 0.5; + opacity: 1; transition: opacity 300ms ease-in-out; } } &-exit { - opacity: 0.5; + opacity: 1; will-change: opacity; &-active { @@ -64,6 +64,7 @@ border-radius: 3px; &_editMode { + opacity: 0.5; border-color: transparent; pointer-events: none; } diff --git a/src/components/OverlayControls/OverlayControls.tsx b/src/components/OverlayControls/OverlayControls.tsx index bd4c858..2518d9d 100644 --- a/src/components/OverlayControls/OverlayControls.tsx +++ b/src/components/OverlayControls/OverlayControls.tsx @@ -65,6 +65,7 @@ interface OverlayControlsProps extends OverlayControlsDefaultProps { items?: OverlayControlItem[]; overlayControls?: Record; forwardRef?: React.ForwardedRef; + className?: string; } type PreparedCopyItemOptionsArg = Pick & { @@ -97,9 +98,9 @@ class OverlayControls extends React.Component { size: 'm', }; context!: React.ContextType; - ref = null; + render() { - const {items = [], position} = this.props; + const {items = [], position, className} = this.props; const hasCustomControlsWithWidgets = items.length > 0; const controls = hasCustomControlsWithWidgets @@ -107,7 +108,7 @@ class OverlayControls extends React.Component { : this.renderControls(); return ( -
+
{controls}
); From 9f896cbb910bb97e1cd2a292eb9465f9d343f6c4 Mon Sep 17 00:00:00 2001 From: Sergey Pavlyuk Date: Tue, 30 Jan 2024 20:57:48 +0300 Subject: [PATCH 4/8] feat: css api for main colors and storybook case --- README.md | 23 ++++ src/components/ActionPanel/ActionPanel.scss | 43 +++++-- src/components/ActionPanel/ActionPanel.tsx | 2 +- .../DashKit/__stories__/CssApiShowcase.tsx | 113 ++++++++++++++++++ .../DashKit/__stories__/DashKit.stories.tsx | 4 + .../DashKit/__stories__/DashKitShowcase.tsx | 2 +- src/components/GridItem/GridItem.js | 4 +- src/components/GridItem/GridItem.scss | 51 +++++--- 8 files changed, 216 insertions(+), 26 deletions(-) create mode 100644 src/components/DashKit/__stories__/CssApiShowcase.tsx diff --git a/README.md b/README.md index 00bb8e8..b1a22a4 100644 --- a/README.md +++ b/README.md @@ -325,6 +325,29 @@ type MenuItem = { DashKit.setSettings({menu: [] as Array}); ``` +| Name | Description | +|-:----------------------------------------------|-:---------------------| +| Action panel variables | | +| `--dashkit-action-panel-color` | Background color | +| `--dashkit-action-panel-border-color` | Border color | +| `--dashkit-action-panel-border-radius` | Border radius | +| Action panel item variables | | +| `--dashkit-action-panel-item-color` | Backgroud color | +| `--dashkit-action-panel-item-text-color` | Text color | +| `--dashkit-action-panel-item-color-hover` | Hover backgroud color | +| `--dashkit-action-panel-item-text-color-hover` | Hover text color | +| Overlay variables | | +| `--dashkit-overlay-border-color` | Border color | +| `--dashkit-overlay-color` | Background color | +| `--dashkit-overlay-opacity` | Opacity | +| Grid item variables | | +| `--dashkit-grid-item-edit-opacity` | Opacity | +| `--dashkit-grid-item-border-radius` | Border radius | +| Placeholder variables | | +| `--dashkit-placeholder-color` | Background color | +| `--dashkit-placeholder-opacity` | Opacity | + + ## Development ### Build & watch diff --git a/src/components/ActionPanel/ActionPanel.scss b/src/components/ActionPanel/ActionPanel.scss index c9848cd..ecfa67f 100644 --- a/src/components/ActionPanel/ActionPanel.scss +++ b/src/components/ActionPanel/ActionPanel.scss @@ -1,14 +1,23 @@ .dashkit-action-panel { $show_panel_transform: translateX(-50%) translateY(0); $hide_panel_transform: translateX(-50%) translateY(calc(100% + 20px)); - $selector: #{&}; - background-color: var(--g-color-base-float); + --_--dashkit-action-panel-color: var(--dashkit-action-panel-color, var(--g-color-base-float)); + --_--dashkit-action-panel-border-color: var( + --dashkit-action-panel-border-color, + var(--g-color-base-brand) + ); + --_--dashkit-action-panel-border-radius: var( + --dashkit-action-panel-border-radius, + var(--g-border-radius-xl) + ); + + background-color: var(--_--dashkit-action-panel-color); position: fixed; bottom: 20px; display: flex; - border-radius: 10px; - border: 2px solid var(--g-color-base-brand); + border-radius: var(--_--dashkit-action-panel-border-radius); + border: 2px solid var(--_--dashkit-action-panel-border-color); padding: 8px; gap: 0; left: 50%; @@ -21,7 +30,7 @@ &-active { transform: $show_panel_transform; - transition: transform 300ms ease-in-out; + transition: transform 300ms ease; } } @@ -31,27 +40,45 @@ &-active { transform: $hide_panel_transform; - transition: transform 300ms ease-in-out; + transition: transform 300ms ease; } } &__item { + --_--dashkit-action-panel-item-color: var(--dashkit-action-panel-item-color, transparent); + --_--dashkit-action-panel-item-text-color: var( + --dashkit-action-panel-item-text-color, + var(--g-color-text-primary) + ); + --_--dashkit-action-panel-item-color-hover: var( + --dashkit-action-panel-item-color-hover, + var(--g-color-base-simple-hover) + ); + --_--dashkit-action-panel-item-text-color-hover: var( + --dashkit-action-panel-item-text-color-hover, + var(--g-color-text-primary) + ); + height: 68px; width: 98px; display: flex; flex-direction: column; justify-content: center; align-items: center; - transition: 0.3s background-color ease-in-out; + transition: 300ms color ease-in-out, 300ms background-color ease-in-out; border-radius: 6px; padding: 0 12px; box-sizing: border-box; white-space: nowrap; overflow: hidden; + background-color: var(--_--dashkit-action-panel-item-color); + color: var(--_--dashkit-action-panel-item-text-color); + will-change: color, backgroung-color; &:hover { cursor: pointer; - background-color: var(--g-color-base-simple-hover); + background-color: var(--_--dashkit-action-panel-item-color-hover); + color: var(--_--dashkit-action-panel-item-text-color-hover); } } diff --git a/src/components/ActionPanel/ActionPanel.tsx b/src/components/ActionPanel/ActionPanel.tsx index a92cd6e..c1dfdcd 100644 --- a/src/components/ActionPanel/ActionPanel.tsx +++ b/src/components/ActionPanel/ActionPanel.tsx @@ -23,7 +23,7 @@ export type ActionPanelProps = { const b = cn('dashkit-action-panel'); export const ActionPanel = (props: ActionPanelProps) => { - const isHidden = props.enable; + const isHidden = !props.enable; const nodeRef = React.useRef(null); const content = ( diff --git a/src/components/DashKit/__stories__/CssApiShowcase.tsx b/src/components/DashKit/__stories__/CssApiShowcase.tsx new file mode 100644 index 0000000..a276fcd --- /dev/null +++ b/src/components/DashKit/__stories__/CssApiShowcase.tsx @@ -0,0 +1,113 @@ +import React from 'react'; +import {Icon} from '@gravity-ui/uikit'; +import { + ChartColumn, + Heading, + Layers3Diagonal, + PlugConnection, + Sliders, + TextAlignLeft, +} from '@gravity-ui/icons'; + +import {Demo, DemoRow} from './Demo'; +import {getConfig} from './utils'; +import {DashKit, ActionPanel, MenuItems} from '../../..'; +import i18n from '../../../i18n'; +import {CogIcon} from '../../../icons/CogIcon'; +import {CopyIcon} from '../../../icons/CopyIcon'; +import {DeleteIcon} from '../../../icons/DeleteIcon'; + +export const CssApiShowcase: React.FC = () => { + React.useEffect(() => { + DashKit.setSettings({ + menu: [ + { + id: 'settings', + title: 'Menu setting text', + icon: , + }, + { + id: MenuItems.Copy, + title: 'Menu setting copy', + icon: , + }, + { + id: MenuItems.Delete, + title: i18n('label_delete'), // for language change check + icon: , + className: 'dashkit-overlay-controls__item_danger', + }, + ], + }); + }, []); + + const items = React.useMemo( + () => [ + { + id: 'chart', + icon: , + title: 'Chart', + className: 'test', + qa: 'chart', + }, + { + id: 'selector', + icon: , + title: 'Selector', + qa: 'selector', + }, + { + id: 'text', + icon: , + title: 'Text', + }, + { + id: 'header', + icon: , + title: 'Header', + }, + { + id: 'links', + icon: , + title: 'Links', + }, + { + id: 'tabs', + icon: , + title: 'Tabs', + }, + ], + [], + ); + + return ( + <> + + + + + + + + + ); +}; diff --git a/src/components/DashKit/__stories__/DashKit.stories.tsx b/src/components/DashKit/__stories__/DashKit.stories.tsx index abd6197..4d82138 100644 --- a/src/components/DashKit/__stories__/DashKit.stories.tsx +++ b/src/components/DashKit/__stories__/DashKit.stories.tsx @@ -5,6 +5,7 @@ import {DashKit, DashKitProps} from '../DashKit'; import pluginTitle from '../../../plugins/Title/Title'; import pluginText from '../../../plugins/Text/Text'; import {DashKitShowcase} from './DashKitShowcase'; +import {CssApiShowcase} from './CssApiShowcase'; import {getConfig} from './utils'; import './DashKit.stories.scss'; @@ -69,3 +70,6 @@ export const Default = DefaultTemplate.bind({}); const ShowcaseTemplate: Story = () => ; export const Showcase = ShowcaseTemplate.bind({}); + +const CssApiShowcaseTemplate: Story = () => ; +export const CSS_API = CssApiShowcaseTemplate.bind({}); diff --git a/src/components/DashKit/__stories__/DashKitShowcase.tsx b/src/components/DashKit/__stories__/DashKitShowcase.tsx index e7af8c6..e1ea3dc 100644 --- a/src/components/DashKit/__stories__/DashKitShowcase.tsx +++ b/src/components/DashKit/__stories__/DashKitShowcase.tsx @@ -175,7 +175,7 @@ export class DashKitShowcase extends React.Component<{}, DashKitDemoState> { {this.state.lastAction}
@@ -64,7 +64,7 @@ class GridItem extends React.PureComponent { in={!isHidden} nodeRef={this.controlsRef} classNames={b('controls')} - timeout={500} + timeout={300} unmountOnExit > Date: Tue, 30 Jan 2024 21:42:25 +0300 Subject: [PATCH 5/8] feat: dashkit and action panel toggle animations properties --- src/components/ActionPanel/ActionPanel.tsx | 32 +++++---- src/components/DashKit/DashKit.tsx | 1 + .../DashKit/__stories__/CssApiShowcase.tsx | 2 +- .../DashKit/__stories__/DashKitShowcase.tsx | 21 +++++- src/components/GridItem/GridItem.js | 66 ++++++++++++------- src/hocs/withContext.js | 2 + 6 files changed, 86 insertions(+), 38 deletions(-) diff --git a/src/components/ActionPanel/ActionPanel.tsx b/src/components/ActionPanel/ActionPanel.tsx index c1dfdcd..3248951 100644 --- a/src/components/ActionPanel/ActionPanel.tsx +++ b/src/components/ActionPanel/ActionPanel.tsx @@ -17,13 +17,15 @@ export type ActionPanelItem = { export type ActionPanelProps = { items: ActionPanelItem[]; className?: string; - enable?: boolean; + disable?: boolean; + toggleAnimation?: boolean; }; const b = cn('dashkit-action-panel'); export const ActionPanel = (props: ActionPanelProps) => { - const isHidden = !props.enable; + const isDisabled = props.disable ?? false; + const isAnimated = props.toggleAnimation ?? false; const nodeRef = React.useRef(null); const content = ( @@ -47,15 +49,19 @@ export const ActionPanel = (props: ActionPanelProps) => {
); - return ( - - {content} - - ); + if (isAnimated) { + return ( + + {content} + + ); + } else { + return isDisabled ? null : content; + } }; diff --git a/src/components/DashKit/DashKit.tsx b/src/components/DashKit/DashKit.tsx index b34e0e8..59a96c7 100644 --- a/src/components/DashKit/DashKit.tsx +++ b/src/components/DashKit/DashKit.tsx @@ -22,6 +22,7 @@ interface DashKitGeneralProps { editMode: boolean; draggableHandleClassName?: string; overlayControls?: Record; + editModeAnimation?: boolean; } interface DashKitDefaultProps { diff --git a/src/components/DashKit/__stories__/CssApiShowcase.tsx b/src/components/DashKit/__stories__/CssApiShowcase.tsx index a276fcd..5c9f17f 100644 --- a/src/components/DashKit/__stories__/CssApiShowcase.tsx +++ b/src/components/DashKit/__stories__/CssApiShowcase.tsx @@ -104,7 +104,7 @@ export const CssApiShowcase: React.FC = () => { - + diff --git a/src/components/DashKit/__stories__/DashKitShowcase.tsx b/src/components/DashKit/__stories__/DashKitShowcase.tsx index e1ea3dc..5d44893 100644 --- a/src/components/DashKit/__stories__/DashKitShowcase.tsx +++ b/src/components/DashKit/__stories__/DashKitShowcase.tsx @@ -35,6 +35,7 @@ type DashKitDemoState = { customControlsActionData: number; showCustomMenu: boolean; enableActionPanel: boolean; + enableAnimations: boolean; }; export class DashKitShowcase extends React.Component<{}, DashKitDemoState> { @@ -53,6 +54,7 @@ export class DashKitShowcase extends React.Component<{}, DashKitDemoState> { customControlsActionData: 0, showCustomMenu: true, enableActionPanel: false, + enableAnimations: true, }; private dashKitRef = React.createRef(); @@ -107,6 +109,17 @@ export class DashKitShowcase extends React.Component<{}, DashKitDemoState> { {editMode ? 'Disable editMode' : 'Enable editMode'}
+