Skip to content

Commit

Permalink
feat: add basic Select component
Browse files Browse the repository at this point in the history
  • Loading branch information
wewoor committed Dec 8, 2020
1 parent b9a3116 commit 027535d
Show file tree
Hide file tree
Showing 7 changed files with 435 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/components/select/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './select';
export * from './option';
49 changes: 49 additions & 0 deletions src/components/select/option.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import './style.scss';
import * as React from 'react';
import { ComponentProps } from 'react';
import { classNames, getBEMElement, getBEMModifier } from 'mo/common/className';

import { selectClassName } from './select';

export interface ISelectOption extends ComponentProps<'option'> {
value?: string;
title?: string;
description?: string;
disabled?: boolean;
}

const selectOptionClassName = getBEMElement(selectClassName, 'option');
const selectOptionDisabledClassName = getBEMModifier(
selectOptionClassName,
'disabled'
);

export function Option(props: ISelectOption) {
const {
className,
value,
title,
description,
disabled,
children,
...custom
} = props;

const claNames = classNames(
selectOptionClassName,
className,
disabled ? selectOptionDisabledClassName : ''
);
const content = children || title;
return (
<div
className={claNames}
title={content}
data-value={value}
data-desc={description}
{...(custom as any)}
>
{content}
</div>
);
}
167 changes: 167 additions & 0 deletions src/components/select/select.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import './style.scss';
import * as React from 'react';
import { useRef, useState, Children, isValidElement, useEffect } from 'react';
import {
prefixClaName,
classNames,
getBEMElement,
getBEMModifier,
} from 'mo/common/className';
import { cloneReactChildren } from 'mo/react';
import { useContextView } from 'mo/components/contextview';

import { ISelectOption } from './option';
import { Icon } from '../icon';

export interface ISelect {
value?: string;
title?: string;
style?: React.CSSProperties;
className?: string;
defaultValue?: string;
placeholder?: string;
prefix?: ReactNode;
showArrow?: boolean;
children?: ReactNode;
onSelect?(e: React.MouseEvent, selectedOption?: ISelectOption): void;
}

export const selectClassName = prefixClaName('select');
const containerClassName = getBEMElement(selectClassName, 'container');
const selectOptionsClassName = getBEMElement(selectClassName, 'options');
const selectDescriptorClassName = getBEMElement(selectClassName, 'descriptor');
const inputClassName = getBEMElement(selectClassName, 'input');
const selectActiveClassName = getBEMModifier(selectClassName, 'active');
const selectArrowClassName = getBEMElement(selectClassName, 'arrow');

export function Select(props: ISelect) {
const {
className,
children,
defaultValue = '',
placeholder,
value,
title,
onSelect,
...custom
} = props;

const contextView = useContextView({
shadowOutline: false,
});

const defaultSelectedOption: ISelectOption = {};
const options = Children.toArray(children);
for (const option of options) {
if (isValidElement(option)) {
const optionProps = option.props as ISelectOption;
if (optionProps.value && optionProps.value === defaultValue) {
defaultSelectedOption.title = optionProps.children as string;
defaultSelectedOption.value = optionProps.value;
break;
}
}
}

const selectElm = useRef<HTMLDivElement>(null);
const selectInput = useRef<HTMLInputElement>(null);
const [isOpen, setIsOpen] = useState(false);
const [inputValue, setInputValue] = useState(defaultSelectedOption);

const handleOnClickOption = (e: React.MouseEvent) => {
const option = e.target as HTMLDivElement;
const value = option.getAttribute('data-value');
const title = option.getAttribute('title');
const desc = option.getAttribute('data-desc');
const optionItem = {
value: value!,
title: title!,
description: desc!,
};

setInputValue(optionItem);
onSelect?.(e, optionItem);
setIsOpen(false);
contextView.hide();
};

const handOnHoverOption = (e: React.MouseEvent) => {
const option = e.target as HTMLDivElement;
const desc = option.getAttribute('data-desc');
const descriptor = contextView.view!.querySelector(
'.' + selectDescriptorClassName
);
if (descriptor) {
const content = desc || 'None';
descriptor.innerHTML = content;
descriptor.setAttribute('title', content);
}
};

const events = {
onClick: (e: React.MouseEvent) => {
const select = selectElm.current;
if (select) {
const selectRect = select?.getBoundingClientRect();
selectRect.y = selectRect.y + selectRect.height;
setIsOpen(true);

contextView.show(selectRect, () => {
return (
<div
style={{
width: selectRect.width,
}}
className={classNames(
containerClassName,
selectActiveClassName
)}
onMouseOver={handOnHoverOption}
>
<div className={selectOptionsClassName}>
{cloneReactChildren<ISelectOption>(children, {
onClick: handleOnClickOption,
})}
</div>
<div className={selectDescriptorClassName}>
None
</div>
</div>
);
});
}
},
};

const selectActive = isOpen ? selectActiveClassName : '';
const claNames = classNames(selectClassName, className, selectActive);

useEffect(() => {
contextView.onHide(() => {
setIsOpen(false);
});

return () => {
contextView.dispose();
};
}, [isOpen, inputValue]);

return (
<div ref={selectElm} className={claNames} {...(custom as any)}>
<input
{...events}
ref={selectInput}
autoComplete="off"
placeholder={placeholder}
className={inputClassName}
value={inputValue.title}
readOnly
>
{title}
</input>
<span className={selectArrowClassName}>
<Icon type={'chevron-down'} />
</span>
</div>
);
}
85 changes: 85 additions & 0 deletions src/components/select/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
@import 'mo/style/common';
$select: prefix('select');

#{$select} {
align-items: center;
border: 1px solid;
box-sizing: border-box;
display: flex;
font: inherit;
height: 26px;
justify-content: left;
position: relative;
width: initial;

&--active {
border: 1px solid rgba(14, 99, 156, 0.8);
}

&__input {
appearance: none;
background: inherit;
border: 0;
color: inherit;
cursor: default;
font: inherit;
font-size: inherit;
height: 100%;
margin: 0;
outline: 0;
padding: 0 8px;
width: 100%;

&:focus {
outline: none;
}
}

&__arrow {
bottom: 0;
font-size: 14px;
height: 14px;
line-height: 14px;
margin: auto;
pointer-events: none;
position: absolute;
right: 6px;
top: 0;
width: 14px;
}

&__container {
align-items: center;
appearance: none;
box-sizing: border-box;
display: flex;
flex-direction: column;
font-size: 13px;
}

&__options,
&__descriptor {
text-indent: 6px;
width: 100%;
}

&__descriptor {
border-top: 1px solid rgb(60, 60, 60);
height: 26px;
line-height: 26px;
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
}

&__option {
cursor: pointer;
height: 20px;
line-height: 20px;
width: 100%;

&--disabled {
cursor: not-allowed;
}
}
}
10 changes: 10 additions & 0 deletions src/style/common.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ $prefix: 'mo';
@return '.' + $prefix + '-' + $name;
}

// The Naming of BEM Element
@function bem-ele($block, $element) {
@return $block + '__' + $element;
}

// The Naming of BEM Modifier
@function bem-mod($blockOrElement, $modifier) {
@return $blockOrElement + '--' + $modifier;
}

.#{$prefix} {
bottom: 0;
font-size: 13px;
Expand Down
16 changes: 16 additions & 0 deletions src/style/theme.scss
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
@import 'mo/components/tabs/style';
@import 'mo/components/button/style';
@import 'mo/components/input/style';
@import 'mo/components/select/style';

// =============== Workbench =============== //
#{prefix($workbench)} {
Expand Down Expand Up @@ -224,5 +225,20 @@
background-color: #f5f5f5;
color: rgba(0, 0, 0, 0.25);
opacity: 1;

// =============== Select =============== //
#{$select} {
&__container {
background-color: rgb(60, 60, 60);
// border-color: rgb(60, 60, 60);
border-top: 0;
color: rgb(240, 240, 240);
}

&__option {
&:hover {
background-color: rgba(14, 99, 156, 0.8);
color: rgb(240, 240, 240);
}
}
}
Loading

0 comments on commit 027535d

Please sign in to comment.