imnyang/cinny
Watch
1
0
Fork
You've already forked cinny
0

initial commit

This commit is contained in:
unknown 2021-07-28 18:45:52 +05:30
commit 026f835a87
104 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)
}
}

Some files were not shown because too many files have changed in this diff Show more