console: fix bugs for login and upgrade header style

[PLAT-437]: https://hasurahq.atlassian.net/browse/PLAT-437?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/7947
GitOrigin-RevId: 9ac807cdb038818d45b0678ac0d98157ee292fab
This commit is contained in:
Nicolas Inchauspe 2023-02-15 18:15:19 +01:00 committed by hasura-bot
parent 565176c155
commit 96856f5de3
14 changed files with 343 additions and 196 deletions

View File

@ -6,6 +6,7 @@ import { DocsContainer, DocsPage } from '@storybook/addon-docs';
import { initialize, mswDecorator } from 'msw-storybook-addon';
import { DARK_MODE_EVENT_NAME } from 'storybook-dark-mode';
import theme from './theme';
import 'react-loading-skeleton/dist/skeleton.css';
import '../src/lib/theme/tailwind.css';
import { store } from '../src/lib/store';
import '../src/lib/components/Common/Common.module.scss';

View File

@ -23,6 +23,7 @@ export { prefetchOnboardingData } from '../lib/features/CloudOnboarding/Onboardi
export { default as PageNotFound } from '../lib/components/Error/PageNotFound';
export * from '../lib/new-components/Button/';
export * from '../lib/new-components/Tooltip/';
export * from '../lib/new-components/Badge/';
export { CONSOLE_ADMIN_SECRET } from '../lib/components/AppState';
export { default as dataHeaders } from '../lib/components/Services/Data/Common/Headers';
export { handleMigrationErrors } from '../lib/components/Services/Data/TableModify/ModifyActions';

View File

@ -18,7 +18,7 @@ import hasuraEELogo from './black-logo-ee.svg';
const validationSchema = z.object({
password: z.string().min(1, { message: 'Please add password' }),
savePassword: z.enum(['checked']).array(),
savePassword: z.enum(['checked']).array().optional(),
});
const Login: React.FC<ConnectInjectedProps> = ({ dispatch, children }) => {
@ -107,7 +107,7 @@ const Login: React.FC<ConnectInjectedProps> = ({ dispatch, children }) => {
options={[
{
value: 'checked',
label: 'Remember in this browser',
label: 'Remember on the browser',
},
]}
/>

View File

@ -3,8 +3,14 @@ import { Link } from 'react-router';
import { getPathRoot } from '@/components/Common/utils/urlUtils';
import { Tooltip } from '@/new-components/Tooltip';
import { IconType } from 'react-icons';
import styles from './Main.module.scss';
import { isBlockActive } from './Main.utils';
import clsx from 'clsx';
export const linkStyle =
'flex items-stretch gap-2 text-white font-bold rounded py-2 px-3 hover:!text-white active:text-primary focus:text-primary bg-transparent hover:bg-slate-900 !no-underline cursor-pointer';
export const activeLinkStyle =
'text-primary hover:!text-primary focus:!text-primary visited:!text-primary bg-slate-900';
export const itemContainerStyle = 'h-full flex items-center ml-xs';
interface NavItemProps {
title: string;
@ -33,22 +39,22 @@ const HeaderNavItem: React.VFC<NavItemProps> = ({
pathname,
});
const className = isCurrentBlockActive ? styles.navSideBarActive : '';
return (
<li>
<Tooltip side="bottom" tooltipContentChildren={tooltipText}>
<Tooltip
className="h-full"
side="bottom"
tooltipContentChildren={tooltipText}
>
<Link
className={className}
className={clsx(linkStyle, isCurrentBlockActive && activeLinkStyle)}
to={appPrefix + path}
data-test={`${title.toLowerCase()}-tab-link`}
>
<div className={styles.iconCenter} data-test={block}>
{React.createElement(icon, {
'aria-hidden': true,
})}
</div>
<p className="uppercase">{title}</p>
<span className="text-sm self-baseline" data-test={block}>
{icon}
</span>
<span className="uppercase text-left">{title}</span>
</Link>
</Tooltip>
</li>

View File

@ -1,11 +1,14 @@
import React from 'react';
import clsx from 'clsx';
import {
FaCloud,
FaCog,
FaCogs,
FaDatabase,
FaInfoCircle,
FaExclamationCircle,
FaExclamationTriangle,
FaFlask,
FaPlug,
} from 'react-icons/fa';
@ -25,7 +28,6 @@ import {
getDataSourceBaseRoute,
} from '../Common/utils/routesUtils';
import { getPathRoot } from '../Common/utils/urlUtils';
import WarningSymbol from '../Common/WarningSymbol/WarningSymbol';
import _push from '../Services/Data/push';
import {
emitProClickedEvent,
@ -49,7 +51,13 @@ import {
setLoveConsentState,
setProClickState,
} from './utils';
import HeaderNavItem from './HeaderNavItem';
import HeaderNavItem, {
linkStyle,
itemContainerStyle,
activeLinkStyle,
} from './HeaderNavItem';
import { Tooltip } from './../../new-components/Tooltip';
import { Badge } from './../../new-components/Badge';
export const updateRequestHeaders = props => {
const { requestHeaders, dispatch } = props;
@ -295,19 +303,19 @@ class Main extends React.Component {
return mainContent;
};
const getMetadataStatusIcon = () => {
const getMetadataStatusIcon = pathname => {
if (metadata.inconsistentObjects.length === 0) {
return <FaCog className={styles.question} />;
return <FaCog />;
}
return (
<div className={styles.question}>
<div className="relative">
<FaCog />
<div className={styles.overlappingExclamation}>
<div className={styles.iconWhiteBackground} />
<div>
<FaExclamationCircle />
</div>
<div className="absolute -top-2 left-2 ">
<FaExclamationCircle
className="bg-white rounded-full"
color="#d9534f"
/>
</div>
</div>
);
@ -318,22 +326,24 @@ class Main extends React.Component {
if (!globals.isAdminSecretSet) {
adminSecretHtml = (
<div className={styles.secureSection}>
<Tooltip
side="bottom"
tooltipContentChildren={`This graphql endpoint is public and you should add an ${globals.adminSecretLabel}`}
>
<div className={itemContainerStyle}>
<a
className={clsx(linkStyle)}
href="https://hasura.io/docs/latest/deployment/securing-graphql-endpoint/"
target="_blank"
rel="noopener noreferrer"
>
<WarningSymbol
tooltipText={tooltips.secureEndpoint}
tooltipPlacement={'left'}
customStyle={styles.secureSectionSymbol}
/>
<span className={styles.secureSectionText}>
<Badge color="yellow">
<FaExclamationTriangle />
&nbsp;Secure your endpoint
</span>
</Badge>
</a>
</div>
</Tooltip>
);
}
return adminSecretHtml;
@ -349,24 +359,22 @@ class Main extends React.Component {
metadata={metadata.metadataObject}
/>
<div className={styles.flexRow}>
<div className={styles.sidebar}>
<div className={styles.header_logo_wrapper}>
<div className={styles.logoParent}>
<div className={styles.logo}>
<div className="font-sans bg-slate-700 text-slate-100 flex h-16">
<div className="flex gap-1 flex-grow">
<div className="px-5 py-2 flex items-center gap-3">
<Link to="/">
<img className="img img-responsive" src={logo} />
<img className="w-24" src={logo} />
</Link>
<Link to="/">
<div className="text-white text-xs max-w-[128px]">
{serverVersion}
</div>
</Link>
</div>
<Link to="/">
<div className={styles.project_version}>{serverVersion}</div>
</Link>
</div>
</div>
<div className={styles.header_items}>
<ul className={styles.sidebarItems}>
<ul className="flex gap-2">
<HeaderNavItem
title="API"
icon={FaFlask}
icon={<FaFlask aria-hidden="true" />}
tooltipText={tooltips.apiExplorer}
path="/api/api-explorer"
appPrefix={appPrefix}
@ -375,7 +383,7 @@ class Main extends React.Component {
/>
<HeaderNavItem
title="Data"
icon={FaDatabase}
icon={<FaDatabase aria-hidden="true" />}
tooltipText={tooltips.data}
path={getDataPath()}
appPrefix={appPrefix}
@ -383,7 +391,7 @@ class Main extends React.Component {
/>
<HeaderNavItem
title="Actions"
icon={FaCogs}
icon={<FaCogs aria-hidden="true" />}
tooltipText={tooltips.actions}
path="/actions/manage/actions"
appPrefix={appPrefix}
@ -391,7 +399,7 @@ class Main extends React.Component {
/>
<HeaderNavItem
title="Remote Schemas"
icon={FaPlug}
icon={<FaPlug aria-hidden="true" />}
tooltipText={tooltips.remoteSchema}
path="/remote-schemas/manage/schemas"
appPrefix={appPrefix}
@ -399,7 +407,7 @@ class Main extends React.Component {
/>
<HeaderNavItem
title="Events"
icon={FaCloud}
icon={<FaCloud aria-hidden="true" />}
tooltipText={tooltips.events}
path="/events/data/manage"
appPrefix={appPrefix}
@ -409,30 +417,42 @@ class Main extends React.Component {
</div>
<div
id="dropdown_wrapper"
className={`${styles.clusterInfoWrapper} ${
className={clsx(
'flex gap-2 justify-end items-stretch relative mr-4',
this.state.isDropdownOpen ? 'open' : ''
}`}
)}
>
{getAdminSecretSection()}
<div className={itemContainerStyle}>
<Link
className={clsx(
linkStyle,
currentActiveBlock === 'settings' && activeLinkStyle
)}
to="/settings"
>
<span className="text-sm self-baseline">
{getMetadataStatusIcon()}
</span>
<span className="uppercase text-left">Settings</span>
</Link>
</div>
{/* Compensate legacy styles directly with style attribute */}
<div className={styles.proWrapper} style={{ padding: '0' }}>
<div className={itemContainerStyle}>
<div
className={`${styles.headerRightNavbarBtn} ${styles.proWrapper}`}
className={clsx(linkStyle, isPopUpOpen && activeLinkStyle)}
onClick={this.onProIconClick}
>
<span
className={`
${isProClicked ? styles.proNameClicked : styles.proName}
${isPopUpOpen ? styles.navActive : ''}`}
>
CLOUD
<span className="text-sm self-baseline">
<FaInfoCircle />
</span>
<span className="uppercase text-left">CLOUD</span>
</div>
</div>
{isPopUpOpen && <ProPopup toggleOpen={this.toggleProPopup} />}
</div>
<Link to="/settings">
<div className={styles.headerRightNavbarBtn}>
{getMetadataStatusIcon()}
{getSettingsSelectedMarker(pathname)}
</div>
</Link>
<Help isSelected={currentActiveBlock === 'support'} />
<NotificationSection
isDropDownOpen={this.state.isDropdownOpen}

View File

@ -1005,7 +1005,7 @@
}
.help_dropdown_menu {
top: 43px;
top: 49px;
right: 53px;
background-color: transparent;
box-shadow: none;
@ -1051,7 +1051,7 @@
}
.dropdown_menu {
top: 43px;
top: 49px;
right: 0px;
background-color: transparent;
box-shadow: none;
@ -1315,6 +1315,7 @@
}
.closeNotificationIcon {
color: #a8a8a8;
user-select: none;
font-size: 16px;
top: 52px;

View File

@ -30,6 +30,12 @@ import { HASURA_COLLABORATOR_TOKEN } from '../../constants';
import { StyledText } from '../UIKit/atoms/Typography/Typography';
import { LS_KEYS } from '../../utils/localStorage';
import { ConsoleState, NotificationsState } from '../../telemetry/state';
import {
linkStyle,
activeLinkStyle,
itemContainerStyle,
} from './HeaderNavItem';
import clsx from 'clsx';
const getDateString = (date: NotificationDate) => {
if (!date) {
@ -721,22 +727,28 @@ const HasuraNotifications: React.FC<
return (
<>
<div className={itemContainerStyle}>
<div
className={`${styles.shareSection} ${styles.headerRightNavbarBtn} ${
isDropDownOpen ? styles.opened : ''
} dropdown-toggle`}
className={clsx(
'dropdown-toggle',
linkStyle,
isDropDownOpen ? styles.opened + ' ' + activeLinkStyle : ''
)}
aria-expanded="false"
onClick={onClickNotificationButton}
ref={wrapperRef}
>
<span className="text-sm">
<FaBell className={styles.bellIcon} />
<ToReadBadge
numberNotifications={numberNotifications}
show={showBadge || !!fixedVersion}
/>
</span>
</div>
</div>
<Box
className={`dropdown-menu ${styles.consoleNotificationPanel}`}
className={`dropdown-menu absolute ${styles.consoleNotificationPanel}`}
ref={dropDownRef}
>
<Flex

View File

@ -1,20 +1,26 @@
import clsx from 'clsx';
import React from 'react';
import { FaQuestionCircle } from 'react-icons/fa';
import { Link } from 'react-router';
import styles from '../Main.module.scss';
import {
activeLinkStyle,
itemContainerStyle,
linkStyle,
} from '../HeaderNavItem';
export const Help = ({ isSelected }: { isSelected: boolean }) => {
return (
<Link to="/support/forums/">
<div className={styles.headerRightNavbarBtn}>
HELP
{isSelected ? (
<span
className={styles.selected}
style={{ width: '90%', marginLeft: '5%' }}
/>
) : null}
</div>
<div className={itemContainerStyle}>
<Link
id="help"
className={clsx(linkStyle, isSelected && activeLinkStyle)}
to="/support/forums/"
>
<span className="text-sm">
<FaQuestionCircle />
</span>
<span className="uppercase">HELP</span>
</Link>
</div>
);
};

View File

@ -14,7 +14,7 @@ export function LinkButton(props: LinkButtonProps) {
<a href={url} target="_blank" rel="noopener noreferrer">
<Button
mode="default"
className="mr-sm bg-none bg-transparent border-red-500"
className="mr-sm bg-none !bg-transparent border-red-500"
icon={icon}
iconPosition={iconPosition}
>

View File

@ -32,7 +32,7 @@ const ClearButton: React.VFC<{ fieldName: string; className: string }> = ({
return (
<Button
className={clsx(
'border-0 bg-transparent bg-none shadow-none active:opacity-75 pointer-events-auto',
'border-0 !bg-transparent bg-none shadow-none active:opacity-75 pointer-events-auto',
className
)}
onClick={() => setValue(fieldName, '')}

View File

@ -1,9 +1,15 @@
import styles from './Main.module.scss';
import { Link } from 'react-router';
import React from 'react';
import { Link } from 'react-router';
import clsx from 'clsx';
import { Tooltip } from '@hasura/console-oss';
export const linkStyle =
'flex items-stretch gap-2 text-white font-bold rounded py-2 px-3 hover:!text-white active:text-primary focus:text-primary bg-transparent hover:bg-slate-900 !no-underline cursor-pointer';
export const activeLinkStyle =
'text-primary hover:!text-primary focus:!text-primary visited:!text-primary bg-slate-900';
export const itemContainerStyle = 'h-full flex items-center ml-xs';
const HeaderNavItem = ({
title,
icon,
@ -14,10 +20,8 @@ const HeaderNavItem = ({
currentActiveBlock,
isDefault = false,
}) => {
const className =
currentActiveBlock === itemPath || (isDefault && currentActiveBlock === '')
? styles.navSideBarActive
: '';
const isActive =
currentActiveBlock === itemPath || (isDefault && currentActiveBlock === '');
return (
<li>
@ -25,16 +29,17 @@ const HeaderNavItem = ({
id={tooltipText}
side="bottom"
tooltipContentChildren={tooltipText}
className="h-full"
>
<Link
className={className}
className={clsx(linkStyle, isActive && activeLinkStyle)}
data-test={`${title.toLowerCase()}-tab-link`}
to={appPrefix + linkPath}
>
<div className={styles.iconCenter} data-test={itemPath}>
<span className="text-sm self-baseline" data-test={itemPath}>
{icon}
</div>
<p className="uppercase">{title}</p>
</span>
<span className="uppercase text-left">{title}</span>
</Link>
</Tooltip>
</li>

View File

@ -12,17 +12,24 @@ import {
FaCloud,
FaPlug,
FaChartLine,
FaQuestionCircle,
FaServer,
} from 'react-icons/fa';
import clsx from 'clsx';
import HeaderNavItem from './HeaderNavItem';
import HeaderNavItem, {
linkStyle,
activeLinkStyle,
itemContainerStyle,
} from './HeaderNavItem';
import Dropdown from 'react-bootstrap/lib/Dropdown';
import MenuItem from 'react-bootstrap/lib/MenuItem';
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
import globals from '../../Globals';
import 'react-toggle/style.css';
import { Spinner } from '@hasura/console-oss';
import {
Badge,
NotificationSection,
Onboarding,
CloudOnboarding,
@ -363,29 +370,19 @@ class Main extends React.Component {
return mainContent;
};
const getMetadataSelectedMarker = () => {
let metadataSelectedMarker = null;
if (currentActiveBlock === 'settings') {
metadataSelectedMarker = <span className={styles.selected} />;
}
return metadataSelectedMarker;
};
const getMetadataIcon = () => {
if (metadata.inconsistentObjects.length === 0) {
return <FaCog className={styles.question} />;
return <FaCog />;
}
return (
<div className={styles.question}>
<div className="relative">
<FaCog />
<div className={styles.overlappingExclamation}>
<div className={styles.iconWhiteBackground} />
<div>
<FaExclamationCircle />
</div>
<div className="absolute -top-2 left-2 ">
<FaExclamationCircle
className="bg-white rounded-full"
color="#d9534f"
/>
</div>
</div>
);
@ -396,20 +393,24 @@ class Main extends React.Component {
if (!globals.isAdminSecretSet) {
adminSecretHtml = (
<div className={styles.secureSection}>
<Tooltip
side="left"
side="bottom"
tooltipContentChildren={`This graphql endpoint is public and you should add an ${globals.adminSecretLabel}`}
>
<div className={itemContainerStyle}>
<a
className={clsx(linkStyle)}
href="https://hasura.io/docs/latest/deployment/securing-graphql-endpoint/"
target="_blank"
rel="noopener noreferrer"
>
<FaExclamationTriangle className={styles.padd_small_right} />
<Badge color="yellow">
<FaExclamationTriangle />
&nbsp;Secure your endpoint
</Badge>
</a>
</Tooltip>
</div>
</Tooltip>
);
}
return adminSecretHtml;
@ -458,45 +459,79 @@ class Main extends React.Component {
const renderLogout = () => {
// userRole is only being set for team console
return extendedGlobals.consoleType !== 'cloud' ? (
<Dropdown
id="dropdown-custom-menu"
className={`${styles.dropDownContainer} ${styles.flexCenterContainer}`}
<div className={itemContainerStyle}>
<DropdownMenu.Root>
{/* Compensate legacy styles directly with style attribute */}
<DropdownMenu.Trigger
noCaret
className={clsx(
linkStyle,
'data-state-open:bg-slate-900 data-state-closed:!text-white data-state-open:!text-primary'
)}
style={{
border: 'none',
}}
>
<span className="text-sm self-baseline">
<FaUser aria-hidden="true" />
</span>
</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content
className="max-w-md -translate-x-6 translate-y-2 data-state-open:animate-dropdownMenuContentOpen data-state-closed:animate-dropdownMenuContentClose"
sideOffset={8}
>
<DropdownMenu.Item
className="shadow-lg bg-white hover:bg-slate-100 p-4 rounded overflow-hidden cursor-pointer"
onSelect={this.logoutCollaboratorWorkFlow.bind(this)}
>
<Dropdown.Toggle noCaret className={styles.userBtn}>
<FaUser aria-hidden="true" className="text-white" />
</Dropdown.Toggle>
<Dropdown.Menu className={styles.dropdownUl}>
<MenuItem onClick={this.logoutCollaboratorWorkFlow.bind(this)}>
<img
className={styles.navBarIcon}
src={logoutIcon}
alt={'logout'}
/>{' '}
Logout
</MenuItem>
</Dropdown.Menu>
</Dropdown>
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu.Root>
</div>
) : null;
};
const renderProjectInfo = () => {
const detailsPath = `${window.location.protocol}//${window.location.host}/project/${globals.hasuraCloudProjectId}/details`;
return window.__env.consoleType === 'cloud' ? (
<span className={styles.projectInfo}>
<img src={projectImg} />
<a href={detailsPath} target="_blank" rel="noopener noreferrer">
{globals.projectName}
<div className={clsx(itemContainerStyle, 'ml-0')}>
<a
className={linkStyle}
href={detailsPath}
target="_blank"
rel="noopener noreferrer"
>
<Badge>
<FaServer />
&nbsp;
<span>{globals.projectName}</span>
</Badge>
</a>
</span>
</div>
) : null;
};
const renderMetadataIcon = () => (
<Link to={getConsolidatedPath('/settings', '/settings', accessState)}>
<div className={styles.headerRightNavbarBtn}>
{getMetadataIcon()}
{getMetadataSelectedMarker()}
</div>
<div className={itemContainerStyle}>
<Link
className={clsx(
linkStyle,
currentActiveBlock === 'settings' && activeLinkStyle
)}
to={getConsolidatedPath('/settings', '/settings', accessState)}
>
<span className="text-sm self-baseline">{getMetadataIcon()}</span>
<span className="uppercase text-left">Settings</span>
</Link>
</div>
);
const getLogoSrc = () => {
@ -510,25 +545,23 @@ class Main extends React.Component {
return (
<div className={styles.container}>
<div className={styles.flexRow}>
<div className={styles.sidebar}>
<div className={styles.header_logo_wrapper}>
<div className={styles.logoParent}>
<div className={styles.logo}>
<div className="font-sans bg-slate-700 text-slate-100 flex h-16">
<div className="flex gap-1 flex-grow">
<div className="px-5 py-2 flex items-center gap-3">
<Link
to={accessState.hasGraphQLAccess ? '/' : '/access-denied'}
>
<img className="img img-responsive" src={getLogoSrc()} />
<img className="w-24" src={getLogoSrc()} />
</Link>
</div>
<Link
to={accessState.hasGraphQLAccess ? '/' : '/access-denied'}
>
<div className={styles.project_version}>{serverVersion}</div>
<div className="text-white text-xs max-w-[128px]">
{serverVersion}
</div>
</Link>
</div>
</div>
<div className={styles.header_items}>
<ul className={styles.sidebarItems}>
<ul className="flex gap-2">
<HeaderNavItem
title="API"
icon={<FaFlask aria-hidden="true" />}
@ -600,21 +633,28 @@ class Main extends React.Component {
</div>
<div
id="dropdown_wrapper"
className={`${styles.clusterInfoWrapper} ${
className={clsx(
'flex gap-2 justify-end items-stretch relative mr-4',
this.state.isDropdownOpen ? 'open' : ''
}`}
)}
>
{getAdminSecretSection()}
{renderProjectInfo()}
{renderMetadataIcon()}
<div className={itemContainerStyle}>
<a
id="help"
className={clsx(linkStyle)}
href={'https://hasura.io/help'}
target="_blank"
rel="noopener noreferrer"
>
<div className={styles.headerRightNavbarBtn}>HELP</div>
<span className="text-sm self-baseline">
<FaQuestionCircle />
</span>
<span className="uppercase text-left">HELP</span>
</a>
</div>
{serverVersion &&
accessState &&
'hasDataAccess' in accessState &&

View File

@ -238,6 +238,7 @@
}
.clusterInfoWrapper {
align-items: stretch;
width: auto;
margin-left: auto;
display: flex;
@ -1000,6 +1001,7 @@
.headerRightNavbarBtn {
display: flex;
align-items: center;
height: 100%;
padding: 15px;
position: relative;
color: #fff;

View File

@ -1,4 +1,31 @@
const colors = require('tailwindcss/colors');
const plugin = require('tailwindcss/plugin');
function dataStateVariant(state, { addVariant, e }) {
addVariant(`data-state-${state}`, ({ modifySelectors, separator }) => {
modifySelectors(({ className }) => {
return `.${e(
`data-state-${state}${separator}${className}`
)}[data-state='${state}']`;
});
});
addVariant(`group-data-state-${state}`, ({ modifySelectors, separator }) => {
modifySelectors(({ className }) => {
return `.group[data-state='${state}'] .${e(
`group-data-state-${state}${separator}${className}`
)}`;
});
});
addVariant(`peer-data-state-${state}`, ({ modifySelectors, separator }) => {
modifySelectors(({ className }) => {
return `.peer[data-state='${state}'] ~ .${e(
`peer-data-state-${state}${separator}${className}`
)}`;
});
});
}
module.exports = {
safelist: {
@ -155,6 +182,22 @@ module.exports = {
transform: `translateY(-100%)`,
},
},
dropdownMenuContentOpen: {
from: {
opacity: '0',
},
to: {
opacity: '1',
},
},
dropdownMenuContentClose: {
from: {
opacity: '1',
},
to: {
opacity: '0',
},
},
},
animation: {
collapsibleContentOpen: 'collapsibleContentOpen 300ms ease-out',
@ -168,6 +211,8 @@ module.exports = {
'slideLeftAndFade 400ms cubic-bezier(0.16, 1, 0.3, 1)',
notificationOpen: 'notificationOpen 300ms ease-in-out',
notificationClose: 'notificationClose 300ms ease-in-out',
dropdownMenuContentOpen: 'dropdownMenuContentOpen 100ms ease-in',
dropdownMenuContentClose: 'dropdownMenuContentClose 100ms ease-out',
},
},
},
@ -175,5 +220,13 @@ module.exports = {
require('@tailwindcss/typography'),
require('@tailwindcss/forms'),
require('tailwindcss-radix')(),
plugin(helpers => {
// variants that help styling Radix-UI components
dataStateVariant('open', helpers);
dataStateVariant('closed', helpers);
dataStateVariant('on', helpers);
dataStateVariant('checked', helpers);
dataStateVariant('unchecked', helpers);
}),
],
};