dashboard components cleanup and reorganization

- SidebarHeader, SidebarItem, SidebarSeparator components removed and made into sub-components of Sidebar (Sidebar.Header, Sidebar.Item, Sidebar.Separator)
- ContentSidebar improved
- Card component added
pull/67/head
dperolio 3 years ago
parent ea79e4c685
commit e21571cdf4
No known key found for this signature in database
GPG Key ID: 3E9BBAA710D3DDCE

@ -24,8 +24,10 @@ export default class AsyncComponent extends PureComponent {
}
/**
* Creates an AsyncComponent from a promise
* @param {Promise} promise Promise of a React component
* Creates an AsyncComponent from a promise.
* @param {Promise<React.Component>} promise Promise of a React component
* @param {React.Component} [fallback] Fallback Component
* @returns {React.MemoExoticComponent<function(): React.ReactElement>}
*/
static from (promise, fallback) {
return memo(props =>
@ -33,14 +35,33 @@ export default class AsyncComponent extends PureComponent {
);
}
/**
* Creates an AsyncComponent from a module by its displayName.
* @param {string} displayName Module displayName
* @param {React.Component} [fallback] Fallback Component
* @returns {React.MemoExoticComponent<function(): React.ReactElement>}
*/
static fromDisplayName (displayName, fallback) {
return AsyncComponent.from(getModuleByDisplayName(displayName, true), fallback);
}
/**
* Creates an AsyncComponent from a module by its props.
* @param {Function|string} filter Module filter
* @param {React.Component} [fallback] Fallback Component
* @returns {React.MemoExoticComponent<function(): React.ReactElement>}
*/
static fromProps (filter, fallback) {
return AsyncComponent.from(getModule(filter, true), fallback);
}
/**
* Creates an AsyncComponent from a module by its props.
* @param {Function|string} filter Module filter
* @param {string} [prop] Module property
* @param {React.Component} [fallback] Fallback Component
* @returns {React.MemoExoticComponent<function(): React.ReactElement>}
*/
static fetchFromProps (filter, prop, fallback) {
return AsyncComponent.from((async () => (await getModule(filter, true))[prop || filter])(), fallback);
}

@ -0,0 +1,47 @@
import React, { memo } from 'react';
import { Button, Icon } from '..';
export default memo(({ icon, header, disabled, description, buttonText, buttonOnClick, route }) => {
return (
<div className='vz-dashboard-home-features-card-wrapper'>
<div className='vz-dashboard-home-features-card'>
<Icon
name={icon}
className='vz-dashboard-home-features-icon-wrapper'
iconClassName='vz-dashboard-home-features-icon'
size='100%'
/>
<div className='vz-dashboard-home-features-card-header'>
{header}
</div>
<div className='vz-dashboard-home-features-card-body'>
{description}
</div>
<div className='vz-dashboard-home-features-card-footer'>
<Button
className='vz-dashboard-home-features-button'
onClick={evt => {
try {
evt?.persist?.();
if (disabled) {
return;
}
if (buttonOnClick) {
return buttonOnClick(evt);
} else if (route) {
return vizality.api.routes.navigateTo(route);
}
} catch (err) {
}
}}
size={Button.Sizes.LARGE}
>
{buttonText}
</Button>
</div>
</div>
</div>
);
});

@ -1,7 +1,6 @@
import { joinClassNames } from '@vizality/util/dom';
import { getModule } from '@vizality/webpack';
import { useToggle } from '@vizality/hooks';
import { motion } from 'framer-motion';
import React, { memo } from 'react';
import { Tooltip } from '..';
@ -12,29 +11,43 @@ import { Sidebar } from '.';
* @component
* @returns {React.MemoExoticComponent<function(): React.ReactElement>}
*/
export default memo(({ header, side = 'right', children, className }) => {
const [ collapsed, toggleCollapsed ] = useToggle(false);
export default memo(({ header, collapsed, onToggle, side = 'right', children, className }) => {
const [ _collapsed, toggleCollapsed ] = useToggle(collapsed);
const { auto, fade } = getModule('thin', 'scrollerBase');
const { header: headerClass } = getModule('header', 'separator', 'side', 'top');
return (
<motion.div layout className={joinClassNames('vz-dashboard-content-sidebar', className, { test: collapsed })}>
<Tooltip text={collapsed ? 'Expand' : 'Collapse'}>
<div className='vz-dashboard-content-sidebar-expander' onClick={toggleCollapsed} />
<div
className={joinClassNames('vz-dashboard-content-sidebar', className, { test: _collapsed })}
>
<Tooltip text={_collapsed ? 'Expand' : 'Collapse'}>
<div
className='vz-dashboard-content-sidebar-expander'
onClick={() => {
toggleCollapsed();
onToggle?.();
}}
/>
</Tooltip>
<div className='vz-dashboard-content-sidebar-inner'>
<div className='vz-dashboard-content-sidebar-header-wrapper'>
<div className={joinClassNames('vz-dashboard-content-sidebar-header', headerClass)}>
{header}
</div>
<Tooltip text={collapsed ? 'Expand' : 'Collapse'}>
<div className='vz-dashboard-content-sidebar-collapser' onClick={toggleCollapsed} />
<Tooltip text={_collapsed ? 'Expand' : 'Collapse'}>
<div
className='vz-dashboard-content-sidebar-collapser'
onClick={() => {
toggleCollapsed();
onToggle?.();
}}
/>
</Tooltip>
</div>
<div className={joinClassNames('vz-dashboard-content-sidebar-items', auto, fade)}>
{children}
</div>
</div>
</motion.div>
</div>
);
});
@ -43,11 +56,10 @@ export default memo(({ header, side = 'right', children, className }) => {
* @component
* @returns {React.MemoExoticComponent<function(): React.ReactElement>}
*/
export const NavItem = memo(props => {
const { icon, route, selected, onClick, children, className } = props;
export const NavItem = memo(({ icon, route, selected, onClick, children, className, ...other }) => {
return (
<Sidebar.Item
{...props}
{...other}
icon={icon}
route={route}
className={joinClassNames('vz-dashboard-content-sidebar-item-wrapper', className)}

@ -2,7 +2,7 @@ import { AdvancedScrollerAuto, ErrorBoundary } from '@vizality/components';
import { joinClassNames } from '@vizality/util/dom';
import { getModule } from '@vizality/webpack';
import React, { memo } from 'react';
import { AnimateSharedLayout, AnimatePresence, motion } from 'framer-motion';
/**
*
* @component
@ -14,22 +14,18 @@ export default memo(({ className, wrapperClassName, children }) => {
const { content } = getModule('wrappedLayout');
const { base } = getModule('base');
return (
<AnimateSharedLayout>
<AnimatePresence>
<motion.div layout layoutId='dashboard' className={joinClassNames('vz-dashboard', wrapperClassName, pageWrapper, perksModal)}>
<AdvancedScrollerAuto className={`${scroller} vz-dashboard-scroller`}>
<div className={joinClassNames('vz-dashboard-layout', className)}>
<ErrorBoundary
className='vz-dashboard-error-boundary'
headerClassName={joinClassNames('vz-dashboard-content-header', base, content)}
showScene={true}
>
{children}
</ErrorBoundary>
</div>
</AdvancedScrollerAuto>
</motion.div>
</AnimatePresence>
</AnimateSharedLayout>
<div className={joinClassNames('vz-dashboard', wrapperClassName, pageWrapper, perksModal)}>
<AdvancedScrollerAuto className={`${scroller} vz-dashboard-scroller`}>
<div className={joinClassNames('vz-dashboard-layout', className)}>
<ErrorBoundary
className='vz-dashboard-error-boundary'
headerClassName={joinClassNames('vz-dashboard-content-header', base, content)}
showScene={true}
>
{children}
</ErrorBoundary>
</div>
</AdvancedScrollerAuto>
</div>
);
});

@ -1,7 +1,6 @@
import { joinClassNames } from '@vizality/util/dom';
import { Divider, Icon } from '@vizality/components';
import { getModule } from '@vizality/webpack';
import { motion } from 'framer-motion';
import React, { memo } from 'react';
/**
@ -12,14 +11,13 @@ import React, { memo } from 'react';
* @returns {React.MemoExoticComponent<function(): React.ReactElement>}
*/
export const Header = memo(({ icon, header, description, separator }) => {
const { marginBottom40 } = getModule('marginBottom20');
const { headerSubtext } = getModule('headerSubtext');
const { content } = getModule('wrappedLayout');
const { h1 } = getModule('h1', 'h2', 'h3');
const { size24 } = getModule('size24');
const { base } = getModule('base');
return (
<motion.div layout='position' className={joinClassNames('vz-dashboard-section-header-wrapper', marginBottom40)}>
<div className={joinClassNames('vz-dashboard-section-header-wrapper')}>
<div className='vz-dashboard-content-header-inner-wrapper'>
{icon && (
<Icon
@ -33,13 +31,15 @@ export const Header = memo(({ icon, header, description, separator }) => {
{header}
</h2>
</div>
<h4 className={joinClassNames('vz-dashboard-section-header-description', h1, headerSubtext)}>
{description}
</h4>
{description && (
<h4 className={joinClassNames('vz-dashboard-section-header-description', h1, headerSubtext)}>
{description}
</h4>
)}
{separator && (
<Divider />
)}
</motion.div>
</div>
);
});
@ -52,9 +52,9 @@ export const Header = memo(({ icon, header, description, separator }) => {
* @param {...any} [children] Section children
* @returns {React.MemoExoticComponent<function(): React.ReactElement>}
*/
export default memo(({ header, description, icon, separator = true, className, children }) => {
export default memo(({ header, description, icon, separator = true, collapsible = false, className, children }) => {
return (
<div className={joinClassNames('vz-dashboard-section', className)}>
<div className={joinClassNames('vz-dashboard-section', className)} vz-collapsible={collapsible && ''}>
{header && (
<Header header={header} description={description} icon={icon} separator={separator} />
)}

@ -51,7 +51,7 @@ const ItemContextMenu = memo(({ path }) => {
* @component
* @returns {React.MemoExoticComponent<function(): React.ReactElement>}
*/
export const Item = memo(({ icon, children, route, selected, disabled, onContextMenu, tooltip, tooltipPosition, onClick, className }) => {
export const Item = memo(({ icon, children, route, selected, disabled, onContextMenu, tooltip, tooltipPosition = 'right', onClick, className }) => {
selected = typeof selected === 'boolean' ? selected : vizality.api.routes.getLocation()?.pathname?.startsWith(route);
const { categoryItem, selectedCategoryItem, itemInner } = getModule('categoryItem', 'selectedCategoryItem', 'itemInner');
const { container, selected: selectedClass, wrappedLayout, layout, avatar, content, nameAndDecorators, name, wrappedName, clickable } = getModule('wrappedLayout', 'wrappedName', 'layout');
@ -59,11 +59,12 @@ export const Item = memo(({ icon, children, route, selected, disabled, onContext
<Clickable
onContextMenu={evt => {
try {
evt?.persist?.();
if (disabled) {
return;
}
if (onContextMenu) {
return onContextMenu();
return onContextMenu(evt);
} else if (route) {
return openContextMenu(evt, () => <ItemContextMenu path={route} />);
}
@ -71,13 +72,14 @@ export const Item = memo(({ icon, children, route, selected, disabled, onContext
}
}}
onClick={() => {
onClick={evt => {
try {
evt?.persist?.();
if (disabled) {
return;
}
if (onClick) {
return onClick();
return onClick(evt);
} else if (route) {
return vizality.api.routes.navigateTo(route);
}
@ -99,7 +101,7 @@ export const Item = memo(({ icon, children, route, selected, disabled, onContext
iconClassName='vz-dashboard-sidebar-item-icon'
name={icon}
tooltip={tooltip}
tooltipPosition={tooltipPosition || 'right'}
tooltipPosition={tooltipPosition}
/>
<div className={content}>
<div className={nameAndDecorators}>
@ -120,7 +122,6 @@ export const Item = memo(({ icon, children, route, selected, disabled, onContext
*/
export default memo(({ header, className, children }) => {
const [ collapsed, setCollapsed ] = vizality.api.settings.useSetting('dashboardSidebarCollapse', false);
return (
<ErrorBoundary>
<AdvancedScrollerThin className={joinClassNames('vz-dashboard-sidebar-inner', className)}>
@ -155,36 +156,82 @@ export default memo(({ header, className, children }) => {
? children
: (
<>
<Item icon='House' route='/vizality/dashboard' tooltip={collapsed && 'Home'}>
<Item
icon='House'
route='/vizality/dashboard'
tooltip={collapsed && 'Home'}
>
Home
</Item>
<Item icon='Gear' route='/vizality/settings' tooltip={collapsed && 'Settings'}>
<Item
icon='Gear'
route='/vizality/settings'
tooltip={collapsed && 'Settings'}
>
Settings
</Item>
<Item icon='Plugin' route='/vizality/plugins' tooltip={collapsed && 'Plugins'}>
<Item
icon='Plugin'
route='/vizality/plugins'
tooltip={collapsed && 'Plugins'}
/**
* In order to match /vizality/plugins/ and /vizality/plugin/ routes
*/
selected={(/\/vizality\/plugin(s)?/).test(vizality.api.routes.getLocation()?.pathname)}
>
Plugins
</Item>
<Item icon='Theme' route='/vizality/themes' tooltip={collapsed && 'Themes'}>
<Item
icon='Theme'
route='/vizality/themes'
tooltip={collapsed && 'Themes'}
/**
* In order to match /vizality/themes/ and /vizality/theme/ routes
*/
selected={(/\/vizality\/theme(s)?/).test(vizality.api.routes.getLocation()?.pathname)}
>
Themes
</Item>
<Item icon='Scissors' route='/vizality/snippets' tooltip={collapsed && 'Snippets'} disabled>
<Item
icon='Scissors'
route='/vizality/snippets'
tooltip={collapsed && 'Snippets'}
disabled
>
Snippets
</Item>
{vizality.manager.builtins.isEnabled('quick-code') && (
<Item icon='Compose' route='/vizality/quick-code' tooltip={collapsed && 'Quick Code'}>
<Item
icon='Compose'
route='/vizality/quick-code'
tooltip={collapsed && 'Quick Code'}
>
Quick Code
</Item>
)}
{/* @todo Addon Guidelines, Publish an Addon, Get Verified, Documentation, VSCode Snippets, Manifest Generator */}
<Separator />
<Item icon='UnknownUser' route='/vizality/development' tooltip={collapsed && 'Development'} disabled>
<Item
icon='UnknownUser'
route='/vizality/development'
tooltip={collapsed && 'Development'}
disabled
>
Development
</Item>
<Separator />
<Item icon='CloudDownload' route='/vizality/updater' tooltip={collapsed && 'Updater'}>
<Item
icon='CloudDownload'
route='/vizality/updater'
tooltip={collapsed && 'Updater'}
>
Updater
</Item>
<Item icon='MoreInfo' route='/vizality/changelog' tooltip={collapsed && 'Changelog'}>
<Item
icon='MoreInfo'
route='/vizality/changelog'
tooltip={collapsed && 'Changelog'}
>
Changelog
</Item>
</>

@ -1,17 +0,0 @@
/**
*
* @component
*/
import { DiscoverySidebarHeader } from '@vizality/components';
import { joinClassNames } from '@vizality/util/dom';
import React, { memo } from 'react';
export default memo(({ text, children, className }) => {
return (
<div className={joinClassNames('vz-dashboard-sidebar-header', className)}>
<DiscoverySidebarHeader text={text} />
{children}
</div>
);
});

@ -1,67 +0,0 @@
/**
* Dashboard sidebar navigation item.
* @component
*/
import { ContextMenu, Clickable, Icon } from '@vizality/components';
import { getModule, contextMenu } from '@vizality/webpack';
import { joinClassNames } from '@vizality/util/dom';
import React, { memo, useState } from 'react';
const { closeContextMenu, openContextMenu } = contextMenu;
/**
* Context menu for dashboard sidebar navigation items.
* @component
*/
const ItemContextMenu = memo(({ path }) => {
return (
<ContextMenu.Menu navId='dashboard-item' onClose={closeContextMenu}>
<ContextMenu.Item
id='copy-link'
label='Copy Link'
action={() => DiscordNative?.clipboard?.copy(`<vizality:/${path.replace('vizality/', '')}>`)}
/>
</ContextMenu.Menu>
);
});
export default memo(({ icon, text, route, selected, disabled, onContextMenu, tooltip }) => {
const [ isSelected ] = useState(typeof selected === 'boolean' ? selected : vizality.api.routes.getLocation()?.pathname?.startsWith(route));
const { categoryItem, selectedCategoryItem, itemInner } = getModule('categoryItem', 'selectedCategoryItem', 'itemInner');
const { container, selected: selectedClass, wrappedLayout, layout, avatar, content, nameAndDecorators, name, wrappedName, clickable } = getModule('wrappedLayout', 'wrappedName', 'layout');
return (
<Clickable
onContextMenu={evt => !disabled && (
onContextMenu || (
openContextMenu(evt, () => <ItemContextMenu path={route} />)
)
)}
onClick={() => !disabled && vizality.api.routes.navigateTo(route)}
className={joinClassNames('vz-dashboard-sidebar-item', categoryItem, container, {
disabled,
[`${selectedClass} ${selectedCategoryItem}`]: isSelected,
[clickable]: !isSelected
})}
>
<div className={joinClassNames('vz-dashboard-sidebar-item-inner', itemInner, layout, wrappedLayout)}>
<Icon
className={joinClassNames('vz-dashboard-sidebar-item-icon-wrapper', avatar)}
iconClassName='vz-dashboard-sidebar-item-icon'
name={icon}
tooltip={tooltip}
tooltipPosition='right'
/>
<div className={content}>
<div className={nameAndDecorators}>
<div className={joinClassNames(name, wrappedName)}>
{text}
</div>
</div>
</div>
</div>
</Clickable>
);
});

@ -1,11 +0,0 @@
/**
* Dashboard sidebar navigation item separator.
* @component
*/
import { joinClassNames } from '@vizality/util/dom';
import React, { memo } from 'react';
export default memo(({ className }) => (
<div className={joinClassNames('vz-dashboard-sidebar-separator', className)} />
));

@ -0,0 +1,14 @@
import { joinClassNames } from '@vizality/util/dom';
import React, { memo } from 'react';
/**
*
* @component
*/
export default memo(({ color, className, ...other }) => {
return <div class={joinClassNames('vz-wave-divider', className)} style={{ color }}>
<svg class='vz-wave-divider-svg' xmlns='http://www.w3.org/2000/svg' viewBox="0 0 1440 100" {...other}>
<path class='vz-wave-divider-svg-path' fill='currentColor' d='M826.337463,25.5396311 C670.970254,58.655965 603.696181,68.7870267 447.802481,35.1443383 C293.342778,1.81111414 137.33377,1.81111414 0,1.81111414 L0,150 L1920,150 L1920,1.81111414 C1739.53523,-16.6853983 1679.86404,73.1607868 1389.7826,37.4859505 C1099.70117,1.81111414 981.704672,-7.57670281 826.337463,25.5396311 Z' />
</svg>
</div>;
});

@ -1,11 +1,10 @@
export { default as SidebarSeparator } from './SidebarSeparator';
export { default as ContentSidebar } from './ContentSidebar';
export { default as SidebarHeader } from './SidebarHeader';
export { default as SidebarItem } from './SidebarItem';
export { default as WaveDivider } from './WaveDivider';
export { default as Content } from './Content';
export { default as Section } from './Section';
export { default as Sidebar } from './Sidebar';
export { default as Layout } from './Layout';
export { default as Card } from './Card';
import { Header as SidebarHeader, Item as SidebarItem, Separator as SidebarSeparator } from './Sidebar';
import { Header as SectionHeader } from './Section';

Loading…
Cancel
Save