style(dashboard-v2): prettify
This commit is contained in:
parent
02b3b814f0
commit
d385f9e689
|
@ -2,5 +2,5 @@ module.exports = {
|
|||
globals: {
|
||||
__PATH_PREFIX__: true,
|
||||
},
|
||||
extends: ['react-app', 'plugin:storybook/recommended'],
|
||||
}
|
||||
extends: ["react-app", "plugin:storybook/recommended"],
|
||||
};
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
module.exports = {
|
||||
stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'],
|
||||
stories: ["../src/**/*.stories.@(js|jsx|ts|tsx)"],
|
||||
addons: [
|
||||
'@storybook/addon-links',
|
||||
'@storybook/addon-essentials',
|
||||
"@storybook/addon-links",
|
||||
"@storybook/addon-essentials",
|
||||
{
|
||||
name: '@storybook/addon-postcss',
|
||||
name: "@storybook/addon-postcss",
|
||||
options: {
|
||||
postcssLoaderOptions: {
|
||||
implementation: require('postcss'),
|
||||
implementation: require("postcss"),
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
core: {
|
||||
builder: 'webpack5',
|
||||
builder: "webpack5",
|
||||
},
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
import 'tailwindcss/tailwind.css'
|
||||
import '@fontsource/sora/300.css' // light
|
||||
import '@fontsource/sora/400.css' // normal
|
||||
import '@fontsource/sora/500.css' // medium
|
||||
import '@fontsource/sora/600.css' // semibold
|
||||
import '@fontsource/source-sans-pro/400.css' // normal
|
||||
import '@fontsource/source-sans-pro/600.css' // semibold
|
||||
import "tailwindcss/tailwind.css";
|
||||
import "@fontsource/sora/300.css"; // light
|
||||
import "@fontsource/sora/400.css"; // normal
|
||||
import "@fontsource/sora/500.css"; // medium
|
||||
import "@fontsource/sora/600.css"; // semibold
|
||||
import "@fontsource/source-sans-pro/400.css"; // normal
|
||||
import "@fontsource/source-sans-pro/600.css"; // semibold
|
||||
|
||||
import '../src/styles/global.css'
|
||||
import "../src/styles/global.css";
|
||||
|
||||
export const parameters = {
|
||||
actions: { argTypesRegex: '^on[A-Z].*' },
|
||||
actions: { argTypesRegex: "^on[A-Z].*" },
|
||||
controls: {
|
||||
matchers: {
|
||||
color: /(background|color)$/i,
|
||||
date: /Date$/,
|
||||
},
|
||||
},
|
||||
layout: 'fullscreen',
|
||||
}
|
||||
layout: "fullscreen",
|
||||
};
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
module.exports = {
|
||||
plugins: [require('tailwindcss/nesting'), require('tailwindcss'), require('autoprefixer')],
|
||||
}
|
||||
plugins: [require("tailwindcss/nesting"), require("tailwindcss"), require("autoprefixer")],
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import PropTypes from 'prop-types'
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
/**
|
||||
* Primary UI component for user interaction
|
||||
|
@ -8,13 +8,13 @@ export const Button = ({ primary, label, ...props }) => {
|
|||
<button
|
||||
type="button"
|
||||
className={`min-w-button min-h-button rounded-full font-sans uppercase tracking-wide text-button
|
||||
${primary ? 'bg-primary' : 'bg-white border-2 border-black'}`}
|
||||
${primary ? "bg-primary" : "bg-white border-2 border-black"}`}
|
||||
{...props}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
Button.propTypes = {
|
||||
/**
|
||||
|
@ -33,9 +33,9 @@ Button.propTypes = {
|
|||
* Optional click handler
|
||||
*/
|
||||
onClick: PropTypes.func,
|
||||
}
|
||||
};
|
||||
|
||||
Button.defaultProps = {
|
||||
primary: false,
|
||||
onClick: undefined,
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,38 +1,38 @@
|
|||
import { Button } from './Button'
|
||||
import { Button } from "./Button";
|
||||
|
||||
// More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
|
||||
export default {
|
||||
title: 'SkynetLibrary/Button',
|
||||
title: "SkynetLibrary/Button",
|
||||
component: Button,
|
||||
// More on argTypes: https://storybook.js.org/docs/react/api/argtypes
|
||||
argTypes: {
|
||||
backgroundColor: { control: 'color' },
|
||||
backgroundColor: { control: "color" },
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
// More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
|
||||
const Template = (args) => <Button {...args} />
|
||||
const Template = (args) => <Button {...args} />;
|
||||
|
||||
export const Primary = Template.bind({})
|
||||
export const Primary = Template.bind({});
|
||||
// More on args: https://storybook.js.org/docs/react/writing-stories/args
|
||||
Primary.args = {
|
||||
primary: true,
|
||||
label: 'Button',
|
||||
}
|
||||
label: "Button",
|
||||
};
|
||||
|
||||
export const Secondary = Template.bind({})
|
||||
export const Secondary = Template.bind({});
|
||||
Secondary.args = {
|
||||
label: 'Button',
|
||||
}
|
||||
label: "Button",
|
||||
};
|
||||
|
||||
export const Large = Template.bind({})
|
||||
export const Large = Template.bind({});
|
||||
Large.args = {
|
||||
size: 'large',
|
||||
label: 'Button',
|
||||
}
|
||||
size: "large",
|
||||
label: "Button",
|
||||
};
|
||||
|
||||
export const Small = Template.bind({})
|
||||
export const Small = Template.bind({});
|
||||
Small.args = {
|
||||
size: 'small',
|
||||
label: 'Button',
|
||||
}
|
||||
size: "small",
|
||||
label: "Button",
|
||||
};
|
||||
|
|
|
@ -1 +1 @@
|
|||
export * from './Button'
|
||||
export * from "./Button";
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { useRef, useState } from 'react'
|
||||
import { useClickAway } from 'react-use'
|
||||
import PropTypes from 'prop-types'
|
||||
import styled, { css, keyframes } from 'styled-components'
|
||||
import { useRef, useState } from "react";
|
||||
import { useClickAway } from "react-use";
|
||||
import PropTypes from "prop-types";
|
||||
import styled, { css, keyframes } from "styled-components";
|
||||
|
||||
import { ChevronDownIcon } from '../Icons'
|
||||
import { ChevronDownIcon } from "../Icons";
|
||||
|
||||
const dropDown = keyframes`
|
||||
0% {
|
||||
|
@ -15,39 +15,39 @@ const dropDown = keyframes`
|
|||
100% {
|
||||
transform: scaleY(1);
|
||||
}
|
||||
`
|
||||
`;
|
||||
|
||||
const Container = styled.div.attrs({ className: `relative inline-flex` })``
|
||||
const Container = styled.div.attrs({ className: `relative inline-flex` })``;
|
||||
|
||||
const Trigger = styled.button.attrs({
|
||||
className: 'flex items-center',
|
||||
})``
|
||||
className: "flex items-center",
|
||||
})``;
|
||||
|
||||
const TriggerIcon = styled(ChevronDownIcon).attrs({
|
||||
className: 'transition-transform text-primary',
|
||||
className: "transition-transform text-primary",
|
||||
})`
|
||||
transform: ${({ open }) => (open ? 'rotateX(180deg)' : 'none')};
|
||||
`
|
||||
transform: ${({ open }) => (open ? "rotateX(180deg)" : "none")};
|
||||
`;
|
||||
|
||||
const Flyout = styled.div.attrs(({ open }) => ({
|
||||
className: `absolute top-full right-0 p-0
|
||||
border rounded border-palette-100
|
||||
bg-white shadow-md shadow-palette-200/50
|
||||
${open ? 'visible' : 'invisible'}`,
|
||||
${open ? "visible" : "invisible"}`,
|
||||
}))`
|
||||
animation: ${({ open }) =>
|
||||
open
|
||||
? css`
|
||||
${dropDown} 0.1s ease-in-out
|
||||
`
|
||||
: 'none'};
|
||||
`
|
||||
: "none"};
|
||||
`;
|
||||
|
||||
export const DropdownMenu = ({ title, children }) => {
|
||||
const [open, setOpen] = useState(false)
|
||||
const menuRef = useRef()
|
||||
const [open, setOpen] = useState(false);
|
||||
const menuRef = useRef();
|
||||
|
||||
useClickAway(menuRef, () => setOpen(false))
|
||||
useClickAway(menuRef, () => setOpen(false));
|
||||
|
||||
return (
|
||||
<Container ref={menuRef}>
|
||||
|
@ -56,10 +56,10 @@ export const DropdownMenu = ({ title, children }) => {
|
|||
</Trigger>
|
||||
<Flyout open={open}>{children}</Flyout>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
DropdownMenu.propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
children: PropTypes.node.isRequired,
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,25 +1,25 @@
|
|||
import { Panel } from '../Panel'
|
||||
import { DropdownMenu, DropdownMenuLink } from '.'
|
||||
import { CogIcon, LockClosedIcon } from '../Icons'
|
||||
import { Panel } from "../Panel";
|
||||
import { DropdownMenu, DropdownMenuLink } from ".";
|
||||
import { CogIcon, LockClosedIcon } from "../Icons";
|
||||
|
||||
export default {
|
||||
title: 'SkynetLibrary/DropdownMenu',
|
||||
title: "SkynetLibrary/DropdownMenu",
|
||||
component: DropdownMenu,
|
||||
subcomponents: {
|
||||
DropdownMenuLink,
|
||||
},
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<Panel style={{ margin: 50, textAlign: 'center' }}>
|
||||
<Panel style={{ margin: 50, textAlign: "center" }}>
|
||||
<Story />
|
||||
</Panel>
|
||||
),
|
||||
],
|
||||
}
|
||||
};
|
||||
|
||||
export const NavigationDropdown = () => (
|
||||
<DropdownMenu title="My account">
|
||||
<DropdownMenuLink href="/settings" icon={CogIcon} label="Settings" active />
|
||||
<DropdownMenuLink href="/logout" icon={LockClosedIcon} label="Log out" />
|
||||
</DropdownMenu>
|
||||
)
|
||||
);
|
||||
|
|
|
@ -1,25 +1,25 @@
|
|||
import styled from 'styled-components'
|
||||
import PropTypes from 'prop-types'
|
||||
import styled from "styled-components";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
const DropdownLink = styled.a.attrs({
|
||||
className: `m-0 border-t border-palette-200/50 h-[60px]
|
||||
whitespace-nowrap transition-colors text-current
|
||||
hover:bg-palette-100/50 flex items-center
|
||||
pr-8 pl-6 py-4 gap-4 first:border-0`,
|
||||
})``
|
||||
})``;
|
||||
|
||||
export const DropdownMenuLink = ({ active, icon: Icon, label, ...props }) => (
|
||||
<>
|
||||
<DropdownLink {...props}>
|
||||
{Icon ? <Icon className={active ? 'text-primary' : 'text-current'} /> : null}
|
||||
{Icon ? <Icon className={active ? "text-primary" : "text-current"} /> : null}
|
||||
{label}
|
||||
</DropdownLink>
|
||||
</>
|
||||
)
|
||||
);
|
||||
|
||||
DropdownMenuLink.propTypes = {
|
||||
label: PropTypes.oneOfType([PropTypes.node, PropTypes.string]).isRequired,
|
||||
active: PropTypes.bool,
|
||||
icon: PropTypes.func,
|
||||
children: PropTypes.node.isRequired,
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
export * from './DropdownMenu'
|
||||
export * from './DropdownMenuLink'
|
||||
export * from "./DropdownMenu";
|
||||
export * from "./DropdownMenuLink";
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import PropTypes from 'prop-types'
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
/**
|
||||
* Primary UI component for user interaction
|
||||
|
@ -8,30 +8,30 @@ export const IconButton = ({ primary, size, icon, ...props }) => {
|
|||
<button
|
||||
type="button"
|
||||
className={`${
|
||||
size === 'small'
|
||||
? 'h-iconButtonSm w-buttonIconSm'
|
||||
: size === 'large'
|
||||
? 'h-iconButtonLg w-iconButtonLg'
|
||||
: 'w-iconButton h-iconButton'
|
||||
size === "small"
|
||||
? "h-iconButtonSm w-buttonIconSm"
|
||||
: size === "large"
|
||||
? "h-iconButtonLg w-iconButtonLg"
|
||||
: "w-iconButton h-iconButton"
|
||||
} rounded-full
|
||||
inline-flex justify-center items-center
|
||||
${primary ? 'bg-primary' : null}`}
|
||||
${primary ? "bg-primary" : null}`}
|
||||
{...props}
|
||||
>
|
||||
<div
|
||||
className={
|
||||
size === 'small'
|
||||
? 'h-buttonIconSm w-buttonIconSm'
|
||||
: size === 'large'
|
||||
? 'h-buttonIconLg w-buttonIconLg'
|
||||
: 'h-buttonIcon w-buttonIcon'
|
||||
size === "small"
|
||||
? "h-buttonIconSm w-buttonIconSm"
|
||||
: size === "large"
|
||||
? "h-buttonIconLg w-buttonIconLg"
|
||||
: "h-buttonIcon w-buttonIcon"
|
||||
}
|
||||
>
|
||||
{icon}
|
||||
</div>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
IconButton.propTypes = {
|
||||
/**
|
||||
|
@ -41,7 +41,7 @@ IconButton.propTypes = {
|
|||
/**
|
||||
* How large should the button be?
|
||||
*/
|
||||
size: PropTypes.oneOf(['small', 'medium', 'large']),
|
||||
size: PropTypes.oneOf(["small", "medium", "large"]),
|
||||
/**
|
||||
* Icon component
|
||||
*/
|
||||
|
@ -50,11 +50,11 @@ IconButton.propTypes = {
|
|||
* Optional click handler
|
||||
*/
|
||||
onClick: PropTypes.func,
|
||||
}
|
||||
};
|
||||
|
||||
IconButton.defaultProps = {
|
||||
backgroundColor: null,
|
||||
primary: false,
|
||||
size: 'medium',
|
||||
size: "medium",
|
||||
onClick: undefined,
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,39 +1,39 @@
|
|||
import { IconButton } from './IconButton'
|
||||
import { ArrowRightIcon } from '../Icons'
|
||||
import { IconButton } from "./IconButton";
|
||||
import { ArrowRightIcon } from "../Icons";
|
||||
|
||||
// More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
|
||||
export default {
|
||||
title: 'SkynetLibrary/IconButton',
|
||||
title: "SkynetLibrary/IconButton",
|
||||
component: IconButton,
|
||||
// More on argTypes: https://storybook.js.org/docs/react/api/argtypes
|
||||
argTypes: {
|
||||
backgroundColor: { control: 'color' },
|
||||
backgroundColor: { control: "color" },
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
// More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
|
||||
const Template = (args) => <IconButton {...args} />
|
||||
const Template = (args) => <IconButton {...args} />;
|
||||
|
||||
export const Primary = Template.bind({})
|
||||
export const Primary = Template.bind({});
|
||||
// More on args: https://storybook.js.org/docs/react/writing-stories/args
|
||||
Primary.args = {
|
||||
primary: true,
|
||||
icon: <ArrowRightIcon />,
|
||||
}
|
||||
};
|
||||
|
||||
export const Secondary = Template.bind({})
|
||||
export const Secondary = Template.bind({});
|
||||
Secondary.args = {
|
||||
icon: <ArrowRightIcon />,
|
||||
}
|
||||
};
|
||||
|
||||
export const Large = Template.bind({})
|
||||
export const Large = Template.bind({});
|
||||
Large.args = {
|
||||
size: 'large',
|
||||
size: "large",
|
||||
icon: <ArrowRightIcon />,
|
||||
}
|
||||
};
|
||||
|
||||
export const Small = Template.bind({})
|
||||
export const Small = Template.bind({});
|
||||
Small.args = {
|
||||
size: 'small',
|
||||
size: "small",
|
||||
icon: <ArrowRightIcon />,
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1 +1 @@
|
|||
export * from './IconButton'
|
||||
export * from "./IconButton";
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import PropTypes from 'prop-types'
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
/**
|
||||
* Primary UI component for user interaction
|
||||
|
@ -10,15 +10,15 @@ export const IconButtonText = ({ primary, label, icon, ...props }) => {
|
|||
className={`flex justify-center items-center w-iconButtonTextWidth py-iconButtonTextY`}
|
||||
{...props}
|
||||
>
|
||||
<div className={`h-buttonTextIcon w-buttonTextIcon ${primary ? 'text-primary' : 'text-palette-600'}`}>{icon}</div>
|
||||
<div className={`h-buttonTextIcon w-buttonTextIcon ${primary ? "text-primary" : "text-palette-600"}`}>{icon}</div>
|
||||
<p
|
||||
className={'ml-iconButtonTextTextLeft tracking-wide text-iconButtonText font-sans font-light text-palette-600'}
|
||||
className={"ml-iconButtonTextTextLeft tracking-wide text-iconButtonText font-sans font-light text-palette-600"}
|
||||
>
|
||||
{label}
|
||||
</p>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
IconButtonText.propTypes = {
|
||||
/**
|
||||
|
@ -37,10 +37,10 @@ IconButtonText.propTypes = {
|
|||
* Optional click handler
|
||||
*/
|
||||
onClick: PropTypes.func,
|
||||
}
|
||||
};
|
||||
|
||||
IconButtonText.defaultProps = {
|
||||
primary: false,
|
||||
label: '',
|
||||
label: "",
|
||||
onClick: undefined,
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,29 +1,29 @@
|
|||
import { IconButtonText } from './IconButtonText'
|
||||
import { CogIcon } from '../Icons'
|
||||
import { IconButtonText } from "./IconButtonText";
|
||||
import { CogIcon } from "../Icons";
|
||||
|
||||
// More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
|
||||
export default {
|
||||
title: 'SkynetLibrary/IconButtonText',
|
||||
title: "SkynetLibrary/IconButtonText",
|
||||
component: IconButtonText,
|
||||
// More on argTypes: https://storybook.js.org/docs/react/api/argtypes
|
||||
argTypes: {
|
||||
backgroundColor: { control: 'color' },
|
||||
backgroundColor: { control: "color" },
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
// More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
|
||||
const Template = (args) => <IconButtonText {...args} />
|
||||
const Template = (args) => <IconButtonText {...args} />;
|
||||
|
||||
export const Primary = Template.bind({})
|
||||
export const Primary = Template.bind({});
|
||||
// More on args: https://storybook.js.org/docs/react/writing-stories/args
|
||||
Primary.args = {
|
||||
primary: true,
|
||||
label: 'Settings',
|
||||
label: "Settings",
|
||||
icon: <CogIcon />,
|
||||
}
|
||||
};
|
||||
|
||||
export const Secondary = Template.bind({})
|
||||
export const Secondary = Template.bind({});
|
||||
Secondary.args = {
|
||||
label: 'Settings',
|
||||
label: "Settings",
|
||||
icon: <CogIcon />,
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1 +1 @@
|
|||
export * from './IconButtonText'
|
||||
export * from "./IconButtonText";
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { Panel } from '../Panel'
|
||||
import * as icons from '.'
|
||||
import { Panel } from "../Panel";
|
||||
import * as icons from ".";
|
||||
|
||||
export default {
|
||||
title: 'SkynetLibrary/Icons',
|
||||
title: "SkynetLibrary/Icons",
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<div style={{ margin: 50 }}>
|
||||
|
@ -10,14 +10,14 @@ export default {
|
|||
</div>
|
||||
),
|
||||
],
|
||||
}
|
||||
};
|
||||
|
||||
export const DefaultSizeIcon = () => <icons.SkynetLogoIcon />
|
||||
export const DefaultSizeIcon = () => <icons.SkynetLogoIcon />;
|
||||
|
||||
export const LargeIcon = () => <icons.SkynetLogoIcon size={60} />
|
||||
export const LargeIcon = () => <icons.SkynetLogoIcon size={60} />;
|
||||
|
||||
export const AllIcons = () => {
|
||||
const sizes = [24, 32, 60]
|
||||
const sizes = [24, 32, 60];
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -25,7 +25,7 @@ export const AllIcons = () => {
|
|||
<Panel key={`panel-${iconName}`}>
|
||||
<pre>{iconName}</pre>
|
||||
|
||||
<div style={{ padding: 10, border: '1px dashed #fafafa', display: 'flex', alignItems: 'center', gap: 50 }}>
|
||||
<div style={{ padding: 10, border: "1px dashed #fafafa", display: "flex", alignItems: "center", gap: 50 }}>
|
||||
{sizes.map((size) => (
|
||||
<IconComponent key={`${iconName}-${size}`} size={size} />
|
||||
))}
|
||||
|
@ -33,5 +33,5 @@ export const AllIcons = () => {
|
|||
</Panel>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { withIconProps } from '../withIconProps'
|
||||
import { withIconProps } from "../withIconProps";
|
||||
|
||||
export const ArrowRightIcon = withIconProps(({ size, ...props }) => (
|
||||
<svg width={size} height={size} viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
|
@ -8,4 +8,4 @@ export const ArrowRightIcon = withIconProps(({ size, ...props }) => (
|
|||
fillRule="nonzero"
|
||||
/>
|
||||
</svg>
|
||||
))
|
||||
));
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { withIconProps } from '../withIconProps'
|
||||
import { withIconProps } from "../withIconProps";
|
||||
|
||||
export const ChevronDownIcon = withIconProps(({ size, ...props }) => (
|
||||
<svg width={size} height={size} viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
|
@ -11,4 +11,4 @@ export const ChevronDownIcon = withIconProps(({ size, ...props }) => (
|
|||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
))
|
||||
));
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { withIconProps } from '../withIconProps'
|
||||
import { withIconProps } from "../withIconProps";
|
||||
|
||||
export const CogIcon = withIconProps(({ size, ...props }) => (
|
||||
<svg width={size} height={size} viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
|
@ -8,4 +8,4 @@ export const CogIcon = withIconProps(({ size, ...props }) => (
|
|||
fillRule="nonzero"
|
||||
/>
|
||||
</svg>
|
||||
))
|
||||
));
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { withIconProps } from '../withIconProps'
|
||||
import { withIconProps } from "../withIconProps";
|
||||
|
||||
export const InfoIcon = withIconProps(({ size, ...props }) => (
|
||||
<svg width={size} height={size} viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
|
@ -8,4 +8,4 @@ export const InfoIcon = withIconProps(({ size, ...props }) => (
|
|||
fillRule="nonzero"
|
||||
/>
|
||||
</svg>
|
||||
))
|
||||
));
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { withIconProps } from '../withIconProps'
|
||||
import { withIconProps } from "../withIconProps";
|
||||
|
||||
export const LockClosedIcon = withIconProps(({ size, ...props }) => (
|
||||
<svg width={size} height={size} viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
|
@ -8,4 +8,4 @@ export const LockClosedIcon = withIconProps(({ size, ...props }) => (
|
|||
fillRule="nonzero"
|
||||
/>
|
||||
</svg>
|
||||
))
|
||||
));
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { withIconProps } from '../withIconProps'
|
||||
import { withIconProps } from "../withIconProps";
|
||||
|
||||
export const SkynetLogoIcon = withIconProps(({ size, ...props }) => (
|
||||
<svg role="img" width={size} fill="#00C65E" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<title>Skynet</title>
|
||||
<path d="m-.0004 6.4602 21.3893 11.297c.561.2935.6633 1.0532.1999 1.4846h-.011a10.0399 10.0399 0 0 1-2.2335 1.5307c-6.912 3.4734-14.9917-1.838-14.5438-9.5605l2.8601 1.9752c.856 4.508 5.6187 7.1094 9.8742 5.3932zm8.6477 3.1509 14.3661 5.6785a.8704.8704 0 0 1 .5197 1.0466v.0182c-.1537.5377-.7668.7938-1.2575.5252zm5.2896-7.4375c2.7093-.2325 6.0946.7869 8.1116 3.3871 1.699 2.1951 2.0497 4.8772 1.9298 7.6465v-.007c-.0478.5874-.6494.9616-1.1975.745l-9.7652-3.8596 9.0656 2.4313a7.296 7.296 0 0 0-1.0677-4.5631c-2.9683-4.7678-9.9847-4.5344-12.6297.4201a7.5048 7.5048 0 0 0-.398.8831L5.5546 7.9614c.069-.1017.1417-.198.2144-.2962.1163-.2416.2417-.487.3798-.7268 1.6118-2.7911 4.3102-4.4338 7.1558-4.6973.2108-.0182.4215-.049.6323-.0672z"></path>
|
||||
</svg>
|
||||
))
|
||||
));
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
export * from './icons/ChevronDownIcon'
|
||||
export * from './icons/CogIcon'
|
||||
export * from './icons/LockClosedIcon'
|
||||
export * from './icons/SkynetLogoIcon'
|
||||
export * from './icons/ArrowRightIcon'
|
||||
export * from './icons/InfoIcon'
|
||||
export * from "./icons/ChevronDownIcon";
|
||||
export * from "./icons/CogIcon";
|
||||
export * from "./icons/LockClosedIcon";
|
||||
export * from "./icons/SkynetLogoIcon";
|
||||
export * from "./icons/ArrowRightIcon";
|
||||
export * from "./icons/InfoIcon";
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
import PropTypes from 'prop-types'
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
const propTypes = {
|
||||
/**
|
||||
* Size of the icon's bounding box.
|
||||
*/
|
||||
size: PropTypes.number,
|
||||
}
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
size: 32,
|
||||
}
|
||||
};
|
||||
|
||||
export const withIconProps = (IconComponent) => {
|
||||
IconComponent.propTypes = propTypes
|
||||
IconComponent.defaultProps = defaultProps
|
||||
IconComponent.propTypes = propTypes;
|
||||
IconComponent.defaultProps = defaultProps;
|
||||
|
||||
return IconComponent
|
||||
}
|
||||
return IconComponent;
|
||||
};
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
import styled from 'styled-components'
|
||||
import styled from "styled-components";
|
||||
|
||||
import { PageContainer } from '../PageContainer'
|
||||
import { PageContainer } from "../PageContainer";
|
||||
|
||||
const NavBarContainer = styled.div.attrs({
|
||||
className: `grid sticky top-0 bg-white`,
|
||||
})``
|
||||
})``;
|
||||
|
||||
const NavBarBody = styled.nav.attrs({
|
||||
className: 'grid h-[80px] font-sans font-light text-sm',
|
||||
className: "grid h-[80px] font-sans font-light text-sm",
|
||||
})`
|
||||
grid-template-columns: auto max-content 1fr;
|
||||
`
|
||||
`;
|
||||
|
||||
export const NavBar = (props) => (
|
||||
<NavBarContainer>
|
||||
|
@ -18,4 +18,4 @@ export const NavBar = (props) => (
|
|||
<NavBarBody {...props} />
|
||||
</PageContainer>
|
||||
</NavBarContainer>
|
||||
)
|
||||
);
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import { NavBar, NavBarLink, NavBarSection } from '.'
|
||||
import { NavBar, NavBarLink, NavBarSection } from ".";
|
||||
|
||||
export default {
|
||||
title: 'SkynetLibrary/NavBar',
|
||||
title: "SkynetLibrary/NavBar",
|
||||
component: NavBar,
|
||||
subcomponents: {
|
||||
NavBarSection,
|
||||
NavBarLink,
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
const Template = (props) => (
|
||||
<NavBar {...props}>
|
||||
|
@ -19,7 +19,7 @@ const Template = (props) => (
|
|||
<NavBarLink href="/payments">Payments</NavBarLink>
|
||||
</NavBarSection>
|
||||
</NavBar>
|
||||
)
|
||||
);
|
||||
|
||||
export const DashboardTopNavigation = Template.bind({})
|
||||
DashboardTopNavigation.args = {}
|
||||
export const DashboardTopNavigation = Template.bind({});
|
||||
DashboardTopNavigation.args = {};
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import PropTypes from 'prop-types'
|
||||
import styled from 'styled-components'
|
||||
import PropTypes from "prop-types";
|
||||
import styled from "styled-components";
|
||||
|
||||
export const NavBarLink = styled.a.attrs(({ active }) => ({
|
||||
className: `
|
||||
|
@ -7,13 +7,13 @@ export const NavBarLink = styled.a.attrs(({ active }) => ({
|
|||
flex h-full items-center justify-center
|
||||
border-x border-x-palette-100 border-b-2
|
||||
text-palette-600 transition-colors hover:bg-palette-100/50
|
||||
${active ? 'border-b-primary' : 'border-b-palette-200/50'}
|
||||
${active ? "border-b-primary" : "border-b-palette-200/50"}
|
||||
`,
|
||||
}))``
|
||||
}))``;
|
||||
|
||||
NavBarLink.propTypes = {
|
||||
/**
|
||||
* When set to true, an additional indicator will be rendered showing the item as active.
|
||||
*/
|
||||
active: PropTypes.bool,
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
import styled from 'styled-components'
|
||||
import styled from "styled-components";
|
||||
|
||||
export const NavBarSection = styled.div.attrs({ className: 'flex items-center' })``
|
||||
export const NavBarSection = styled.div.attrs({ className: "flex items-center" })``;
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
export * from './NavBar'
|
||||
export * from './NavBarSection'
|
||||
export * from './NavBarLink'
|
||||
export * from "./NavBar";
|
||||
export * from "./NavBarSection";
|
||||
export * from "./NavBarLink";
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import PropTypes from 'prop-types'
|
||||
import styled from 'styled-components'
|
||||
import PropTypes from "prop-types";
|
||||
import styled from "styled-components";
|
||||
|
||||
export const PageContainer = styled.div.attrs({
|
||||
className: `mx-auto w-page md:w-page-md lg:w-page-lg xl:w-page-xl`,
|
||||
})``
|
||||
})``;
|
||||
|
||||
PageContainer.propTypes = {
|
||||
/**
|
||||
* Optional `class` attribute.
|
||||
*/
|
||||
className: PropTypes.string,
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1 +1 @@
|
|||
export * from './PageContainer'
|
||||
export * from "./PageContainer";
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import PropTypes from 'prop-types'
|
||||
import styled from 'styled-components'
|
||||
import PropTypes from "prop-types";
|
||||
import styled from "styled-components";
|
||||
|
||||
const PanelBody = styled.div.attrs({
|
||||
className: 'p-6 bg-white rounded',
|
||||
})``
|
||||
className: "p-6 bg-white rounded",
|
||||
})``;
|
||||
|
||||
const PanelTitle = styled.h6.attrs({
|
||||
className: 'uppercase text-xs text-palette-400 h-8 flex items-center',
|
||||
})``
|
||||
className: "uppercase text-xs text-palette-400 h-8 flex items-center",
|
||||
})``;
|
||||
|
||||
/**
|
||||
* Besides documented props, it accepts all HMTL attributes a `<div>` element does.
|
||||
|
@ -19,15 +19,15 @@ export const Panel = ({ title, ...props }) => (
|
|||
{title && <PanelTitle>{title}</PanelTitle>}
|
||||
<PanelBody {...props} />
|
||||
</div>
|
||||
)
|
||||
);
|
||||
|
||||
Panel.propTypes = {
|
||||
/**
|
||||
* Label of the panel
|
||||
*/
|
||||
title: PropTypes.string,
|
||||
}
|
||||
};
|
||||
|
||||
Panel.defaultProps = {
|
||||
title: '',
|
||||
}
|
||||
title: "",
|
||||
};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Panel } from './Panel'
|
||||
import { Panel } from "./Panel";
|
||||
|
||||
export default {
|
||||
title: 'SkynetLibrary/Panel',
|
||||
title: "SkynetLibrary/Panel",
|
||||
component: Panel,
|
||||
decorators: [
|
||||
(Story) => (
|
||||
|
@ -12,7 +12,7 @@ export default {
|
|||
</div>
|
||||
),
|
||||
],
|
||||
}
|
||||
};
|
||||
|
||||
const SampleContent = () => (
|
||||
<>
|
||||
|
@ -20,21 +20,21 @@ const SampleContent = () => (
|
|||
<p>This is the second paragraph</p>
|
||||
<p>This is the third paragraph</p>
|
||||
</>
|
||||
)
|
||||
);
|
||||
|
||||
const Template = (args) => (
|
||||
<Panel {...args}>
|
||||
<SampleContent />
|
||||
</Panel>
|
||||
)
|
||||
);
|
||||
|
||||
export const RawPanel = Template.bind({})
|
||||
RawPanel.args = {}
|
||||
export const RawPanel = Template.bind({});
|
||||
RawPanel.args = {};
|
||||
|
||||
export const TitledPanel = Template.bind({})
|
||||
export const TitledPanel = Template.bind({});
|
||||
TitledPanel.args = {
|
||||
title: 'Latest activity',
|
||||
}
|
||||
title: "Latest activity",
|
||||
};
|
||||
|
||||
export const InlinePanelsExample = () => (
|
||||
<div className="grid gap-4 grid-flow-col auto-cols-fr">
|
||||
|
@ -45,7 +45,7 @@ export const InlinePanelsExample = () => (
|
|||
<SampleContent />
|
||||
</Panel>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
|
||||
export const FullWidthPanelsExample = () => (
|
||||
<>
|
||||
|
@ -56,10 +56,10 @@ export const FullWidthPanelsExample = () => (
|
|||
<SampleContent />
|
||||
</Panel>
|
||||
</>
|
||||
)
|
||||
);
|
||||
|
||||
export const CustomPanelBackground = Template.bind({})
|
||||
export const CustomPanelBackground = Template.bind({});
|
||||
CustomPanelBackground.args = {
|
||||
className: 'bg-red-500',
|
||||
title: 'Background below should be red',
|
||||
}
|
||||
className: "bg-red-500",
|
||||
title: "Background below should be red",
|
||||
};
|
||||
|
|
|
@ -1 +1 @@
|
|||
export * from './Panel'
|
||||
export * from "./Panel";
|
||||
|
|
|
@ -1 +1 @@
|
|||
export * from './PopoverMenu'
|
||||
export * from "./PopoverMenu";
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { Children, cloneElement, useEffect, useMemo, useRef } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { useClickAway } from 'react-use'
|
||||
import styled, { css, keyframes } from 'styled-components'
|
||||
import { Children, cloneElement, useEffect, useMemo, useRef } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { useClickAway } from "react-use";
|
||||
import styled, { css, keyframes } from "styled-components";
|
||||
|
||||
import { ChevronDownIcon } from '../Icons'
|
||||
import { useCallbacks, useSelectReducer } from './hooks'
|
||||
import { SelectOption } from './SelectOption'
|
||||
import { ChevronDownIcon } from "../Icons";
|
||||
import { useCallbacks, useSelectReducer } from "./hooks";
|
||||
import { SelectOption } from "./SelectOption";
|
||||
|
||||
const dropDown = keyframes`
|
||||
0% {
|
||||
|
@ -17,52 +17,52 @@ const dropDown = keyframes`
|
|||
100% {
|
||||
transform: scaleY(1);
|
||||
}
|
||||
`
|
||||
`;
|
||||
|
||||
const Container = styled.div.attrs({ className: 'relative inline-flex' })``
|
||||
const Container = styled.div.attrs({ className: "relative inline-flex" })``;
|
||||
|
||||
const Trigger = styled.button.attrs(({ placeholder }) => ({
|
||||
className: `flex items-center cursor-pointer ${placeholder ? 'text-palette-300' : ''}`,
|
||||
}))``
|
||||
className: `flex items-center cursor-pointer ${placeholder ? "text-palette-300" : ""}`,
|
||||
}))``;
|
||||
|
||||
const TriggerIcon = styled(ChevronDownIcon).attrs({
|
||||
className: 'transition-transform text-primary',
|
||||
className: "transition-transform text-primary",
|
||||
})`
|
||||
transform: ${({ open }) => (open ? 'rotateX(180deg)' : 'none')};
|
||||
`
|
||||
transform: ${({ open }) => (open ? "rotateX(180deg)" : "none")};
|
||||
`;
|
||||
|
||||
const Flyout = styled.ul.attrs(({ open }) => ({
|
||||
className: `absolute top-[20px] right-0
|
||||
p-0 h-0 border rounded bg-white
|
||||
overflow-hidden pointer-events-none
|
||||
shadow-md shadow-palette-200/50
|
||||
${open ? 'pointer-events-auto h-auto overflow-visible border-primary' : ''}
|
||||
${open ? 'visible' : 'invisible'}`,
|
||||
${open ? "pointer-events-auto h-auto overflow-visible border-primary" : ""}
|
||||
${open ? "visible" : "invisible"}`,
|
||||
}))`
|
||||
animation: ${({ open }) =>
|
||||
open
|
||||
? css`
|
||||
${dropDown} 0.1s ease-in-out
|
||||
`
|
||||
: 'none'};
|
||||
`
|
||||
: "none"};
|
||||
`;
|
||||
|
||||
export const Select = ({ defaultValue, children, onChange, placeholder }) => {
|
||||
const selectRef = useRef()
|
||||
const options = useMemo(() => Children.toArray(children).filter(({ type }) => type === SelectOption), [children])
|
||||
const [state, dispatch] = useSelectReducer({ defaultValue, placeholder, options })
|
||||
const { close, toggle, selectOption } = useCallbacks(state, dispatch)
|
||||
const selectRef = useRef();
|
||||
const options = useMemo(() => Children.toArray(children).filter(({ type }) => type === SelectOption), [children]);
|
||||
const [state, dispatch] = useSelectReducer({ defaultValue, placeholder, options });
|
||||
const { close, toggle, selectOption } = useCallbacks(state, dispatch);
|
||||
|
||||
useClickAway(selectRef, close)
|
||||
useClickAway(selectRef, close);
|
||||
|
||||
useEffect(() => {
|
||||
if (state.selectedOptionIndex > -1) {
|
||||
onChange(options[state.selectedOptionIndex].props.value)
|
||||
onChange(options[state.selectedOptionIndex].props.value);
|
||||
}
|
||||
}, [onChange, options, state.selectedOptionIndex])
|
||||
}, [onChange, options, state.selectedOptionIndex]);
|
||||
|
||||
const activeOption = options[state.selectedOptionIndex]
|
||||
const activeLabel = activeOption?.props?.label ?? null
|
||||
const activeOption = options[state.selectedOptionIndex];
|
||||
const activeLabel = activeOption?.props?.label ?? null;
|
||||
|
||||
return (
|
||||
<Container ref={selectRef}>
|
||||
|
@ -79,8 +79,8 @@ export const Select = ({ defaultValue, children, onChange, placeholder }) => {
|
|||
)}
|
||||
</Flyout>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
Select.propTypes = {
|
||||
/**
|
||||
|
@ -99,4 +99,4 @@ Select.propTypes = {
|
|||
* Placeholder to be displayed when no option is selected.
|
||||
*/
|
||||
placeholder: PropTypes.string,
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
import { Panel } from '../Panel'
|
||||
import { Select, SelectOption } from '.'
|
||||
import { Panel } from "../Panel";
|
||||
import { Select, SelectOption } from ".";
|
||||
|
||||
export default {
|
||||
title: 'SkynetLibrary/Select',
|
||||
title: "SkynetLibrary/Select",
|
||||
component: Select,
|
||||
subcomponents: {
|
||||
SelectOption,
|
||||
},
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<Panel style={{ margin: 50, textAlign: 'center' }}>
|
||||
<Panel style={{ margin: 50, textAlign: "center" }}>
|
||||
<Story />
|
||||
</Panel>
|
||||
),
|
||||
],
|
||||
}
|
||||
};
|
||||
|
||||
const Template = (props) => (
|
||||
<Select {...props}>
|
||||
|
@ -25,23 +25,23 @@ const Template = (props) => (
|
|||
<SelectOption value="date-desc" label="Latest" />
|
||||
<SelectOption value="date-asc" label="Oldest" />
|
||||
</Select>
|
||||
)
|
||||
Template.args = {}
|
||||
);
|
||||
Template.args = {};
|
||||
|
||||
export const NoDefaultNoPlaceholder = Template.bind({})
|
||||
export const NoDefaultNoPlaceholder = Template.bind({});
|
||||
NoDefaultNoPlaceholder.args = {
|
||||
onChange: console.info.bind(console, 'onChange'),
|
||||
}
|
||||
onChange: console.info.bind(console, "onChange"),
|
||||
};
|
||||
|
||||
export const WithPlaceholder = Template.bind({})
|
||||
export const WithPlaceholder = Template.bind({});
|
||||
WithPlaceholder.args = {
|
||||
placeholder: 'Select...',
|
||||
onChange: console.info.bind(console, 'onChange'),
|
||||
}
|
||||
placeholder: "Select...",
|
||||
onChange: console.info.bind(console, "onChange"),
|
||||
};
|
||||
|
||||
export const WithDefautValue = Template.bind({})
|
||||
export const WithDefautValue = Template.bind({});
|
||||
WithDefautValue.args = {
|
||||
defaultValue: 'size-desc',
|
||||
placeholder: 'Select...',
|
||||
onChange: console.info.bind(console, 'onChange'),
|
||||
}
|
||||
defaultValue: "size-desc",
|
||||
placeholder: "Select...",
|
||||
onChange: console.info.bind(console, "onChange"),
|
||||
};
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
import PropTypes from 'prop-types'
|
||||
import styled from 'styled-components'
|
||||
import PropTypes from "prop-types";
|
||||
import styled from "styled-components";
|
||||
|
||||
const Option = styled.li.attrs(({ selected }) => ({
|
||||
className: `m-0 px-4 whitespace-nowrap py-1 px-4 cursor-pointer
|
||||
transition-colors hover:bg-palette-100/50
|
||||
${selected ? 'pl-3.5 border-l-2 border-l-primary' : ''}`,
|
||||
}))``
|
||||
${selected ? "pl-3.5 border-l-2 border-l-primary" : ""}`,
|
||||
}))``;
|
||||
|
||||
export const SelectOption = ({ selected, label, ...props }) => (
|
||||
<Option selected={selected} role="option" {...props}>
|
||||
{label}
|
||||
</Option>
|
||||
)
|
||||
);
|
||||
|
||||
SelectOption.propTypes = {
|
||||
/**
|
||||
|
@ -26,4 +26,4 @@ SelectOption.propTypes = {
|
|||
* Indicates an option is currently selected. **Controlled by parent `<Select>` component**.
|
||||
*/
|
||||
selected: PropTypes.bool,
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,86 +1,86 @@
|
|||
import { useCallback, useReducer } from 'react'
|
||||
import { useCallback, useReducer } from "react";
|
||||
|
||||
const initialState = {
|
||||
open: false,
|
||||
selectedOptionIndex: -1,
|
||||
}
|
||||
};
|
||||
|
||||
const withDefaultValue = (state, { defaultValue, options, placeholder }) => {
|
||||
let index = -1
|
||||
let index = -1;
|
||||
|
||||
if (!defaultValue) {
|
||||
if (!placeholder) {
|
||||
// If no default value and no placeholder are provided, select first option.
|
||||
// TODO: might need to look for the first *available* option.
|
||||
index = 0
|
||||
index = 0;
|
||||
}
|
||||
} else {
|
||||
index = options.findIndex((option) => {
|
||||
if (!option || !option.props) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
return option.props.value === defaultValue
|
||||
})
|
||||
return option.props.value === defaultValue;
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
selectedOptionIndex: index,
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const stateReducer = (state, action) => {
|
||||
switch (action.type) {
|
||||
case 'close':
|
||||
case "close":
|
||||
return {
|
||||
...state,
|
||||
open: false,
|
||||
}
|
||||
case 'open':
|
||||
};
|
||||
case "open":
|
||||
return {
|
||||
...state,
|
||||
open: true,
|
||||
}
|
||||
case 'selectOption':
|
||||
};
|
||||
case "selectOption":
|
||||
return {
|
||||
...state,
|
||||
open: false,
|
||||
selectedOptionIndex: action.index,
|
||||
}
|
||||
};
|
||||
default:
|
||||
return {
|
||||
...state,
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const useSelectReducer = ({ defaultValue, options, placeholder }) =>
|
||||
useReducer(stateReducer, withDefaultValue(initialState, { defaultValue, options, placeholder }))
|
||||
useReducer(stateReducer, withDefaultValue(initialState, { defaultValue, options, placeholder }));
|
||||
|
||||
export const useCallbacks = (state, dispatch) => {
|
||||
const close = useCallback(() => {
|
||||
if (state.open) {
|
||||
dispatch({ type: 'close' })
|
||||
dispatch({ type: "close" });
|
||||
}
|
||||
}, [dispatch, state.open])
|
||||
}, [dispatch, state.open]);
|
||||
|
||||
const toggle = useCallback(() => {
|
||||
dispatch({ type: state.open ? 'close' : 'open' })
|
||||
}, [dispatch, state.open])
|
||||
dispatch({ type: state.open ? "close" : "open" });
|
||||
}, [dispatch, state.open]);
|
||||
|
||||
const selectOption = useCallback(
|
||||
(optionIndex) => {
|
||||
if (optionIndex !== state.selectedOptionIndex) {
|
||||
dispatch({ type: 'selectOption', index: optionIndex })
|
||||
dispatch({ type: "selectOption", index: optionIndex });
|
||||
}
|
||||
},
|
||||
[dispatch, state.selectedOptionIndex]
|
||||
)
|
||||
);
|
||||
|
||||
return {
|
||||
close,
|
||||
selectOption,
|
||||
toggle,
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
export * from './Select'
|
||||
export * from './SelectOption'
|
||||
export * from "./Select";
|
||||
export * from "./SelectOption";
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
}
|
||||
|
||||
.react-switch-label .react-switch-button {
|
||||
content: '';
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import PropTypes from 'prop-types'
|
||||
import './Switch.css'
|
||||
import PropTypes from "prop-types";
|
||||
import "./Switch.css";
|
||||
|
||||
/**
|
||||
* Primary UI component for user interaction
|
||||
|
@ -14,12 +14,12 @@ export const Switch = ({ isOn, handleToggle }) => {
|
|||
id={`react-switch-new`}
|
||||
type="checkbox"
|
||||
/>
|
||||
<label className={'react-switch-label'} htmlFor={`react-switch-new`}>
|
||||
<span className={`react-switch-button ${isOn ? 'bg-primary' : 'bg-palette-200'}`} />
|
||||
<label className={"react-switch-label"} htmlFor={`react-switch-new`}>
|
||||
<span className={`react-switch-button ${isOn ? "bg-primary" : "bg-palette-200"}`} />
|
||||
</label>
|
||||
</>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
Switch.propTypes = {
|
||||
/**
|
||||
|
@ -30,8 +30,8 @@ Switch.propTypes = {
|
|||
* Function to execute on change
|
||||
*/
|
||||
handleToggle: PropTypes.func,
|
||||
}
|
||||
};
|
||||
|
||||
Switch.defaultProps = {
|
||||
isOn: false,
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
import { Switch } from './Switch'
|
||||
import { Switch } from "./Switch";
|
||||
|
||||
// More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
|
||||
export default {
|
||||
title: 'SkynetLibrary/Switch',
|
||||
title: "SkynetLibrary/Switch",
|
||||
component: Switch,
|
||||
// More on argTypes: https://storybook.js.org/docs/react/api/argtypes
|
||||
}
|
||||
};
|
||||
|
||||
// More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
|
||||
const Template = (args) => <Switch {...args} />
|
||||
const Template = (args) => <Switch {...args} />;
|
||||
|
||||
export const SwitchTrue = Template.bind({})
|
||||
export const SwitchTrue = Template.bind({});
|
||||
// More on args: https://storybook.js.org/docs/react/writing-stories/args
|
||||
SwitchTrue.args = {
|
||||
isOn: true,
|
||||
}
|
||||
export const SwitchFalse = Template.bind({})
|
||||
};
|
||||
export const SwitchFalse = Template.bind({});
|
||||
// More on args: https://storybook.js.org/docs/react/writing-stories/args
|
||||
SwitchFalse.args = {
|
||||
isOn: false,
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1 +1 @@
|
|||
export * from './Switch'
|
||||
export * from "./Switch";
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import styled from 'styled-components'
|
||||
import styled from "styled-components";
|
||||
|
||||
const Container = styled.div.attrs({
|
||||
className: 'p-1 max-w-full overflow-x-auto',
|
||||
})``
|
||||
className: "p-1 max-w-full overflow-x-auto",
|
||||
})``;
|
||||
|
||||
const StyledTable = styled.table.attrs({
|
||||
className: 'table-auto w-full border-separate',
|
||||
className: "table-auto w-full border-separate",
|
||||
})`
|
||||
border-spacing: 0;
|
||||
`
|
||||
`;
|
||||
|
||||
/**
|
||||
* Accepts all HMTL attributes a `<table>` element does.
|
||||
|
@ -17,4 +17,4 @@ export const Table = (props) => (
|
|||
<Container>
|
||||
<StyledTable {...props} />
|
||||
</Container>
|
||||
)
|
||||
);
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { CogIcon } from '../Icons'
|
||||
import { CogIcon } from "../Icons";
|
||||
|
||||
import { IconButton } from '../IconButton'
|
||||
import { Table, TableBody, TableHead, TableCell, TableRow, TableHeadCell } from './'
|
||||
import { IconButton } from "../IconButton";
|
||||
import { Table, TableBody, TableHead, TableCell, TableRow, TableHeadCell } from "./";
|
||||
|
||||
export default {
|
||||
title: 'SkynetLibrary/Table',
|
||||
title: "SkynetLibrary/Table",
|
||||
component: Table,
|
||||
subcomponents: {
|
||||
TableBody,
|
||||
|
@ -13,52 +13,52 @@ export default {
|
|||
TableRow,
|
||||
TableHeadCell,
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
const DATA = [
|
||||
{
|
||||
name: 'At_vereo_eos_censes',
|
||||
type: '.mp4',
|
||||
size: '2.45 MB',
|
||||
uploaded: 'a few seconds ago',
|
||||
skylink: '_HyFqH632Rmy99c93idTtBVXeRDgaDAAWg6Bmm5P1izriu',
|
||||
name: "At_vereo_eos_censes",
|
||||
type: ".mp4",
|
||||
size: "2.45 MB",
|
||||
uploaded: "a few seconds ago",
|
||||
skylink: "_HyFqH632Rmy99c93idTtBVXeRDgaDAAWg6Bmm5P1izriu",
|
||||
},
|
||||
{
|
||||
name: 'Miriam Klein IV',
|
||||
type: '.pdf',
|
||||
size: '7.52 MB',
|
||||
uploaded: '01/04/2021; 17:11',
|
||||
skylink: '_izriuHyFqH632Rmy99c93idTtBVXeRDgaDAAWg6Bmm5P1',
|
||||
name: "Miriam Klein IV",
|
||||
type: ".pdf",
|
||||
size: "7.52 MB",
|
||||
uploaded: "01/04/2021; 17:11",
|
||||
skylink: "_izriuHyFqH632Rmy99c93idTtBVXeRDgaDAAWg6Bmm5P1",
|
||||
},
|
||||
{
|
||||
name: 'tmp/QmWR6eVDVkwhAYq7X99w4xT9KNKBzwK39Fj1PDmr4ZnzMm/QmWR6eVDVkwhAYq7X99w4xT9KNKBzwK39Fj1PDmr4ZnzMm',
|
||||
type: '.doc',
|
||||
size: '8.15 MB',
|
||||
uploaded: '10/26/2020; 7:21',
|
||||
skylink: '_VXeRDgaDAAWg6Bmm5P1izriuHyFqH632Rmy99c93idTtB',
|
||||
name: "tmp/QmWR6eVDVkwhAYq7X99w4xT9KNKBzwK39Fj1PDmr4ZnzMm/QmWR6eVDVkwhAYq7X99w4xT9KNKBzwK39Fj1PDmr4ZnzMm",
|
||||
type: ".doc",
|
||||
size: "8.15 MB",
|
||||
uploaded: "10/26/2020; 7:21",
|
||||
skylink: "_VXeRDgaDAAWg6Bmm5P1izriuHyFqH632Rmy99c93idTtB",
|
||||
},
|
||||
{
|
||||
name: 'Perm_London',
|
||||
type: '.avi',
|
||||
size: '225.6 MB',
|
||||
uploaded: '09/12/2020; 19:28',
|
||||
skylink: '_eRDgaDAAWg6Bmm5P1izriuHyFqH632Rmy99c93idTtBVX',
|
||||
name: "Perm_London",
|
||||
type: ".avi",
|
||||
size: "225.6 MB",
|
||||
uploaded: "09/12/2020; 19:28",
|
||||
skylink: "_eRDgaDAAWg6Bmm5P1izriuHyFqH632Rmy99c93idTtBVX",
|
||||
},
|
||||
{
|
||||
name: 'Santa_Clara',
|
||||
type: '.pdf',
|
||||
size: '7.52 MB',
|
||||
uploaded: '09/12/2020; 19:23',
|
||||
skylink: '_AWg6Bmm5P1izriuHyFqH632Rmy99c93idTtBVXeRDgaDA',
|
||||
name: "Santa_Clara",
|
||||
type: ".pdf",
|
||||
size: "7.52 MB",
|
||||
uploaded: "09/12/2020; 19:23",
|
||||
skylink: "_AWg6Bmm5P1izriuHyFqH632Rmy99c93idTtBVXeRDgaDA",
|
||||
},
|
||||
{
|
||||
name: 'Marysa_Labrone',
|
||||
type: '.doc',
|
||||
size: '8.15 MB',
|
||||
uploaded: '09/12/2020; 19:21',
|
||||
skylink: '_P1izriuHyFqH632Rmy99c93idTtBVXeRDgaDAAWg6Bmm5',
|
||||
name: "Marysa_Labrone",
|
||||
type: ".doc",
|
||||
size: "8.15 MB",
|
||||
uploaded: "09/12/2020; 19:21",
|
||||
skylink: "_P1izriuHyFqH632Rmy99c93idTtBVXeRDgaDAAWg6Bmm5",
|
||||
},
|
||||
]
|
||||
];
|
||||
|
||||
const Template = (args) => (
|
||||
<Table {...args}>
|
||||
|
@ -87,7 +87,7 @@ const Template = (args) => (
|
|||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
)
|
||||
);
|
||||
|
||||
export const RegularTable = Template.bind({})
|
||||
RegularTable.args = {}
|
||||
export const RegularTable = Template.bind({});
|
||||
RegularTable.args = {};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import styled from 'styled-components'
|
||||
import styled from "styled-components";
|
||||
|
||||
/**
|
||||
* Accepts all HMTL attributes a `<tbody>` element does.
|
||||
*/
|
||||
export const TableBody = styled.tbody``
|
||||
export const TableBody = styled.tbody``;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import styled from 'styled-components'
|
||||
import styled from "styled-components";
|
||||
|
||||
/**
|
||||
* Accepts all HMTL attributes a `<td>` element does.
|
||||
|
@ -8,6 +8,6 @@ export const TableCell = styled.td.attrs({
|
|||
text-palette-600 even:text-palette-400
|
||||
first:rounded-l-sm last:rounded-r-sm`,
|
||||
})`
|
||||
text-align: ${({ align }) => align ?? 'left'};
|
||||
max-width: ${({ maxWidth }) => maxWidth ?? 'none'};
|
||||
`
|
||||
text-align: ${({ align }) => align ?? "left"};
|
||||
max-width: ${({ maxWidth }) => maxWidth ?? "none"};
|
||||
`;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import styled from 'styled-components'
|
||||
import styled from "styled-components";
|
||||
|
||||
/**
|
||||
* Accepts all HMTL attributes a `<thead>` element does.
|
||||
*/
|
||||
export const TableHead = styled.thead``
|
||||
export const TableHead = styled.thead``;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import styled from 'styled-components'
|
||||
import styled from "styled-components";
|
||||
|
||||
/**
|
||||
* Accepts all HMTL attributes a `<th>` element does.
|
||||
|
@ -8,6 +8,6 @@ export const TableHeadCell = styled.th.attrs({
|
|||
text-palette-600 font-sans font-light text-xs
|
||||
first:rounded-l-sm last:rounded-r-sm`,
|
||||
})`
|
||||
text-align: ${({ align }) => align ?? 'left'};
|
||||
max-width: ${({ maxWidth }) => maxWidth ?? 'none'};
|
||||
`
|
||||
text-align: ${({ align }) => align ?? "left"};
|
||||
max-width: ${({ maxWidth }) => maxWidth ?? "none"};
|
||||
`;
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
import PropTypes from 'prop-types'
|
||||
import styled from 'styled-components'
|
||||
import PropTypes from "prop-types";
|
||||
import styled from "styled-components";
|
||||
|
||||
/**
|
||||
* Besides documented props, it accepts all HMTL attributes a `<tr>` element does.
|
||||
*/
|
||||
export const TableRow = styled.tr.attrs(({ noHoverEffect }) => ({
|
||||
className: `bg-palette-100/50 odd:bg-white ${noHoverEffect ? '' : 'hover:bg-palette-200/20'}`,
|
||||
}))``
|
||||
className: `bg-palette-100/50 odd:bg-white ${noHoverEffect ? "" : "hover:bg-palette-200/20"}`,
|
||||
}))``;
|
||||
|
||||
/**
|
||||
* Allows disabling `hover` effect on a row. Useful for `<thead>` row.
|
||||
*/
|
||||
TableRow.propTypes = {
|
||||
noHoverEffect: PropTypes.bool,
|
||||
}
|
||||
};
|
||||
|
||||
TableRow.defaultProps = {
|
||||
noHoverEffect: false,
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
export * from './Table'
|
||||
export * from './TableHead'
|
||||
export * from './TableHeadCell'
|
||||
export * from './TableBody'
|
||||
export * from './TableRow'
|
||||
export * from './TableCell'
|
||||
export * from "./Table";
|
||||
export * from "./TableHead";
|
||||
export * from "./TableHeadCell";
|
||||
export * from "./TableBody";
|
||||
export * from "./TableRow";
|
||||
export * from "./TableCell";
|
||||
|
|
|
@ -1,38 +1,38 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import styled from 'styled-components'
|
||||
import { useEffect, useState } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import styled from "styled-components";
|
||||
|
||||
const Wrapper = styled.div.attrs({
|
||||
className: 'absolute left-0 bottom-0 w-full h-0.5 bg-palette-200',
|
||||
})``
|
||||
className: "absolute left-0 bottom-0 w-full h-0.5 bg-palette-200",
|
||||
})``;
|
||||
|
||||
const Indicator = styled.div.attrs({
|
||||
className: 'absolute h-0.5 bottom-0 bg-primary duration-200 ease-in-out',
|
||||
className: "absolute h-0.5 bottom-0 bg-primary duration-200 ease-in-out",
|
||||
})`
|
||||
will-change: left, width;
|
||||
`
|
||||
`;
|
||||
|
||||
export const ActiveTabIndicator = ({ tabRef }) => {
|
||||
const [position, setPosition] = useState(0)
|
||||
const [width, setWidth] = useState(0)
|
||||
const [position, setPosition] = useState(0);
|
||||
const [width, setWidth] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (!tabRef?.current) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
const { offsetLeft, offsetWidth } = tabRef.current
|
||||
setPosition(offsetLeft)
|
||||
setWidth(offsetWidth)
|
||||
}, [tabRef])
|
||||
const { offsetLeft, offsetWidth } = tabRef.current;
|
||||
setPosition(offsetLeft);
|
||||
setWidth(offsetWidth);
|
||||
}, [tabRef]);
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<Indicator style={{ left: position, width: `${width}px` }} />
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
ActiveTabIndicator.propTypes = {
|
||||
tabRef: PropTypes.oneOfType([PropTypes.func, PropTypes.shape({ current: PropTypes.instanceOf(Element) })]),
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
import { forwardRef } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import styled from 'styled-components'
|
||||
import { forwardRef } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import styled from "styled-components";
|
||||
|
||||
export const StyledTab = styled.button.attrs(({ active, variant }) => ({
|
||||
className: `m-0 px-2 pb-2
|
||||
text-tab text-left font-sans
|
||||
transition-colors hover:text-palette-500
|
||||
${active ? 'font-semibold text-palette-600' : 'font-light text-palette-300'}
|
||||
${variant === 'regular' ? 'sm:min-w-[180px]' : ''}`,
|
||||
}))``
|
||||
${active ? "font-semibold text-palette-600" : "font-light text-palette-300"}
|
||||
${variant === "regular" ? "sm:min-w-[180px]" : ""}`,
|
||||
}))``;
|
||||
|
||||
export const Tab = forwardRef(({ active, title, id, variant, ...props }, ref) => (
|
||||
<StyledTab
|
||||
ref={ref}
|
||||
role="tab"
|
||||
type="button"
|
||||
ariaSelected={`${active ? 'true' : 'false'}`}
|
||||
ariaSelected={`${active ? "true" : "false"}`}
|
||||
ariaControls={`tabpanel-${id}`}
|
||||
id={`tab-${id}`}
|
||||
active={active}
|
||||
|
@ -24,9 +24,9 @@ export const Tab = forwardRef(({ active, title, id, variant, ...props }, ref) =>
|
|||
>
|
||||
{title}
|
||||
</StyledTab>
|
||||
))
|
||||
));
|
||||
|
||||
Tab.displayName = 'Tab'
|
||||
Tab.displayName = "Tab";
|
||||
|
||||
Tab.propTypes = {
|
||||
/**
|
||||
|
@ -48,8 +48,8 @@ Tab.propTypes = {
|
|||
* Controlled by `Tabs` component.
|
||||
*/
|
||||
variant: PropTypes.string,
|
||||
}
|
||||
};
|
||||
|
||||
Tab.defaultProps = {
|
||||
variant: 'regular',
|
||||
}
|
||||
variant: "regular",
|
||||
};
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
import PropTypes from 'prop-types'
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
/**
|
||||
* Besides documented props, it accepts all HMTL attributes a `<div>` element does.
|
||||
*/
|
||||
export const TabPanel = ({ children, active, tabId, ...props }) => {
|
||||
if (!active) {
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div role="tabpanel" id={`tabpanel-${tabId}`} aria-labelledby={`tab-${tabId}`} {...props}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
TabPanel.propTypes = {
|
||||
/**
|
||||
|
@ -28,4 +28,4 @@ TabPanel.propTypes = {
|
|||
* Controlled by `Tabs` component.
|
||||
*/
|
||||
active: PropTypes.bool,
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,61 +1,61 @@
|
|||
import { cloneElement, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import styled from 'styled-components'
|
||||
import { cloneElement, useCallback, useEffect, useMemo, useState } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import styled from "styled-components";
|
||||
|
||||
import { ActiveTabIndicator } from './ActiveTabIndicator'
|
||||
import { usePrefixedTabIds, useTabsChildren } from './hooks'
|
||||
import { ActiveTabIndicator } from "./ActiveTabIndicator";
|
||||
import { usePrefixedTabIds, useTabsChildren } from "./hooks";
|
||||
|
||||
const Container = styled.div.attrs({
|
||||
className: 'tabs-container',
|
||||
})``
|
||||
className: "tabs-container",
|
||||
})``;
|
||||
|
||||
const Header = styled.div.attrs({
|
||||
className: 'relative flex justify-start overflow-hidden',
|
||||
})``
|
||||
className: "relative flex justify-start overflow-hidden",
|
||||
})``;
|
||||
|
||||
const TabList = styled.div.attrs(({ variant }) => ({
|
||||
role: 'tablist',
|
||||
role: "tablist",
|
||||
className: `relative inline-grid grid-flow-col auto-cols-fr
|
||||
${variant === 'regular' ? 'w-full sm:w-auto' : 'w-full'}`,
|
||||
}))``
|
||||
${variant === "regular" ? "w-full sm:w-auto" : "w-full"}`,
|
||||
}))``;
|
||||
|
||||
const Divider = styled.div.attrs({
|
||||
'aria-hidden': 'true',
|
||||
className: 'absolute bottom-0 w-screen border-b border-b-palette-200',
|
||||
"aria-hidden": "true",
|
||||
className: "absolute bottom-0 w-screen border-b border-b-palette-200",
|
||||
})`
|
||||
right: calc(-100vw - 2px);
|
||||
`
|
||||
`;
|
||||
|
||||
const Body = styled.div``
|
||||
const Body = styled.div``;
|
||||
|
||||
/**
|
||||
* Besides documented props, it accepts all HMTL attributes a `<div>` element does.
|
||||
*/
|
||||
export const Tabs = ({ defaultTab, children, variant }) => {
|
||||
const getTabId = usePrefixedTabIds()
|
||||
const { tabs, panels, tabsRefs } = useTabsChildren(children, getTabId)
|
||||
const defaultTabId = useMemo(() => getTabId(defaultTab || tabs[0].props.id), [getTabId, defaultTab, tabs])
|
||||
const [activeTabId, setActiveTabId] = useState(defaultTabId)
|
||||
const [activeTabRef, setActiveTabRef] = useState(tabsRefs[activeTabId])
|
||||
const isActive = (id) => id === activeTabId
|
||||
const getTabId = usePrefixedTabIds();
|
||||
const { tabs, panels, tabsRefs } = useTabsChildren(children, getTabId);
|
||||
const defaultTabId = useMemo(() => getTabId(defaultTab || tabs[0].props.id), [getTabId, defaultTab, tabs]);
|
||||
const [activeTabId, setActiveTabId] = useState(defaultTabId);
|
||||
const [activeTabRef, setActiveTabRef] = useState(tabsRefs[activeTabId]);
|
||||
const isActive = (id) => id === activeTabId;
|
||||
const onTabChange = useCallback(
|
||||
(id) => {
|
||||
setActiveTabId(id)
|
||||
setActiveTabId(id);
|
||||
},
|
||||
[setActiveTabId]
|
||||
)
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
// Refresh active tab indicator whenever active tab changes.
|
||||
setActiveTabRef(tabsRefs[activeTabId])
|
||||
}, [setActiveTabRef, tabsRefs, activeTabId])
|
||||
setActiveTabRef(tabsRefs[activeTabId]);
|
||||
}, [setActiveTabRef, tabsRefs, activeTabId]);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Header>
|
||||
<TabList variant={variant}>
|
||||
{tabs.map((tab) => {
|
||||
const tabId = getTabId(tab.props.id)
|
||||
const tabId = getTabId(tab.props.id);
|
||||
|
||||
return cloneElement(tab, {
|
||||
ref: tabsRefs[tabId],
|
||||
|
@ -63,7 +63,7 @@ export const Tabs = ({ defaultTab, children, variant }) => {
|
|||
variant,
|
||||
active: isActive(tabId),
|
||||
onClick: () => onTabChange(tabId),
|
||||
})
|
||||
});
|
||||
})}
|
||||
<Divider />
|
||||
<ActiveTabIndicator tabRef={activeTabRef} />
|
||||
|
@ -71,18 +71,18 @@ export const Tabs = ({ defaultTab, children, variant }) => {
|
|||
</Header>
|
||||
<Body>
|
||||
{panels.map((panel) => {
|
||||
const tabId = getTabId(panel.props.tabId)
|
||||
const tabId = getTabId(panel.props.tabId);
|
||||
|
||||
return cloneElement(panel, {
|
||||
...panel.props,
|
||||
tabId,
|
||||
active: isActive(tabId),
|
||||
})
|
||||
});
|
||||
})}
|
||||
</Body>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
Tabs.propTypes = {
|
||||
/**
|
||||
|
@ -98,9 +98,9 @@ Tabs.propTypes = {
|
|||
*
|
||||
* `fill` will make the tabs spread throughout the available width
|
||||
*/
|
||||
variant: PropTypes.oneOf(['regular', 'fill']),
|
||||
}
|
||||
variant: PropTypes.oneOf(["regular", "fill"]),
|
||||
};
|
||||
|
||||
Tabs.defaultProps = {
|
||||
variant: 'regular',
|
||||
}
|
||||
variant: "regular",
|
||||
};
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { Tab, TabPanel, Tabs } from './'
|
||||
import { Tab, TabPanel, Tabs } from "./";
|
||||
|
||||
export default {
|
||||
title: 'SkynetLibrary/Tabs',
|
||||
title: "SkynetLibrary/Tabs",
|
||||
component: Tabs,
|
||||
subcomponents: { Tab, TabPanel },
|
||||
}
|
||||
};
|
||||
|
||||
const Template = (props) => (
|
||||
<>
|
||||
|
@ -29,39 +29,39 @@ const Template = (props) => (
|
|||
</TabPanel>
|
||||
</Tabs>
|
||||
</>
|
||||
)
|
||||
);
|
||||
|
||||
const RegularTabs = Template.bind({})
|
||||
const RegularTabs = Template.bind({});
|
||||
|
||||
const FillingTabs = Template.bind({})
|
||||
const FillingTabs = Template.bind({});
|
||||
FillingTabs.args = {
|
||||
variant: 'fill',
|
||||
}
|
||||
variant: "fill",
|
||||
};
|
||||
|
||||
const FillingTabsInNarrowContainer = Template.bind({})
|
||||
const FillingTabsInNarrowContainer = Template.bind({});
|
||||
FillingTabsInNarrowContainer.args = {
|
||||
variant: 'fill',
|
||||
defaultTab: 'downloads',
|
||||
}
|
||||
variant: "fill",
|
||||
defaultTab: "downloads",
|
||||
};
|
||||
FillingTabsInNarrowContainer.decorators = [
|
||||
(Story) => (
|
||||
<div style={{ width: 360, background: '#fafafa', padding: 10, border: '1px solid #eee' }}>
|
||||
<div style={{ width: 360, background: "#fafafa", padding: 10, border: "1px solid #eee" }}>
|
||||
<Story />
|
||||
</div>
|
||||
),
|
||||
]
|
||||
];
|
||||
|
||||
const MultipleTabsComponents = Template.bind({})
|
||||
const MultipleTabsComponents = Template.bind({});
|
||||
MultipleTabsComponents.args = {
|
||||
variant: 'fill',
|
||||
}
|
||||
variant: "fill",
|
||||
};
|
||||
MultipleTabsComponents.decorators = [
|
||||
(Story) => (
|
||||
<div style={{ width: 360, background: '#fafafa', padding: 10, border: '1px solid #eee' }}>
|
||||
<div style={{ width: 360, background: "#fafafa", padding: 10, border: "1px solid #eee" }}>
|
||||
<Story />
|
||||
<Story />
|
||||
</div>
|
||||
),
|
||||
]
|
||||
];
|
||||
|
||||
export { RegularTabs, FillingTabs, FillingTabsInNarrowContainer, MultipleTabsComponents }
|
||||
export { RegularTabs, FillingTabs, FillingTabsInNarrowContainer, MultipleTabsComponents };
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
import { Children, createRef, useCallback, useMemo } from 'react'
|
||||
import { Children, createRef, useCallback, useMemo } from "react";
|
||||
|
||||
import { Tab } from './Tab'
|
||||
import { TabPanel } from './TabPanel'
|
||||
import { Tab } from "./Tab";
|
||||
import { TabPanel } from "./TabPanel";
|
||||
|
||||
export const usePrefixedTabIds = () => {
|
||||
const seed = useMemo(() => Math.random().toString().split('.')[1], [])
|
||||
const seed = useMemo(() => Math.random().toString().split(".")[1], []);
|
||||
|
||||
return useCallback((id) => `${seed}-${id}`, [seed])
|
||||
}
|
||||
return useCallback((id) => `${seed}-${id}`, [seed]);
|
||||
};
|
||||
|
||||
export const useTabsChildren = (children, prefixId) => {
|
||||
const childrenArray = useMemo(() => Children.toArray(children), [children])
|
||||
const tabs = useMemo(() => childrenArray.filter(({ type }) => type === Tab), [childrenArray])
|
||||
const panels = useMemo(() => childrenArray.filter(({ type }) => type === TabPanel), [childrenArray])
|
||||
const childrenArray = useMemo(() => Children.toArray(children), [children]);
|
||||
const tabs = useMemo(() => childrenArray.filter(({ type }) => type === Tab), [childrenArray]);
|
||||
const panels = useMemo(() => childrenArray.filter(({ type }) => type === TabPanel), [childrenArray]);
|
||||
const tabsRefs = useMemo(
|
||||
() =>
|
||||
tabs.reduce(
|
||||
|
@ -23,11 +23,11 @@ export const useTabsChildren = (children, prefixId) => {
|
|||
{}
|
||||
),
|
||||
[tabs, prefixId]
|
||||
)
|
||||
);
|
||||
|
||||
return {
|
||||
tabs,
|
||||
panels,
|
||||
tabsRefs,
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
export * from './Tab'
|
||||
export * from './Tabs'
|
||||
export * from './TabPanel'
|
||||
export * from "./Tab";
|
||||
export * from "./Tabs";
|
||||
export * from "./TabPanel";
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import PropTypes from 'prop-types'
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
/**
|
||||
* Primary UI component for user interaction
|
||||
|
@ -7,25 +7,25 @@ export const TextIndicator = ({ variant }) => {
|
|||
return (
|
||||
<div
|
||||
className={`flex justify-center items-center w-textIndicator h-textIndicator text-textIndicator font-sans uppercase tracking-wide text-button bg-opacity-10 ${
|
||||
variant === 'success'
|
||||
? 'text-primary bg-primary'
|
||||
: variant === 'next'
|
||||
? 'text-next bg-next'
|
||||
: 'text-error bg-error'
|
||||
variant === "success"
|
||||
? "text-primary bg-primary"
|
||||
: variant === "next"
|
||||
? "text-next bg-next"
|
||||
: "text-error bg-error"
|
||||
}`}
|
||||
>
|
||||
{variant === 'success' ? 'success' : variant === 'next' ? 'next' : 'error'}
|
||||
{variant === "success" ? "success" : variant === "next" ? "next" : "error"}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
TextIndicator.propTypes = {
|
||||
/**
|
||||
* Variant of text indicator
|
||||
*/
|
||||
variant: PropTypes.oneOf(['success', 'next', 'error']),
|
||||
}
|
||||
variant: PropTypes.oneOf(["success", "next", "error"]),
|
||||
};
|
||||
|
||||
TextIndicator.defaultProps = {
|
||||
variant: 'success',
|
||||
}
|
||||
variant: "success",
|
||||
};
|
||||
|
|
|
@ -1,27 +1,27 @@
|
|||
import { TextIndicator } from './TextIndicator'
|
||||
import { TextIndicator } from "./TextIndicator";
|
||||
|
||||
// More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
|
||||
export default {
|
||||
title: 'SkynetLibrary/TextIndicator',
|
||||
title: "SkynetLibrary/TextIndicator",
|
||||
component: TextIndicator,
|
||||
// More on argTypes: https://storybook.js.org/docs/react/api/argtypes
|
||||
}
|
||||
};
|
||||
|
||||
// More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
|
||||
const Template = (args) => <TextIndicator {...args} />
|
||||
const Template = (args) => <TextIndicator {...args} />;
|
||||
|
||||
export const Success = Template.bind({})
|
||||
export const Success = Template.bind({});
|
||||
// More on args: https://storybook.js.org/docs/react/writing-stories/args
|
||||
Success.args = {
|
||||
variant: 'success',
|
||||
}
|
||||
variant: "success",
|
||||
};
|
||||
|
||||
export const Next = Template.bind({})
|
||||
export const Next = Template.bind({});
|
||||
Next.args = {
|
||||
variant: 'next',
|
||||
}
|
||||
variant: "next",
|
||||
};
|
||||
|
||||
export const Error = Template.bind({})
|
||||
export const Error = Template.bind({});
|
||||
Error.args = {
|
||||
variant: 'error',
|
||||
}
|
||||
variant: "error",
|
||||
};
|
||||
|
|
|
@ -1 +1 @@
|
|||
export * from './TextIndicator'
|
||||
export * from "./TextIndicator";
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
import PropTypes from 'prop-types'
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
/**
|
||||
* Primary UI component for user interaction
|
||||
*/
|
||||
export const TextInputBasic = ({ label, placeholder }) => {
|
||||
return (
|
||||
<div className={''}>
|
||||
<p className={'font-sans uppercase text-palette-300 text-inputLabel mb-textInputLabelBottom'}>{label}</p>
|
||||
<div className={""}>
|
||||
<p className={"font-sans uppercase text-palette-300 text-inputLabel mb-textInputLabelBottom"}>{label}</p>
|
||||
<input
|
||||
placeholder={placeholder}
|
||||
className={
|
||||
'w-full bg-palette-100 h-textInput px-textInputBasicX focus:outline-none bg-transparent ' +
|
||||
'placeholder-palette-400 text-content tracking-inputPlaceholder text-textInput'
|
||||
"w-full bg-palette-100 h-textInput px-textInputBasicX focus:outline-none bg-transparent " +
|
||||
"placeholder-palette-400 text-content tracking-inputPlaceholder text-textInput"
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
TextInputBasic.propTypes = {
|
||||
/**
|
||||
|
@ -27,4 +27,4 @@ TextInputBasic.propTypes = {
|
|||
* Input placeholder
|
||||
*/
|
||||
placeholder: PropTypes.string,
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
import { TextInputBasic } from './TextInputBasic'
|
||||
import { TextInputBasic } from "./TextInputBasic";
|
||||
|
||||
// More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
|
||||
export default {
|
||||
title: 'SkynetLibrary/TextInputBasic',
|
||||
title: "SkynetLibrary/TextInputBasic",
|
||||
component: TextInputBasic,
|
||||
// More on argTypes: https://storybook.js.org/docs/react/api/argtypes
|
||||
}
|
||||
};
|
||||
|
||||
// More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
|
||||
const Template = (args) => <TextInputBasic {...args} />
|
||||
const Template = (args) => <TextInputBasic {...args} />;
|
||||
|
||||
export const Input = Template.bind({})
|
||||
export const Input = Template.bind({});
|
||||
// More on args: https://storybook.js.org/docs/react/writing-stories/args
|
||||
Input.args = {
|
||||
label: 'Display Name',
|
||||
placeholder: 'Your Name',
|
||||
}
|
||||
label: "Display Name",
|
||||
placeholder: "Your Name",
|
||||
};
|
||||
|
|
|
@ -1 +1 @@
|
|||
export * from './TextInputBasic'
|
||||
export * from "./TextInputBasic";
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
import PropTypes from 'prop-types'
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
/**
|
||||
* Primary UI component for user interaction
|
||||
*/
|
||||
export const TextInputIcon = ({ icon, position, placeholder }) => {
|
||||
return (
|
||||
<div className={'flex flex-row items-center px-textInputIcon h-textInput rounded-full bg-palette-100'}>
|
||||
{position === 'left' ? <div className={'w-buttonIconLg h-buttonIconLg'}>{icon}</div> : null}
|
||||
<div className={"flex flex-row items-center px-textInputIcon h-textInput rounded-full bg-palette-100"}>
|
||||
{position === "left" ? <div className={"w-buttonIconLg h-buttonIconLg"}>{icon}</div> : null}
|
||||
<input
|
||||
placeholder={placeholder}
|
||||
className={
|
||||
'w-full focus:outline-none mx-textInputHorizontal rounded-full bg-transparent ' +
|
||||
'placeholder-palette-400 text-content tracking-inputPlaceholder text-textInput'
|
||||
"w-full focus:outline-none mx-textInputHorizontal rounded-full bg-transparent " +
|
||||
"placeholder-palette-400 text-content tracking-inputPlaceholder text-textInput"
|
||||
}
|
||||
/>
|
||||
{position === 'right' ? <div className={'w-buttonIconLg h-buttonIconLg'}>{icon}</div> : null}
|
||||
{position === "right" ? <div className={"w-buttonIconLg h-buttonIconLg"}>{icon}</div> : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
TextInputIcon.propTypes = {
|
||||
/**
|
||||
|
@ -27,9 +27,9 @@ TextInputIcon.propTypes = {
|
|||
/**
|
||||
* Side to place icon
|
||||
*/
|
||||
position: PropTypes.oneOf(['left', 'right']),
|
||||
position: PropTypes.oneOf(["left", "right"]),
|
||||
/**
|
||||
* Input placeholder
|
||||
*/
|
||||
placeholder: PropTypes.string,
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,27 +1,27 @@
|
|||
import { TextInputIcon } from './TextInputIcon'
|
||||
import { CogIcon } from '../Icons'
|
||||
import { TextInputIcon } from "./TextInputIcon";
|
||||
import { CogIcon } from "../Icons";
|
||||
|
||||
// More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
|
||||
export default {
|
||||
title: 'SkynetLibrary/TextInputIcon',
|
||||
title: "SkynetLibrary/TextInputIcon",
|
||||
component: TextInputIcon,
|
||||
// More on argTypes: https://storybook.js.org/docs/react/api/argtypes
|
||||
}
|
||||
};
|
||||
|
||||
// More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
|
||||
const Template = (args) => <TextInputIcon {...args} />
|
||||
const Template = (args) => <TextInputIcon {...args} />;
|
||||
|
||||
export const IconLeft = Template.bind({})
|
||||
export const IconLeft = Template.bind({});
|
||||
// More on args: https://storybook.js.org/docs/react/writing-stories/args
|
||||
IconLeft.args = {
|
||||
icon: <CogIcon />,
|
||||
position: 'left',
|
||||
placeholder: 'Search',
|
||||
}
|
||||
position: "left",
|
||||
placeholder: "Search",
|
||||
};
|
||||
|
||||
export const IconRight = Template.bind({})
|
||||
export const IconRight = Template.bind({});
|
||||
IconRight.args = {
|
||||
icon: <CogIcon />,
|
||||
position: 'right',
|
||||
placeholder: 'Search',
|
||||
}
|
||||
position: "right",
|
||||
placeholder: "Search",
|
||||
};
|
||||
|
|
|
@ -1 +1 @@
|
|||
export * from './TextInputIcon'
|
||||
export * from "./TextInputIcon";
|
||||
|
|
|
@ -34,8 +34,8 @@ module.exports = {
|
|||
content: ["Source\\ Sans\\ Pro", ...defaultTheme.fontFamily.sans],
|
||||
},
|
||||
fontSize: {
|
||||
body: ['21px', { lineHeight: '1.58' }],
|
||||
tab: ['18px', '28px'],
|
||||
body: ["21px", { lineHeight: "1.58" }],
|
||||
tab: ["18px", "28px"],
|
||||
},
|
||||
backgroundColor: ["disabled"],
|
||||
textColor: ["disabled"],
|
||||
|
@ -55,7 +55,7 @@ module.exports = {
|
|||
"page-xl": "1312px",
|
||||
},
|
||||
minWidth: {
|
||||
button: '112px',
|
||||
button: "112px",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
Reference in New Issue