import * as React from 'react' import * as PropTypes from 'prop-types' import styled from 'styled-components' import stringify from '../../services/stringify' import { Size, SizeMap } from '../../services/utilities' import Icon from '../Icon/Icon' const MIN_HEIGHTS: SizeMap = { small: '2.5rem', medium: '3rem', large: '4rem', } const LABEL_VERTICAL_PADDING_SIZES: SizeMap = { small: '0.125rem', medium: '0.25rem', large: '0.5rem', } const VERTICAL_PADDING_SIZES: SizeMap = { small: '0.6rem', medium: '0.85rem', large: '1.25rem', } const INPUT_FONT_SIZES: SizeMap = { small: '0.85em', medium: '0.85em', large: '1em', } const SECONDARY_TEXT_SIZES: SizeMap = { small: '0.65em', medium: '0.75em', large: '0.85em', } const ComponentBase = styled('div')({ 'position': 'relative', 'borderRadius': '0.25rem', 'fontFamily': 'var(--font-family-base)', 'maxWidth': '100%', ':focus-within': { '--color-accent': 'var(--color-active, Highlight)', }, }) ComponentBase.displayName = 'div' const CaptureArea = styled('label')({ display: 'block', }) CaptureArea.displayName = 'label' const LabelWrapper = styled('span')({ color: 'var(--color-accent, blue)', boxSizing: 'border-box', position: 'absolute', top: 0, left: 0, paddingLeft: '0.5rem', fontSize: '0.85em', maxWidth: '100%', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', fontWeight: 'bolder', zIndex: 2, pointerEvents: 'none', transitionProperty: 'color', lineHeight: 1, userSelect: 'none', }) LabelWrapper.displayName = 'span' const Input = styled('select')({ 'backgroundColor': 'var(--color-bg, white)', 'color': 'var(--color-fg, black)', 'appearance': 'none', 'boxSizing': 'border-box', 'position': 'relative', 'border': 0, 'paddingLeft': '1rem', 'margin': 0, 'font': 'inherit', 'minHeight': '4rem', 'minWidth': '16rem', 'maxWidth': '100%', 'zIndex': 1, 'cursor': 'pointer', 'transitionProperty': 'background-color, color', ':focus': { outline: 0, }, ':disabled': { cursor: 'not-allowed', }, '::-moz-focus-inner': { outline: 0, border: 0, }, }) Input.displayName = 'select' const Border = styled('span')({ 'borderColor': 'var(--color-accent, blue)', 'boxSizing': 'border-box', 'display': 'inline-block', 'borderWidth': '0.125rem', 'borderStyle': 'solid', 'position': 'absolute', 'top': 0, 'left': 0, 'width': '100%', 'height': '100%', 'borderRadius': 'inherit', 'zIndex': 2, 'pointerEvents': 'none', 'transitionProperty': 'border-color', '::before': { position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', content: "''", borderRadius: '0.125rem', opacity: 0.5, pointerEvents: 'none', }, [`${ComponentBase}:focus-within &::before`]: { boxShadow: '0 0 0 0.375rem var(--color-accent, blue)', }, }) const HintWrapper = styled('span')({ boxSizing: 'border-box', position: 'absolute', bottom: 0, left: 0, paddingLeft: '1rem', fontSize: '0.85em', opacity: 0.5, maxWidth: '100%', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', zIndex: 2, pointerEvents: 'none', lineHeight: 1, userSelect: 'none', }) const IndicatorWrapper = styled('span')({ color: 'var(--color-accent, blue)', boxSizing: 'border-box', position: 'absolute', top: 0, right: 0, height: '100%', display: 'grid', placeContent: 'center', padding: '0 1rem', zIndex: 1, pointerEvents: 'none', transitionProperty: 'color', lineHeight: 1, userSelect: 'none', }) const propTypes = { /** * Short textual description indicating the nature of the component's value. */ label: PropTypes.any, /** * Class name for the component, used for styling. */ className: PropTypes.string, /** * Short textual description as guidelines for valid input values. */ hint: PropTypes.any, /** * Size of the component. */ size: PropTypes.oneOf(['small', 'medium', 'large']), /** * Should the component take up the remaining space parallel to the content flow? */ block: PropTypes.bool, /** * Can multiple values be selected? */ multiple: PropTypes.bool, /** * Is the component active? */ disabled: PropTypes.bool, /** * CSS styles. */ style: PropTypes.object, } type Props = PropTypes.InferProps /** * Component for selecting values from a larger number of options. * @see {@link Checkbox} for a similar component on selecting values among very few choices. * @see {@link RadioButton} for a similar component on selecting a single value among very few choices. * @type {React.ComponentType<{readonly label?: string, readonly hint?: string, readonly className?: string, readonly * size?: 'small' | 'medium' | 'large', readonly multiple?: boolean, readonly block?: boolean} & * React.ClassAttributes>} */ const Select = React.forwardRef( ( { label = '', className = '', hint = '', size: sizeProp = 'medium', block = false, multiple = false, disabled = false, style = {}, ...etcProps }, ref, ) => { const size = sizeProp as Size return ( {stringify(label)} {stringify(label).length > 0 && ' '} {stringify(hint).length > 0 && ' '} {stringify(hint).length > 0 && ( ({stringify(hint)}) )} {!multiple && ( )} ) }, ) Select.propTypes = propTypes Select.displayName = 'Select' export default Select