initial commit

This commit is contained in:
unknown 2021-07-28 18:45:52 +05:30
commit 026f835a87
176 changed files with 10613 additions and 0 deletions

View file

@ -0,0 +1,57 @@
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import './Avatar.scss';
import Text from '../text/Text';
import RawIcon from '../system-icons/RawIcon';
function Avatar({
text, bgColor, iconSrc, imageSrc, size,
}) {
const [image, updateImage] = useState(imageSrc);
let textSize = 's1';
if (size === 'large') textSize = 'h1';
if (size === 'small') textSize = 'b1';
if (size === 'extra-small') textSize = 'b3';
useEffect(() => updateImage(imageSrc), [imageSrc]);
return (
<div className={`avatar-container avatar-container__${size} noselect`}>
{
image !== null
? <img src={image} onError={() => updateImage(null)} alt="avatar" />
: (
<span
style={{ backgroundColor: iconSrc === null ? bgColor : 'transparent' }}
className={`avatar__border${iconSrc !== null ? ' avatar__bordered' : ''} inline-flex--center`}
>
{
iconSrc !== null
? <RawIcon size={size} src={iconSrc} />
: text !== null && <Text variant={textSize}>{text}</Text>
}
</span>
)
}
</div>
);
}
Avatar.defaultProps = {
text: null,
bgColor: 'transparent',
iconSrc: null,
imageSrc: null,
size: 'normal',
};
Avatar.propTypes = {
text: PropTypes.string,
bgColor: PropTypes.string,
iconSrc: PropTypes.string,
imageSrc: PropTypes.string,
size: PropTypes.oneOf(['large', 'normal', 'small', 'extra-small']),
};
export default Avatar;

View file

@ -0,0 +1,52 @@
.avatar-container {
display: inline-flex;
width: 42px;
height: 42px;
border-radius: var(--bo-radius);
position: relative;
&__large {
width: var(--av-large);
height: var(--av-large);
}
&__normal {
width: var(--av-normal);
height: var(--av-normal);
}
&__small {
width: var(--av-small);
height: var(--av-small);
}
&__extra-small {
width: var(--av-extra-small);
height: var(--av-extra-small);
}
img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: inherit;
}
.avatar__bordered {
box-shadow: var(--bs-surface-border);
}
.avatar__border {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border-radius: inherit;
.text {
color: var(--tc-primary-high);
}
}
}

View file

@ -0,0 +1,28 @@
import React from 'react';
import PropTypes from 'prop-types';
import './NotificationBadge.scss';
import Text from '../text/Text';
function NotificationBadge({ alert, children }) {
const notificationClass = alert ? ' notification-badge--alert' : '';
return (
<div className={`notification-badge${notificationClass}`}>
<Text variant="b3">{children}</Text>
</div>
);
}
NotificationBadge.defaultProps = {
alert: false,
};
NotificationBadge.propTypes = {
alert: PropTypes.bool,
children: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
]).isRequired,
};
export default NotificationBadge;

View file

@ -0,0 +1,18 @@
.notification-badge {
min-width: 18px;
padding: 1px var(--sp-ultra-tight);
background-color: var(--tc-surface-low);
border-radius: 9px;
.text {
color: var(--bg-surface-low);
text-align: center;
}
&--alert {
background-color: var(--bg-positive);
.text {
color: white;
}
}
}

View file

@ -0,0 +1,47 @@
import React from 'react';
import PropTypes from 'prop-types';
import './Button.scss';
import Text from '../text/Text';
import RawIcon from '../system-icons/RawIcon';
import { blurOnBubbling } from './script';
function Button({
id, variant, iconSrc, type, onClick, children, disabled,
}) {
const iconClass = (iconSrc === null) ? '' : `btn-${variant}--icon`;
return (
<button
id={id === '' ? undefined : id}
className={`btn-${variant} ${iconClass} noselect`}
onMouseUp={(e) => blurOnBubbling(e, `.btn-${variant}`)}
onClick={onClick}
type={type === 'button' ? 'button' : 'submit'}
disabled={disabled}
>
{iconSrc !== null && <RawIcon size="small" src={iconSrc} />}
<Text variant="b1">{ children }</Text>
</button>
);
}
Button.defaultProps = {
id: '',
variant: 'surface',
iconSrc: null,
type: 'button',
onClick: null,
disabled: false,
};
Button.propTypes = {
id: PropTypes.string,
variant: PropTypes.oneOf(['surface', 'primary', 'caution', 'danger']),
iconSrc: PropTypes.string,
type: PropTypes.oneOf(['button', 'submit']),
onClick: PropTypes.func,
children: PropTypes.node.isRequired,
disabled: PropTypes.bool,
};
export default Button;

View file

@ -0,0 +1,83 @@
@use 'state';
.btn-surface,
.btn-primary,
.btn-caution,
.btn-danger {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 80px;
padding: var(--sp-extra-tight) var(--sp-normal);
background-color: transparent;
border: none;
border-radius: var(--bo-radius);
cursor: pointer;
@include state.disabled;
&--icon {
padding: {
left: var(--sp-tight);
right: var(--sp-loose);
}
[dir=rtl] & {
padding: {
left: var(--sp-loose);
right: var(--sp-tight);
}
}
.ic-raw {
margin-right: var(--sp-extra-tight);
[dir=rtl] & {
margin: {
right: 0;
left: var(--sp-extra-tight);
}
}
}
}
}
@mixin color($textColor, $iconColor) {
.text {
color: $textColor;
}
.ic-raw {
background-color: $iconColor;
}
}
.btn-surface {
box-shadow: var(--bs-surface-border);
@include color(var(--tc-surface-high), var(--ic-surface-normal));
@include state.hover(var(--bg-surface-hover));
@include state.focus(var(--bs-surface-outline));
@include state.active(var(--bg-surface-active));
}
.btn-primary {
background-color: var(--bg-primary);
@include color(var(--tc-primary-high), var(--ic-primary-normal));
@include state.hover(var(--bg-primary-hover));
@include state.focus(var(--bs-primary-outline));
@include state.active(var(--bg-primary-active));
}
.btn-caution {
box-shadow: var(--bs-caution-border);
@include color(var(--tc-caution-high), var(--ic-caution-normal));
@include state.hover(var(--bg-caution-hover));
@include state.focus(var(--bs-caution-outline));
@include state.active(var(--bg-caution-active));
}
.btn-danger {
box-shadow: var(--bs-danger-border);
@include color(var(--tc-danger-high), var(--ic-danger-normal));
@include state.hover(var(--bg-danger-hover));
@include state.focus(var(--bs-danger-outline));
@include state.active(var(--bg-danger-active));
}

View file

@ -0,0 +1,60 @@
import React from 'react';
import PropTypes from 'prop-types';
import './IconButton.scss';
import Tippy from '@tippyjs/react';
import RawIcon from '../system-icons/RawIcon';
import { blurOnBubbling } from './script';
import Text from '../text/Text';
// TODO:
// 1. [done] an icon only button have "src"
// 2. have multiple variant
// 3. [done] should have a smart accessibility "label" arial-label
// 4. [done] have size as RawIcon
const IconButton = React.forwardRef(({
variant, size, type,
tooltip, tooltipPlacement, src, onClick,
}, ref) => (
<Tippy
content={<Text variant="b2">{tooltip}</Text>}
className="ic-btn-tippy"
touch="hold"
arrow={false}
maxWidth={250}
placement={tooltipPlacement}
delay={[0, 0]}
duration={[100, 0]}
>
<button
ref={ref}
className={`ic-btn-${variant}`}
onMouseUp={(e) => blurOnBubbling(e, `.ic-btn-${variant}`)}
onClick={onClick}
type={type === 'button' ? 'button' : 'submit'}
>
<RawIcon size={size} src={src} />
</button>
</Tippy>
));
IconButton.defaultProps = {
variant: 'surface',
size: 'normal',
type: 'button',
tooltipPlacement: 'top',
onClick: null,
};
IconButton.propTypes = {
variant: PropTypes.oneOf(['surface']),
size: PropTypes.oneOf(['normal', 'small', 'extra-small']),
type: PropTypes.oneOf(['button', 'submit']),
tooltip: PropTypes.string.isRequired,
tooltipPlacement: PropTypes.oneOf(['top', 'right', 'bottom', 'left']),
src: PropTypes.string.isRequired,
onClick: PropTypes.func,
};
export default IconButton;

View file

@ -0,0 +1,45 @@
@use 'state';
.ic-btn-surface,
.ic-btn-primary,
.ic-btn-caution,
.ic-btn-danger {
padding: var(--sp-extra-tight);
border: none;
border-radius: var(--bo-radius);
background-color: transparent;
font-size: 0;
line-height: 0;
cursor: pointer;
@include state.disabled;
}
@mixin color($color) {
.ic-raw {
background-color: $color;
}
}
@mixin focus($color) {
&:focus {
outline: none;
background-color: $color;
}
}
.ic-btn-surface {
@include color(var(--ic-surface-normal));
@include state.hover(var(--bg-surface-hover));
@include focus(var(--bg-surface-hover));
@include state.active(var(--bg-surface-active));
}
.ic-btn-tippy {
padding: var(--sp-extra-tight) var(--sp-normal);
background-color: var(--bg-tooltip);
border-radius: var(--bo-radius);
box-shadow: var(--bs-popup);
.text {
color: var(--tc-tooltip);
}
}

View file

@ -0,0 +1,25 @@
import React from 'react';
import PropTypes from 'prop-types';
import './Toggle.scss';
function Toggle({ isActive, onToggle }) {
return (
// eslint-disable-next-line jsx-a11y/control-has-associated-label
<button
onClick={() => onToggle(!isActive)}
className={`toggle${isActive ? ' toggle--active' : ''}`}
type="button"
/>
);
}
Toggle.defaultProps = {
isActive: false,
};
Toggle.propTypes = {
isActive: PropTypes.bool,
onToggle: PropTypes.func.isRequired,
};
export default Toggle;

View file

@ -0,0 +1,39 @@
.toggle {
width: 44px;
height: 24px;
padding: 0 var(--sp-ultra-tight);
display: flex;
align-items: center;
border-radius: var(--bo-radius);
box-shadow: var(--bs-surface-border);
cursor: pointer;
background-color: var(--bg-surface-low);
transition: background 200ms ease-in-out;
&::before {
content: '';
display: inline-block;
width: 16px;
height: 16px;
background-color: var(--tc-surface-low);
border-radius: calc(var(--bo-radius) / 2);
transition: transform 200ms ease-in-out,
opacity 200ms ease-in-out;
opacity: 0.6;
}
&--active {
background-color: var(--bg-positive);
&::before {
background-color: white;
transform: translateX(calc(125%));
opacity: 1;
[dir=rtl] & {
transform: translateX(calc(-125%));
}
}
}
}

View file

@ -0,0 +1,25 @@
@mixin hover($color) {
@media (hover: hover) {
&:hover {
background-color: $color;
}
}
}
@mixin focus($outline) {
&:focus {
outline: none;
box-shadow: $outline;
}
}
@mixin active($color) {
&:active {
background-color: $color !important;
}
}
@mixin disabled {
&:disabled {
opacity: 0.4;
cursor: no-drop;
}
}

View file

@ -0,0 +1,23 @@
/**
* blur [selector] element in bubbling path.
* @param {Event} e Event
* @param {string} selector element selector for Element.matches([selector])
* @return {boolean} if blured return true, else return false with warning in console
*/
function blurOnBubbling(e, selector) {
const bubblingPath = e.nativeEvent.composedPath();
for (let elIndex = 0; elIndex < bubblingPath.length; elIndex += 1) {
if (bubblingPath[elIndex] === document) {
console.warn(blurOnBubbling, 'blurOnBubbling: not found selector in bubbling path');
break;
}
if (bubblingPath[elIndex].matches(selector)) {
setTimeout(() => bubblingPath[elIndex].blur(), 50);
return true;
}
}
return false;
}
export { blurOnBubbling };

View file

@ -0,0 +1,103 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import './ContextMenu.scss';
import Tippy from '@tippyjs/react';
import 'tippy.js/animations/scale-extreme.css';
import Text from '../text/Text';
import Button from '../button/Button';
import ScrollView from '../scroll/ScrollView';
function ContextMenu({
content, placement, maxWidth, render,
}) {
const [isVisible, setVisibility] = useState(false);
const showMenu = () => setVisibility(true);
const hideMenu = () => setVisibility(false);
return (
<Tippy
animation="scale-extreme"
className="context-menu"
visible={isVisible}
onClickOutside={hideMenu}
content={<ScrollView invisible>{typeof content === 'function' ? content(hideMenu) : content}</ScrollView>}
placement={placement}
interactive
arrow={false}
maxWidth={maxWidth}
>
{render(isVisible ? hideMenu : showMenu)}
</Tippy>
);
}
ContextMenu.defaultProps = {
maxWidth: 'unset',
placement: 'right',
};
ContextMenu.propTypes = {
content: PropTypes.oneOfType([
PropTypes.node,
PropTypes.func,
]).isRequired,
placement: PropTypes.oneOf(['top', 'right', 'bottom', 'left']),
maxWidth: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
]),
render: PropTypes.func.isRequired,
};
function MenuHeader({ children }) {
return (
<div className="context-menu__header">
<Text variant="b3">{ children }</Text>
</div>
);
}
MenuHeader.propTypes = {
children: PropTypes.string.isRequired,
};
function MenuItem({
variant, iconSrc, type, onClick, children,
}) {
return (
<div className="context-menu__item">
<Button
variant={variant}
iconSrc={iconSrc}
type={type}
onClick={onClick}
>
{ children }
</Button>
</div>
);
}
MenuItem.defaultProps = {
variant: 'surface',
iconSrc: 'none',
type: 'button',
};
MenuItem.propTypes = {
variant: PropTypes.oneOf(['surface', 'caution', 'danger']),
iconSrc: PropTypes.string,
type: PropTypes.oneOf(['button', 'submit']),
onClick: PropTypes.func.isRequired,
children: PropTypes.string.isRequired,
};
function MenuBorder() {
return <div style={{ borderBottom: '1px solid var(--bg-surface-border)' }}> </div>;
}
export {
ContextMenu as default, MenuHeader, MenuItem, MenuBorder,
};

View file

@ -0,0 +1,71 @@
.context-menu {
background-color: var(--bg-surface);
box-shadow: var(--bs-popup);
border-radius: var(--bo-radius);
overflow: hidden;
&:focus {
outline: none;
}
& .tippy-content > div > .scrollbar {
max-height: 90vh;
}
}
.context-menu__click-wrapper {
display: inline-flex;
&:focus {
outline: none;
}
}
.context-menu__header {
height: 34px;
padding: 0 var(--sp-tight);
margin-bottom: var(--sp-ultra-tight);
display: flex;
align-items: center;
border-bottom: 1px solid var(--bg-surface-border);
.text {
color: var(--tc-surface-low);
}
&:not(:first-child) {
margin-top: var(--sp-normal);
border-top: 1px solid var(--bg-surface-border);
}
}
.context-menu__item {
button[class^="btn"] {
width: 100%;
justify-content: start;
border-radius: 0;
box-shadow: none;
.text:first-child {
margin: {
left: calc(var(--ic-small) + var(--sp-ultra-tight));
right: var(--sp-extra-tight);
}
[dir=rtl] & {
margin: {
left: var(--sp-extra-tight);
right: calc(var(--ic-small) + var(--sp-ultra-tight));
}
}
}
}
.btn-surface:focus {
background-color: var(--bg-surface-hover);
}
.btn-caution:focus {
background-color: var(--bg-caution-hover);
}
.btn-danger:focus {
background-color: var(--bg-danger-hover);
}
}

View file

@ -0,0 +1,29 @@
import React from 'react';
import PropTypes from 'prop-types';
import './Divider.scss';
import Text from '../text/Text';
function Divider({ text, variant }) {
const dividerClass = ` divider--${variant}`;
return (
<div className={`divider${dividerClass}`}>
{text !== false && <Text className="divider__text" variant="b3">{text}</Text>}
</div>
);
}
Divider.defaultProps = {
text: false,
variant: 'surface',
};
Divider.propTypes = {
text: PropTypes.oneOfType([
PropTypes.string,
PropTypes.bool,
]),
variant: PropTypes.oneOf(['surface', 'primary', 'caution', 'danger']),
};
export default Divider;

View file

@ -0,0 +1,68 @@
.divider {
--local-divider-color: var(--bg-surface-border);
margin: var(--sp-extra-tight) var(--sp-normal);
margin-right: var(--sp-extra-tight);
display: flex;
align-items: center;
position: relative;
&::before {
content: "";
display: inline-block;
flex: 1;
margin-left: calc(var(--av-small) + var(--sp-tight));
border-bottom: 1px solid var(--local-divider-color);
opacity: 0.18;
[dir=rtl] & {
margin: {
left: 0;
right: calc(var(--av-small) + var(--sp-tight));
}
}
}
&__text {
margin-left: var(--sp-normal);
}
[dir=rtl] & {
margin: {
left: var(--sp-extra-tight);
right: var(--sp-normal);
}
&__text {
margin: {
left: 0;
right: var(--sp-normal);
}
}
}
}
.divider--surface {
--local-divider-color: var(--tc-surface-low);
.divider__text {
color: var(--tc-surface-low);
}
}
.divider--primary {
--local-divider-color: var(--bg-primary);
.divider__text {
color: var(--bg-primary);
}
}
.divider--danger {
--local-divider-color: var(--bg-danger);
.divider__text {
color: var(--bg-danger);
}
}
.divider--caution {
--local-divider-color: var(--bg-caution);
.divider__text {
color: var(--bg-caution);
}
}

View file

@ -0,0 +1,29 @@
import React from 'react';
import PropTypes from 'prop-types';
import './Header.scss';
function Header({ children }) {
return (
<div className="header">
{children}
</div>
);
}
Header.propTypes = {
children: PropTypes.node.isRequired,
};
function TitleWrapper({ children }) {
return (
<div className="header__title-wrapper">
{children}
</div>
);
}
TitleWrapper.propTypes = {
children: PropTypes.node.isRequired,
};
export { Header as default, TitleWrapper };

View file

@ -0,0 +1,63 @@
.header {
padding: {
left: var(--sp-normal);
right: var(--sp-extra-tight);
}
width: 100%;
height: var(--header-height);
border-bottom: 1px solid var(--bg-surface-border);
display: flex;
align-items: center;
[dir=rtl] & {
padding: {
left: var(--sp-extra-tight);
right: var(--sp-normal);
}
}
&__title-wrapper {
flex: 1;
min-width: 0;
display: flex;
align-items: center;
margin: 0 var(--sp-tight);
&:first-child {
margin-left: 0;
[dir=rtl] & {
margin-right: 0;
}
}
& > .text:first-child {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
& > .text-b3{
flex: 1;
min-width: 0;
margin-top: var(--sp-ultra-tight);
margin-left: var(--sp-tight);
padding-left: var(--sp-tight);
border-left: 1px solid var(--bg-surface-border);
max-height: calc(2 * var(--lh-b3));
overflow: hidden;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
display: -webkit-box;
[dir=rtl] & {
margin-left: 0;
padding-left: 0;
border-left: none;
margin-right: var(--sp-tight);
padding-right: var(--sp-tight);
border-right: 1px solid var(--bg-surface-border);
}
}
}
}

View file

@ -0,0 +1,77 @@
import React from 'react';
import PropTypes from 'prop-types';
import './Input.scss';
import TextareaAutosize from 'react-autosize-textarea';
function Input({
id, label, value, placeholder,
required, type, onChange, forwardRef,
resizable, minHeight, onResize, state,
}) {
return (
<div className="input-container">
{ label !== '' && <label className="input__label text-b2" htmlFor={id}>{label}</label> }
{ resizable
? (
<TextareaAutosize
style={{ minHeight: `${minHeight}px` }}
id={id}
className={`input input--resizable${state !== 'normal' ? ` input--${state}` : ''}`}
ref={forwardRef}
type={type}
placeholder={placeholder}
required={required}
defaultValue={value}
autoComplete="off"
onChange={onChange}
onResize={onResize}
/>
) : (
<input
ref={forwardRef}
id={id}
className={`input ${state !== 'normal' ? ` input--${state}` : ''}`}
type={type}
placeholder={placeholder}
required={required}
defaultValue={value}
autoComplete="off"
onChange={onChange}
/>
)}
</div>
);
}
Input.defaultProps = {
id: null,
label: '',
value: '',
placeholder: '',
type: 'text',
required: false,
onChange: null,
forwardRef: null,
resizable: false,
minHeight: 46,
onResize: null,
state: 'normal',
};
Input.propTypes = {
id: PropTypes.string,
label: PropTypes.string,
value: PropTypes.string,
placeholder: PropTypes.string,
required: PropTypes.bool,
type: PropTypes.string,
onChange: PropTypes.func,
forwardRef: PropTypes.shape({}),
resizable: PropTypes.bool,
minHeight: PropTypes.number,
onResize: PropTypes.func,
state: PropTypes.oneOf(['normal', 'success', 'error']),
};
export default Input;

View file

@ -0,0 +1,40 @@
.input {
display: block;
width: 100%;
min-width: 0px;
padding: var(--sp-tight) var(--sp-normal);
background-color: var(--bg-surface-low);
color: var(--tc-surface-normal);
box-shadow: none;
border-radius: var(--bo-radius);
border: 1px solid var(--bg-surface-border);
font-size: var(--fs-b2);
letter-spacing: var(--ls-b2);
line-height: var(--lh-b2);
&__label {
display: inline-block;
margin-bottom: var(--sp-ultra-tight);
color: var(--tc-surface-low);
}
&--resizable {
resize: vertical !important;
}
&--success {
border: 1px solid var(--bg-positive);
box-shadow: none !important;
}
&--error {
border: 1px solid var(--bg-danger);
box-shadow: none !important;
}
&:focus {
outline: none;
box-shadow: var(--bs-primary-border);
}
&::placeholder {
color: var(--tc-surface-low)
}
}

View file

@ -0,0 +1,67 @@
import React from 'react';
import PropTypes from 'prop-types';
import './RawModal.scss';
import Modal from 'react-modal';
Modal.setAppElement('#root');
function RawModal({
className, overlayClassName,
isOpen, size, onAfterOpen, onAfterClose,
onRequestClose, closeFromOutside, children,
}) {
let modalClass = (className !== null) ? `${className} ` : '';
switch (size) {
case 'large':
modalClass += 'raw-modal__large ';
break;
case 'medium':
modalClass += 'raw-modal__medium ';
break;
case 'small':
default:
modalClass += 'raw-modal__small ';
}
const modalOverlayClass = (overlayClassName !== null) ? `${overlayClassName} ` : '';
return (
<Modal
className={`${modalClass}raw-modal`}
overlayClassName={`${modalOverlayClass}raw-modal__overlay`}
isOpen={isOpen}
onAfterOpen={onAfterOpen}
onAfterClose={onAfterClose}
onRequestClose={onRequestClose}
shouldCloseOnEsc={closeFromOutside}
shouldCloseOnOverlayClick={closeFromOutside}
shouldReturnFocusAfterClose={false}
closeTimeoutMS={300}
>
{children}
</Modal>
);
}
RawModal.defaultProps = {
className: null,
overlayClassName: null,
size: 'small',
onAfterOpen: null,
onAfterClose: null,
onRequestClose: null,
closeFromOutside: true,
};
RawModal.propTypes = {
className: PropTypes.string,
overlayClassName: PropTypes.string,
isOpen: PropTypes.bool.isRequired,
size: PropTypes.oneOf(['large', 'medium', 'small']),
onAfterOpen: PropTypes.func,
onAfterClose: PropTypes.func,
onRequestClose: PropTypes.func,
closeFromOutside: PropTypes.bool,
children: PropTypes.node.isRequired,
};
export default RawModal;

View file

@ -0,0 +1,63 @@
.ReactModal__Overlay {
opacity: 0;
transition: opacity 200ms cubic-bezier(0.13, 0.56, 0.25, 0.99);
}
.ReactModal__Overlay--after-open{
opacity: 1;
}
.ReactModal__Overlay--before-close{
opacity: 0;
}
.ReactModal__Content {
transform: translateY(100%);
transition: transform 200ms cubic-bezier(0.13, 0.56, 0.25, 0.99);
}
.ReactModal__Content--after-open{
transform: translateY(0);
}
.ReactModal__Content--before-close{
transform: translateY(100%);
}
.raw-modal {
--small-modal-width: 525px;
--medium-modal-width: 712px;
--large-modal-width: 1024px;
width: 100%;
max-height: 100%;
border-radius: var(--bo-radius);
box-shadow: var(--bs-popup);
outline: none;
overflow: hidden;
&__small {
max-width: var(--small-modal-width);
}
&__medium {
max-width: var(--medium-modal-width);
}
&__large {
max-width: var(--large-modal-width);
}
&__overlay {
position: fixed;
top: 0;
left: 0;
z-index: 999;
display: flex;
justify-content: center;
align-items: center;
padding: var(--sp-normal);
width: 100%;
height: 100%;
background-color: var(--bg-overlay);
}
}

View file

@ -0,0 +1,37 @@
import React from 'react';
import PropTypes from 'prop-types';
import './ScrollView.scss';
const ScrollView = React.forwardRef(({
horizontal, vertical, autoHide, invisible, onScroll, children,
}, ref) => {
let scrollbarClasses = '';
if (horizontal) scrollbarClasses += ' scrollbar__h';
if (vertical) scrollbarClasses += ' scrollbar__v';
if (autoHide) scrollbarClasses += ' scrollbar--auto-hide';
if (invisible) scrollbarClasses += ' scrollbar--invisible';
return (
<div onScroll={onScroll} ref={ref} className={`scrollbar${scrollbarClasses}`}>
{children}
</div>
);
});
ScrollView.defaultProps = {
horizontal: false,
vertical: true,
autoHide: false,
invisible: false,
onScroll: null,
};
ScrollView.propTypes = {
horizontal: PropTypes.bool,
vertical: PropTypes.bool,
autoHide: PropTypes.bool,
invisible: PropTypes.bool,
onScroll: PropTypes.func,
children: PropTypes.node.isRequired,
};
export default ScrollView;

View file

@ -0,0 +1,22 @@
@use '_scrollbar';
.scrollbar {
width: 100%;
height: 100%;
@include scrollbar.scroll;
&__h {
@include scrollbar.scroll__h;
}
&__v {
@include scrollbar.scroll__v;
}
&--auto-hide {
@include scrollbar.scroll--auto-hide;
}
&--invisible {
@include scrollbar.scroll--invisible;
}
}

View file

@ -0,0 +1,62 @@
.firefox-scrollbar {
scrollbar-width: thin;
scrollbar-color: var(--bg-surface-hover) transparent;
&--transparent {
scrollbar-color: transparent transparent;
}
}
.webkit-scrollbar {
&::-webkit-scrollbar {
width: 8px;
height: 8px;
}
}
.webkit-scrollbar-track {
&::-webkit-scrollbar-track {
background-color: transparent;
}
}
.webkit-scrollbar-thumb {
&::-webkit-scrollbar-thumb {
background-color: var(--bg-surface-hover);
}
&::-webkit-scrollbar-thumb:hover {
background-color: var(--bg-surface-active);
}
&--transparent {
&::-webkit-scrollbar-thumb {
background-color: transparent;
}
}
}
@mixin scroll {
overflow: hidden;
@extend .firefox-scrollbar;
@extend .webkit-scrollbar;
@extend .webkit-scrollbar-track;
@extend .webkit-scrollbar-thumb;
}
@mixin scroll__h {
overflow-x: scroll;
}
@mixin scroll__v {
overflow-y: scroll;
}
@mixin scroll--auto-hide {
@extend .firefox-scrollbar--transparent;
@extend .webkit-scrollbar-thumb--transparent;
&:hover {
@extend .firefox-scrollbar;
@extend .webkit-scrollbar-thumb;
}
}
@mixin scroll--invisible {
-ms-overflow-style: none;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
}

View file

@ -0,0 +1,51 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import './SegmentedControls.scss';
import { blurOnBubbling } from '../button/script';
import Text from '../text/Text';
import RawIcon from '../system-icons/RawIcon';
function SegmentedControls({
selected, segments, onSelect,
}) {
const [select, setSelect] = useState(selected);
function selectSegment(segmentIndex) {
setSelect(segmentIndex);
onSelect(segmentIndex);
}
return (
<div className="segmented-controls">
{
segments.map((segment, index) => (
<button
key={Math.random().toString(20).substr(2, 6)}
className={`segment-btn${select === index ? ' segment-btn--active' : ''}`}
type="button"
onClick={() => selectSegment(index)}
onMouseUp={(e) => blurOnBubbling(e, '.segment-btn')}
>
<div className="segment-btn__base">
{segment.iconSrc && <RawIcon size="small" src={segment.iconSrc} />}
{segment.text && <Text variant="b2">{segment.text}</Text>}
</div>
</button>
))
}
</div>
);
}
SegmentedControls.propTypes = {
selected: PropTypes.number.isRequired,
segments: PropTypes.arrayOf(PropTypes.shape({
iconSrc: PropTypes.string,
text: PropTypes.string,
})).isRequired,
onSelect: PropTypes.func.isRequired,
};
export default SegmentedControls;

View file

@ -0,0 +1,61 @@
@use '../button/state';
.segmented-controls {
background-color: var(--bg-surface-low);
border-radius: var(--bo-radius);
border: 1px solid var(--bg-surface-border);
display: inline-flex;
overflow: hidden;
}
.segment-btn {
padding: var(--sp-extra-tight) 0;
cursor: pointer;
@include state.hover(var(--bg-surface-hover));
@include state.active(var(--bg-surface-active));
&__base {
padding: 0 var(--sp-normal);
display: flex;
align-items: center;
justify-content: center;
border-left: 1px solid var(--bg-surface-border);
[dir=rtl] & {
border-left: none;
border-right: 1px solid var(--bg-surface-border);
}
& .text:nth-child(2) {
margin: 0 var(--sp-extra-tight);
}
}
&:first-child &__base {
border: none;
}
&--active {
background-color: var(--bg-surface);
border: 1px solid var(--bg-surface-border);
border-width: 0 1px 0 1px;
& .segment-btn__base,
& + .segment-btn .segment-btn__base {
border: none;
}
&:first-child{
border-left: none;
}
&:last-child {
border-right: none;
}
[dir=rtl] & {
border-left: 1px solid var(--bg-surface-border);
border-right: 1px solid var(--bg-surface-border);
&:first-child { border-right: none;}
&:last-child { border-left: none;}
}
}
}

View file

@ -0,0 +1,19 @@
import React from 'react';
import PropTypes from 'prop-types';
import './Spinner.scss';
function Spinner({ size }) {
return (
<div className={`donut-spinner donut-spinner--${size}`}> </div>
);
}
Spinner.defaultProps = {
size: 'normal',
};
Spinner.propTypes = {
size: PropTypes.oneOf(['normal', 'small']),
};
export default Spinner;

View file

@ -0,0 +1,22 @@
.donut-spinner {
display: inline-block;
border: 4px solid var(--bg-surface-border);
border-left-color: var(--tc-surface-normal);
border-radius: 50%;
animation: donut-spin 1.2s cubic-bezier(0.73, 0.32, 0.67, 0.86) infinite;
&--normal {
width: 40px;
height: 40px;
}
&--small {
width: 28px;
height: 28px;
}
}
@keyframes donut-spin {
to {
transform: rotate(1turn);
}
}

View file

@ -0,0 +1,25 @@
import React from 'react';
import PropTypes from 'prop-types';
import './RawIcon.scss';
function RawIcon({ color, size, src }) {
const style = {
WebkitMaskImage: `url(${src})`,
maskImage: `url(${src})`,
};
if (color !== null) style.backgroundColor = color;
return <span className={`ic-raw ic-raw-${size}`} style={style}> </span>;
}
RawIcon.defaultProps = {
color: null,
size: 'normal',
};
RawIcon.propTypes = {
color: PropTypes.string,
size: PropTypes.oneOf(['large', 'normal', 'small', 'extra-small']),
src: PropTypes.string.isRequired,
};
export default RawIcon;

View file

@ -0,0 +1,25 @@
@mixin icSize($size) {
width: $size;
height: $size;
}
.ic-raw {
display: inline-block;
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
-webkit-mask-size: cover;
mask-size: cover;
background-color: var(--ic-surface-normal);
}
.ic-raw-large {
@include icSize(var(--ic-large));
}
.ic-raw-normal {
@include icSize(var(--ic-normal));
}
.ic-raw-small {
@include icSize(var(--ic-small));
}
.ic-raw-extra-small {
@include icSize(var(--ic-extra-small));
}

View file

@ -0,0 +1,28 @@
import React from 'react';
import PropTypes from 'prop-types';
import './Text.scss';
function Text({
id, className, variant, children,
}) {
const cName = className !== '' ? `${className} ` : '';
if (variant === 'h1') return <h1 id={id === '' ? undefined : id} className={`${cName}text text-h1`}>{ children }</h1>;
if (variant === 'h2') return <h2 id={id === '' ? undefined : id} className={`${cName}text text-h2`}>{ children }</h2>;
if (variant === 's1') return <h4 id={id === '' ? undefined : id} className={`${cName}text text-s1`}>{ children }</h4>;
return <p id={id === '' ? undefined : id} className={`${cName}text text-${variant}`}>{ children }</p>;
}
Text.defaultProps = {
id: '',
className: '',
variant: 'b1',
};
Text.propTypes = {
id: PropTypes.string,
className: PropTypes.string,
variant: PropTypes.oneOf(['h1', 'h2', 's1', 'b1', 'b2', 'b3']),
children: PropTypes.node.isRequired,
};
export default Text;

View file

@ -0,0 +1,41 @@
@mixin font($type, $weight) {
font-size: var(--fs-#{$type});
font-weight: $weight;
letter-spacing: var(--ls-#{$type});
line-height: var(--lh-#{$type});
}
%text {
margin: 0;
padding: 0;
color: var(--tc-surface-high);
}
.text-h1 {
@extend %text;
@include font(h1, 500);
}
.text-h2 {
@extend %text;
@include font(h2, 500);
}
.text-s1 {
@extend %text;
@include font(s1, 400);
}
.text-b1 {
@extend %text;
@include font(b1, 400);
color: var(--tc-surface-normal);
}
.text-b2 {
@extend %text;
@include font(b2, 400);
color: var(--tc-surface-normal);
}
.text-b3 {
@extend %text;
@include font(b3, 400);
color: var(--tc-surface-low);
}