diff --git a/components/avatar/__tests__/__snapshots__/index.test.tsx.snap b/components/avatar/__tests__/__snapshots__/index.test.tsx.snap index 9f221ee8a..0083f397b 100644 --- a/components/avatar/__tests__/__snapshots__/index.test.tsx.snap +++ b/components/avatar/__tests__/__snapshots__/index.test.tsx.snap @@ -34,10 +34,6 @@ initialize { margin: 0 0 0 0; } - .avatar:first-child { - margin: 0; - } - .avatar-img { display: inline-block; width: 100%; @@ -102,10 +98,6 @@ initialize { margin: 0 0 0 0; } - .avatar:first-child { - margin: 0; - } - .avatar-img { display: inline-block; width: 100%; @@ -231,10 +223,6 @@ initialize { margin: 0 0 0 0; } - .avatar:first-child { - margin: 0; - } - .avatar-img { display: inline-block; width: 100%; @@ -299,10 +287,6 @@ initialize { margin: 0 0 0 0; } - .avatar:first-child { - margin: 0; - } - .avatar-img { display: inline-block; width: 100%; @@ -433,10 +417,6 @@ initialize { margin: 0 0 0 0; } - .avatar:first-child { - margin: 0; - } - .avatar-img { display: inline-block; width: 100%; @@ -501,10 +481,6 @@ initialize { margin: 0 0 0 0; } - .avatar:first-child { - margin: 0; - } - .avatar-img { display: inline-block; width: 100%; @@ -591,10 +567,6 @@ initialize { margin: 0 0 0 0; } - .avatar:first-child { - margin: 0; - } - .avatar-img { display: inline-block; width: 100%; @@ -659,10 +631,6 @@ initialize { margin: 0 0 0 0; } - .avatar:first-child { - margin: 0; - } - .avatar-img { display: inline-block; width: 100%; @@ -810,10 +778,6 @@ initialize { margin: 0 0 0 0; } - .avatar:first-child { - margin: 0; - } - .avatar-img { display: inline-block; width: 100%; @@ -878,10 +842,6 @@ initialize { margin: 0 0 0 0; } - .avatar:first-child { - margin: 0; - } - .avatar-img { display: inline-block; width: 100%; @@ -1010,10 +970,6 @@ initialize { margin: 0 0 0 0; } - .avatar:first-child { - margin: 0; - } - .avatar-img { display: inline-block; width: 100%; @@ -1078,10 +1034,6 @@ initialize { margin: 0 0 0 0; } - .avatar:first-child { - margin: 0; - } - .avatar-img { display: inline-block; width: 100%; @@ -1223,10 +1175,6 @@ initialize { margin: 0 0 0 0; } - .avatar:first-child { - margin: 0; - } - .avatar-img { display: inline-block; width: 100%; @@ -1291,10 +1239,6 @@ initialize { margin: 0 0 0 0; } - .avatar:first-child { - margin: 0; - } - .avatar-img { display: inline-block; width: 100%; @@ -1383,10 +1327,6 @@ initialize { margin: 0 0 0 0; } - .avatar:first-child { - margin: 0; - } - .avatar-img { display: inline-block; width: 100%; @@ -1451,10 +1391,6 @@ initialize { margin: 0 0 0 0; } - .avatar:first-child { - margin: 0; - } - .avatar-img { display: inline-block; width: 100%; diff --git a/components/avatar/avatar.tsx b/components/avatar/avatar.tsx index 12f16e5b7..8fa756394 100644 --- a/components/avatar/avatar.tsx +++ b/components/avatar/avatar.tsx @@ -69,10 +69,6 @@ const AvatarComponent: React.FC = ({ margin: ${SCALES.mt(0)} ${SCALES.mr(0)} ${SCALES.mb(0)} ${marginLeft}; } - .avatar:first-child { - margin: 0; - } - .avatar-img { display: inline-block; width: 100%; diff --git a/components/modal/__tests__/index.test.tsx b/components/modal/__tests__/index.test.tsx index 6f56ba461..dae2b3269 100644 --- a/components/modal/__tests__/index.test.tsx +++ b/components/modal/__tests__/index.test.tsx @@ -1,20 +1,13 @@ import React from 'react' import { mount } from 'enzyme' -import { Modal } from 'components' +import { KeyCode, Modal } from 'components' import { nativeEvent, updateWrapper } from 'tests/utils' import { expectModalIsClosed, expectModalIsOpened } from './use-modal.test' -import { act } from 'react-dom/test-utils' - -const TabEvent = { - key: 'TAB', - keyCode: 9, - which: 9, -} describe('Modal', () => { it('should render correctly', () => { const wrapper = mount( - + Modal This is a modal @@ -29,19 +22,17 @@ describe('Modal', () => { }) it('should trigger event when modal changed', async () => { - const openHandler = jest.fn() const closeHandler = jest.fn() const wrapper = mount( - + Modal , ) expectModalIsClosed(wrapper) - wrapper.setProps({ open: true }) + wrapper.setProps({ visible: true }) await updateWrapper(wrapper, 350) expectModalIsOpened(wrapper) - expect(openHandler).toHaveBeenCalled() wrapper.find('.backdrop').simulate('click', nativeEvent) await updateWrapper(wrapper, 500) @@ -52,7 +43,7 @@ describe('Modal', () => { it('should disable backdrop event', async () => { const closeHandler = jest.fn() const wrapper = mount( - + Modal Submit , @@ -66,7 +57,7 @@ describe('Modal', () => { it('should disable backdrop even if actions missing', async () => { const closeHandler = jest.fn() const wrapper = mount( - + Modal , ) @@ -80,7 +71,7 @@ describe('Modal', () => { const actions1 = jest.fn() const actions2 = jest.fn() const wrapper = mount( - + Modal Submit @@ -100,7 +91,7 @@ describe('Modal', () => { it('should be close modal through action event', async () => { const closeHandler = jest.fn() const wrapper = mount( - + Modal e.close()}> Close @@ -115,7 +106,7 @@ describe('Modal', () => { it('customization should be supported', () => { const wrapper = mount( - + Modal , ) @@ -127,29 +118,46 @@ describe('Modal', () => { it('focus should only be switched within modal', () => { const wrapper = mount( - + Modal , ) const tabStart = wrapper.find('.hide-tab').at(0).getDOMNode() const tabEnd = wrapper.find('.hide-tab').at(1).getDOMNode() - const eventElement = wrapper.find('.wrapper').at(0) expect(document.activeElement).toBe(tabStart) - act(() => { - eventElement.simulate('keydown', { - ...TabEvent, - shiftKey: true, - }) + document.dispatchEvent(new KeyboardEvent('keydown', { keyCode: KeyCode.Tab })) + + expect(tabEnd.outerHTML).toEqual(document.activeElement?.outerHTML) + document.dispatchEvent(new KeyboardEvent('keydown', { keyCode: KeyCode.Tab })) + expect(tabStart.outerHTML).toEqual(document.activeElement?.outerHTML) + }) + + it('should close modal when keyboard event is triggered', async () => { + const wrapper = mount( + + Modal + , + ) + expectModalIsOpened(wrapper) + wrapper.simulate('keydown', { + keyCode: KeyCode.Escape, }) - expect(document.activeElement).toBe(tabEnd) + await updateWrapper(wrapper, 500) + expectModalIsClosed(wrapper) + }) - act(() => { - eventElement.simulate('keydown', { - ...TabEvent, - shiftKey: false, - }) + it('should prevent close modal when keyboard is false', async () => { + const wrapper = mount( + + Modal + , + ) + expectModalIsOpened(wrapper) + wrapper.simulate('keydown', { + keyCode: KeyCode.Escape, }) - expect(document.activeElement).toBe(tabStart) + await updateWrapper(wrapper, 500) + expectModalIsOpened(wrapper) }) }) diff --git a/components/modal/index.ts b/components/modal/index.ts index ad7761702..9ab720eb9 100644 --- a/components/modal/index.ts +++ b/components/modal/index.ts @@ -20,4 +20,5 @@ export type { ModalTitleProps } from './modal-title' export type { ModalSubtitleProps } from './modal-subtitle' export type { ModalActionProps } from './modal-action' export type { ModalContentProps } from './modal-content' +export type { ModalHooksBindings } from './use-modal' export default Modal as ModalComponentType diff --git a/components/modal/modal.tsx b/components/modal/modal.tsx index 5facc2d0d..568718c34 100644 --- a/components/modal/modal.tsx +++ b/components/modal/modal.tsx @@ -1,4 +1,4 @@ -import React, { MouseEvent, useEffect, useMemo } from 'react' +import React, { MouseEvent, useEffect, useMemo, useState } from 'react' import { createPortal } from 'react-dom' import usePortal from '../utils/use-portal' import ModalWrapper from './modal-wrapper' @@ -8,20 +8,21 @@ import Backdrop from '../shared/backdrop' import { ModalConfig, ModalContext } from './modal-context' import { pickChild } from '../utils/collections' import useBodyScroll from '../utils/use-body-scroll' -import useCurrentState from '../utils/use-current-state' import useScaleable, { withScaleable } from '../use-scaleable' +import useKeyboard, { KeyCode } from '../use-keyboard' interface Props { disableBackdropClick?: boolean onClose?: () => void - onOpen?: () => void onContentClick?: (event: MouseEvent) => void - open?: boolean + visible?: boolean + keyboard?: boolean wrapClassName?: string } const defaultProps = { wrapClassName: '', + keyboard: true, disableBackdropClick: false, } @@ -29,10 +30,10 @@ type NativeAttrs = Omit, keyof Props> export type ModalProps = Props & NativeAttrs const ModalComponent: React.FC> = ({ - open, - onOpen, + visible: customVisible, onClose, children, + keyboard, wrapClassName, onContentClick, disableBackdropClick, @@ -40,10 +41,9 @@ const ModalComponent: React.FC> = ({ const portal = usePortal('modal') const { SCALES } = useScaleable() const [, setBodyHidden] = useBodyScroll(null, { scrollLayer: true }) - const [visible, setVisible, visibleRef] = useCurrentState(false) + const [visible, setVisible] = useState(false) const [withoutActionsChildren, ActionsChildren] = pickChild(children, ModalAction) const hasActions = ActionsChildren && React.Children.count(ActionsChildren) > 0 - const closeModal = () => { onClose && onClose() setVisible(false) @@ -51,17 +51,20 @@ const ModalComponent: React.FC> = ({ } useEffect(() => { - if (open === undefined) return - if (open) { - onOpen && onOpen() - } - if (!open && visibleRef.current) { - onClose && onClose() - } + if (typeof customVisible === 'undefined') return + setVisible(customVisible) + setBodyHidden(customVisible) + }, [customVisible]) - setVisible(open) - setBodyHidden(open) - }, [open]) + const { bindings } = useKeyboard( + () => { + keyboard && closeModal() + }, + KeyCode.Escape, + { + disableGlobalEvent: true, + }, + ) const closeFromBackdrop = () => { if (disableBackdropClick) return @@ -82,7 +85,8 @@ const ModalComponent: React.FC> = ({ onClick={closeFromBackdrop} onContentClick={onContentClick} visible={visible} - width={SCALES.width(26)}> + width={SCALES.width(26)} + {...bindings}> {withoutActionsChildren} {hasActions && {ActionsChildren}} diff --git a/components/modal/use-modal.ts b/components/modal/use-modal.ts index 63f248ff5..d6513925c 100644 --- a/components/modal/use-modal.ts +++ b/components/modal/use-modal.ts @@ -1,5 +1,8 @@ import { Dispatch, MutableRefObject, SetStateAction } from 'react' import useCurrentState from '../utils/use-current-state' +import { ModalProps } from '../modal' + +export type ModalHooksBindings = Pick const useModal = ( initialVisible: boolean = false, @@ -7,10 +10,7 @@ const useModal = ( visible: boolean setVisible: Dispatch> currentRef: MutableRefObject - bindings: { - open: boolean - onClose: () => void - } + bindings: ModalHooksBindings } => { const [visible, setVisible, currentRef] = useCurrentState(initialVisible) @@ -19,7 +19,7 @@ const useModal = ( setVisible, currentRef, bindings: { - open: visible, + visible, onClose: () => setVisible(false), }, } diff --git a/components/select/__tests__/__snapshots__/index.test.tsx.snap b/components/select/__tests__/__snapshots__/index.test.tsx.snap index c0b3ae0fd..48f05b1f4 100644 --- a/components/select/__tests__/__snapshots__/index.test.tsx.snap +++ b/components/select/__tests__/__snapshots__/index.test.tsx.snap @@ -1,7 +1,20 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Select should render correctly 1`] = ` -"
" + .select { + display: inline-flex; + align-items: center; + user-select: none; + white-space: nowrap; + position: relative; + cursor: pointer; + max-width: 90vw; + overflow: hidden; + transition: border 150ms ease-in 0s, color 200ms ease-out 0s, + box-shadow 200ms ease 0s; + border: 1px solid #eaeaea; + border-radius: 5px; + + background-color: #fff; + --select-font-size: calc(1.3125 * 16px); + --select-height: calc(3.375 * 16px); + min-width: 11.5em; + width: initial; + height: var(--select-height); + padding: 0 calc(0.501 * 16px) 0 + calc(1.0005000000000002 * 16px); + margin: 0 0 0 0; + } + + .multiple { + height: auto; + min-height: var(--select-height); + padding: calc(0.501 * 16px) calc(0.501 * 16px) calc(0.501 * 16px) + calc(1.0005000000000002 * 16px); + } + + .select.active, + .select:hover { + border-color: #000; + } + + .select.active.icon, + .select:hover .icon { + color: #000; + } + + .value { + display: inline-flex; + flex: 1; + height: 100%; + align-items: center; + line-height: 1; + padding: 0; + margin-right: 1.25em; + font-size: var(--select-font-size); + color: #000; + width: calc(100% - 1.25em); + } + + .value > :global(div), + .value > :global(div:hover) { + border-radius: 0; + background-color: transparent; + padding: 0; + margin: 0; + color: inherit; + } + + .placeholder { + color: #999; + } + + .icon { + position: absolute; + right: 4pt; + font-size: var(--select-font-size); + top: 50%; + bottom: 0; + transform: translateY(-50%) rotate(0deg); + pointer-events: none; + transition: transform 200ms ease; + display: flex; + align-items: center; + color: #666; + } + " `; exports[`Select should work correctly with labels 1`] = ` -"
" + .select { + display: inline-flex; + align-items: center; + user-select: none; + white-space: nowrap; + position: relative; + cursor: pointer; + max-width: 90vw; + overflow: hidden; + transition: border 150ms ease-in 0s, color 200ms ease-out 0s, + box-shadow 200ms ease 0s; + border: 1px solid #eaeaea; + border-radius: 5px; + + background-color: #fff; + --select-font-size: calc(0.875 * 16px); + --select-height: calc(2.25 * 16px); + min-width: 11.5em; + width: initial; + height: var(--select-height); + padding: 0 calc(0.334 * 16px) 0 + calc(0.667 * 16px); + margin: 0 0 0 0; + } + + .multiple { + height: auto; + min-height: var(--select-height); + padding: calc(0.334 * 16px) calc(0.334 * 16px) calc(0.334 * 16px) + calc(0.667 * 16px); + } + + .select.active, + .select:hover { + border-color: #000; + } + + .select.active.icon, + .select:hover .icon { + color: #000; + } + + .value { + display: inline-flex; + flex: 1; + height: 100%; + align-items: center; + line-height: 1; + padding: 0; + margin-right: 1.25em; + font-size: var(--select-font-size); + color: #000; + width: calc(100% - 1.25em); + } + + .value > :global(div), + .value > :global(div:hover) { + border-radius: 0; + background-color: transparent; + padding: 0; + margin: 0; + color: inherit; + } + + .placeholder { + color: #999; + } + + .icon { + position: absolute; + right: 4pt; + font-size: var(--select-font-size); + top: 50%; + bottom: 0; + transform: translateY(-50%) rotate(0deg); + pointer-events: none; + transition: transform 200ms ease; + display: flex; + align-items: center; + color: #666; + } + " `; exports[`Select should work with different icons 1`] = ` initialize { "0": Node { "attribs": Object { - "class": "select ", + "class": "select ", }, "children": Array [ Node { "attribs": Object { - "class": "value placeholder", + "aria-expanded": "false", + "aria-haspopup": "listbox", + "readonly": "", + "role": "combobox", + "type": "search", + "unselectable": "on", }, - "children": Array [ - Node { - "attribs": Object {}, + "children": Array [], + "name": "input", + "namespace": "http://www.w3.org/1999/xhtml", + "next": Node { + "attribs": Object {}, + "children": Array [ + Node { + "data": " + input { + position: fixed; + top: -10000px; + left: -10000px; + opacity: 0; + z-index: -1; + width: 0; + height: 0; + padding: 0; + font-size: 0; + border: none; + } + ", + "next": null, + "parent": [Circular], + "prev": null, + "type": "text", + }, + ], + "name": "style", + "namespace": "http://www.w3.org/1999/xhtml", + "next": Node { + "attribs": Object { + "class": "value placeholder", + }, "children": Array [ Node { "attribs": Object {}, "children": Array [ Node { - "data": " + "attribs": Object {}, + "children": Array [ + Node { + "data": " span { overflow: hidden; text-overflow: ellipsis; @@ -324,128 +407,146 @@ initialize { min-width: 0; } ", + "next": null, + "parent": [Circular], + "prev": null, + "type": "text", + }, + ], + "name": "style", + "namespace": "http://www.w3.org/1999/xhtml", "next": null, "parent": [Circular], "prev": null, - "type": "text", + "type": "style", + "x-attribsNamespace": Object {}, + "x-attribsPrefix": Object {}, }, ], - "name": "style", + "name": "span", "namespace": "http://www.w3.org/1999/xhtml", "next": null, "parent": [Circular], "prev": null, - "type": "style", + "type": "tag", "x-attribsNamespace": Object {}, "x-attribsPrefix": Object {}, }, ], "name": "span", "namespace": "http://www.w3.org/1999/xhtml", - "next": null, - "parent": [Circular], - "prev": null, - "type": "tag", - "x-attribsNamespace": Object {}, - "x-attribsPrefix": Object {}, - }, - ], - "name": "span", - "namespace": "http://www.w3.org/1999/xhtml", - "next": Node { - "attribs": Object {}, - "children": Array [ - Node { - "data": " - .select { - display: inline-flex; - align-items: center; - user-select: none; - white-space: nowrap; - position: relative; - cursor: pointer; - max-width: 90vw; - overflow: hidden; - transition: border 150ms ease-in 0s, color 200ms ease-out 0s, - box-shadow 200ms ease 0s; - border: 1px solid #eaeaea; - border-radius: 5px; - - background-color: #fff; - --select-font-size: calc(0.875 * 16px); - --select-height: calc(2.25 * 16px); - min-width: 11.5em; - width: initial; - height: var(--select-height); - padding: 0 calc(0.334 * 16px) 0 - calc(0.667 * 16px); - margin: 0 0 0 0; - } - - .multiple { - height: auto; - min-height: var(--select-height); - padding: calc(0.334 * 16px) calc(0.334 * 16px) calc(0.334 * 16px) - calc(0.667 * 16px); - } - - .select:hover { - border-color: #000; - } - - .select:hover .icon { - color: #000; - } - - .value { - display: inline-flex; - flex: 1; - height: 100%; - align-items: center; - line-height: 1; - padding: 0; - margin-right: 1.25em; - font-size: var(--select-font-size); - color: #000; - width: calc(100% - 1.25em); - } - - .value > :global(div), - .value > :global(div:hover) { - border-radius: 0; - background-color: transparent; - padding: 0; - margin: 0; - color: inherit; - } - - .placeholder { - color: #999; - } - - .icon { - position: absolute; - right: 4pt; - font-size: var(--select-font-size); - top: 50%; - bottom: 0; - transform: translateY(-50%) rotate(0deg); - pointer-events: none; - transition: transform 200ms ease; - display: flex; - align-items: center; - color: #666; - } - ", + "next": Node { + "attribs": Object {}, + "children": Array [ + Node { + "data": " + .select { + display: inline-flex; + align-items: center; + user-select: none; + white-space: nowrap; + position: relative; + cursor: pointer; + max-width: 90vw; + overflow: hidden; + transition: border 150ms ease-in 0s, color 200ms ease-out 0s, + box-shadow 200ms ease 0s; + border: 1px solid #eaeaea; + border-radius: 5px; + + background-color: #fff; + --select-font-size: calc(0.875 * 16px); + --select-height: calc(2.25 * 16px); + min-width: 11.5em; + width: initial; + height: var(--select-height); + padding: 0 calc(0.334 * 16px) 0 + calc(0.667 * 16px); + margin: 0 0 0 0; + } + + .multiple { + height: auto; + min-height: var(--select-height); + padding: calc(0.334 * 16px) calc(0.334 * 16px) calc(0.334 * 16px) + calc(0.667 * 16px); + } + + .select.active, + .select:hover { + border-color: #000; + } + + .select.active.icon, + .select:hover .icon { + color: #000; + } + + .value { + display: inline-flex; + flex: 1; + height: 100%; + align-items: center; + line-height: 1; + padding: 0; + margin-right: 1.25em; + font-size: var(--select-font-size); + color: #000; + width: calc(100% - 1.25em); + } + + .value > :global(div), + .value > :global(div:hover) { + border-radius: 0; + background-color: transparent; + padding: 0; + margin: 0; + color: inherit; + } + + .placeholder { + color: #999; + } + + .icon { + position: absolute; + right: 4pt; + font-size: var(--select-font-size); + top: 50%; + bottom: 0; + transform: translateY(-50%) rotate(0deg); + pointer-events: none; + transition: transform 200ms ease; + display: flex; + align-items: center; + color: #666; + } + ", + "next": null, + "parent": [Circular], + "prev": null, + "type": "text", + }, + ], + "name": "style", + "namespace": "http://www.w3.org/1999/xhtml", "next": null, "parent": [Circular], - "prev": null, - "type": "text", + "prev": [Circular], + "type": "style", + "x-attribsNamespace": Object {}, + "x-attribsPrefix": Object {}, }, - ], - "name": "style", - "namespace": "http://www.w3.org/1999/xhtml", - "next": null, + "parent": [Circular], + "prev": [Circular], + "type": "tag", + "x-attribsNamespace": Object { + "class": undefined, + }, + "x-attribsPrefix": Object { + "class": undefined, + }, + }, "parent": [Circular], "prev": [Circular], "type": "style", @@ -456,10 +557,20 @@ initialize { "prev": null, "type": "tag", "x-attribsNamespace": Object { - "class": undefined, + "aria-expanded": undefined, + "aria-haspopup": undefined, + "readonly": undefined, + "role": undefined, + "type": undefined, + "unselectable": undefined, }, "x-attribsPrefix": Object { - "class": undefined, + "aria-expanded": undefined, + "aria-haspopup": undefined, + "readonly": undefined, + "role": undefined, + "type": undefined, + "unselectable": undefined, }, }, Node { @@ -467,84 +578,17 @@ initialize { "children": Array [ Node { "data": " - .select { - display: inline-flex; - align-items: center; - user-select: none; - white-space: nowrap; - position: relative; - cursor: pointer; - max-width: 90vw; - overflow: hidden; - transition: border 150ms ease-in 0s, color 200ms ease-out 0s, - box-shadow 200ms ease 0s; - border: 1px solid #eaeaea; - border-radius: 5px; - - background-color: #fff; - --select-font-size: calc(0.875 * 16px); - --select-height: calc(2.25 * 16px); - min-width: 11.5em; - width: initial; - height: var(--select-height); - padding: 0 calc(0.334 * 16px) 0 - calc(0.667 * 16px); - margin: 0 0 0 0; - } - - .multiple { - height: auto; - min-height: var(--select-height); - padding: calc(0.334 * 16px) calc(0.334 * 16px) calc(0.334 * 16px) - calc(0.667 * 16px); - } - - .select:hover { - border-color: #000; - } - - .select:hover .icon { - color: #000; - } - - .value { - display: inline-flex; - flex: 1; - height: 100%; - align-items: center; - line-height: 1; - padding: 0; - margin-right: 1.25em; - font-size: var(--select-font-size); - color: #000; - width: calc(100% - 1.25em); - } - - .value > :global(div), - .value > :global(div:hover) { - border-radius: 0; - background-color: transparent; + input { + position: fixed; + top: -10000px; + left: -10000px; + opacity: 0; + z-index: -1; + width: 0; + height: 0; padding: 0; - margin: 0; - color: inherit; - } - - .placeholder { - color: #999; - } - - .icon { - position: absolute; - right: 4pt; - font-size: var(--select-font-size); - top: 50%; - bottom: 0; - transform: translateY(-50%) rotate(0deg); - pointer-events: none; - transition: transform 200ms ease; - display: flex; - align-items: center; - color: #666; + font-size: 0; + border: none; } ", "next": null, @@ -555,9 +599,7 @@ initialize { ], "name": "style", "namespace": "http://www.w3.org/1999/xhtml", - "next": null, - "parent": [Circular], - "prev": Node { + "next": Node { "attribs": Object { "class": "value placeholder", }, @@ -606,60 +648,1171 @@ initialize { ], "name": "span", "namespace": "http://www.w3.org/1999/xhtml", + "next": Node { + "attribs": Object {}, + "children": Array [ + Node { + "data": " + .select { + display: inline-flex; + align-items: center; + user-select: none; + white-space: nowrap; + position: relative; + cursor: pointer; + max-width: 90vw; + overflow: hidden; + transition: border 150ms ease-in 0s, color 200ms ease-out 0s, + box-shadow 200ms ease 0s; + border: 1px solid #eaeaea; + border-radius: 5px; + + background-color: #fff; + --select-font-size: calc(0.875 * 16px); + --select-height: calc(2.25 * 16px); + min-width: 11.5em; + width: initial; + height: var(--select-height); + padding: 0 calc(0.334 * 16px) 0 + calc(0.667 * 16px); + margin: 0 0 0 0; + } + + .multiple { + height: auto; + min-height: var(--select-height); + padding: calc(0.334 * 16px) calc(0.334 * 16px) calc(0.334 * 16px) + calc(0.667 * 16px); + } + + .select.active, + .select:hover { + border-color: #000; + } + + .select.active.icon, + .select:hover .icon { + color: #000; + } + + .value { + display: inline-flex; + flex: 1; + height: 100%; + align-items: center; + line-height: 1; + padding: 0; + margin-right: 1.25em; + font-size: var(--select-font-size); + color: #000; + width: calc(100% - 1.25em); + } + + .value > :global(div), + .value > :global(div:hover) { + border-radius: 0; + background-color: transparent; + padding: 0; + margin: 0; + color: inherit; + } + + .placeholder { + color: #999; + } + + .icon { + position: absolute; + right: 4pt; + font-size: var(--select-font-size); + top: 50%; + bottom: 0; + transform: translateY(-50%) rotate(0deg); + pointer-events: none; + transition: transform 200ms ease; + display: flex; + align-items: center; + color: #666; + } + ", + "next": null, + "parent": [Circular], + "prev": null, + "type": "text", + }, + ], + "name": "style", + "namespace": "http://www.w3.org/1999/xhtml", + "next": null, + "parent": [Circular], + "prev": [Circular], + "type": "style", + "x-attribsNamespace": Object {}, + "x-attribsPrefix": Object {}, + }, + "parent": [Circular], + "prev": [Circular], + "type": "tag", + "x-attribsNamespace": Object { + "class": undefined, + }, + "x-attribsPrefix": Object { + "class": undefined, + }, + }, + "parent": [Circular], + "prev": Node { + "attribs": Object { + "aria-expanded": "false", + "aria-haspopup": "listbox", + "readonly": "", + "role": "combobox", + "type": "search", + "unselectable": "on", + }, + "children": Array [], + "name": "input", + "namespace": "http://www.w3.org/1999/xhtml", + "next": [Circular], + "parent": [Circular], + "prev": null, + "type": "tag", + "x-attribsNamespace": Object { + "aria-expanded": undefined, + "aria-haspopup": undefined, + "readonly": undefined, + "role": undefined, + "type": undefined, + "unselectable": undefined, + }, + "x-attribsPrefix": Object { + "aria-expanded": undefined, + "aria-haspopup": undefined, + "readonly": undefined, + "role": undefined, + "type": undefined, + "unselectable": undefined, + }, + }, + "type": "style", + "x-attribsNamespace": Object {}, + "x-attribsPrefix": Object {}, + }, + Node { + "attribs": Object { + "class": "value placeholder", + }, + "children": Array [ + Node { + "attribs": Object {}, + "children": Array [ + Node { + "attribs": Object {}, + "children": Array [ + Node { + "data": " + span { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + line-height: var(--select-height); + min-width: 0; + } + ", + "next": null, + "parent": [Circular], + "prev": null, + "type": "text", + }, + ], + "name": "style", + "namespace": "http://www.w3.org/1999/xhtml", + "next": null, + "parent": [Circular], + "prev": null, + "type": "style", + "x-attribsNamespace": Object {}, + "x-attribsPrefix": Object {}, + }, + ], + "name": "span", + "namespace": "http://www.w3.org/1999/xhtml", + "next": null, + "parent": [Circular], + "prev": null, + "type": "tag", + "x-attribsNamespace": Object {}, + "x-attribsPrefix": Object {}, + }, + ], + "name": "span", + "namespace": "http://www.w3.org/1999/xhtml", + "next": Node { + "attribs": Object {}, + "children": Array [ + Node { + "data": " + .select { + display: inline-flex; + align-items: center; + user-select: none; + white-space: nowrap; + position: relative; + cursor: pointer; + max-width: 90vw; + overflow: hidden; + transition: border 150ms ease-in 0s, color 200ms ease-out 0s, + box-shadow 200ms ease 0s; + border: 1px solid #eaeaea; + border-radius: 5px; + + background-color: #fff; + --select-font-size: calc(0.875 * 16px); + --select-height: calc(2.25 * 16px); + min-width: 11.5em; + width: initial; + height: var(--select-height); + padding: 0 calc(0.334 * 16px) 0 + calc(0.667 * 16px); + margin: 0 0 0 0; + } + + .multiple { + height: auto; + min-height: var(--select-height); + padding: calc(0.334 * 16px) calc(0.334 * 16px) calc(0.334 * 16px) + calc(0.667 * 16px); + } + + .select.active, + .select:hover { + border-color: #000; + } + + .select.active.icon, + .select:hover .icon { + color: #000; + } + + .value { + display: inline-flex; + flex: 1; + height: 100%; + align-items: center; + line-height: 1; + padding: 0; + margin-right: 1.25em; + font-size: var(--select-font-size); + color: #000; + width: calc(100% - 1.25em); + } + + .value > :global(div), + .value > :global(div:hover) { + border-radius: 0; + background-color: transparent; + padding: 0; + margin: 0; + color: inherit; + } + + .placeholder { + color: #999; + } + + .icon { + position: absolute; + right: 4pt; + font-size: var(--select-font-size); + top: 50%; + bottom: 0; + transform: translateY(-50%) rotate(0deg); + pointer-events: none; + transition: transform 200ms ease; + display: flex; + align-items: center; + color: #666; + } + ", + "next": null, + "parent": [Circular], + "prev": null, + "type": "text", + }, + ], + "name": "style", + "namespace": "http://www.w3.org/1999/xhtml", + "next": null, + "parent": [Circular], + "prev": [Circular], + "type": "style", + "x-attribsNamespace": Object {}, + "x-attribsPrefix": Object {}, + }, + "parent": [Circular], + "prev": Node { + "attribs": Object {}, + "children": Array [ + Node { + "data": " + input { + position: fixed; + top: -10000px; + left: -10000px; + opacity: 0; + z-index: -1; + width: 0; + height: 0; + padding: 0; + font-size: 0; + border: none; + } + ", + "next": null, + "parent": [Circular], + "prev": null, + "type": "text", + }, + ], + "name": "style", + "namespace": "http://www.w3.org/1999/xhtml", + "next": [Circular], + "parent": [Circular], + "prev": Node { + "attribs": Object { + "aria-expanded": "false", + "aria-haspopup": "listbox", + "readonly": "", + "role": "combobox", + "type": "search", + "unselectable": "on", + }, + "children": Array [], + "name": "input", + "namespace": "http://www.w3.org/1999/xhtml", + "next": [Circular], + "parent": [Circular], + "prev": null, + "type": "tag", + "x-attribsNamespace": Object { + "aria-expanded": undefined, + "aria-haspopup": undefined, + "readonly": undefined, + "role": undefined, + "type": undefined, + "unselectable": undefined, + }, + "x-attribsPrefix": Object { + "aria-expanded": undefined, + "aria-haspopup": undefined, + "readonly": undefined, + "role": undefined, + "type": undefined, + "unselectable": undefined, + }, + }, + "type": "style", + "x-attribsNamespace": Object {}, + "x-attribsPrefix": Object {}, + }, + "type": "tag", + "x-attribsNamespace": Object { + "class": undefined, + }, + "x-attribsPrefix": Object { + "class": undefined, + }, + }, + Node { + "attribs": Object {}, + "children": Array [ + Node { + "data": " + .select { + display: inline-flex; + align-items: center; + user-select: none; + white-space: nowrap; + position: relative; + cursor: pointer; + max-width: 90vw; + overflow: hidden; + transition: border 150ms ease-in 0s, color 200ms ease-out 0s, + box-shadow 200ms ease 0s; + border: 1px solid #eaeaea; + border-radius: 5px; + + background-color: #fff; + --select-font-size: calc(0.875 * 16px); + --select-height: calc(2.25 * 16px); + min-width: 11.5em; + width: initial; + height: var(--select-height); + padding: 0 calc(0.334 * 16px) 0 + calc(0.667 * 16px); + margin: 0 0 0 0; + } + + .multiple { + height: auto; + min-height: var(--select-height); + padding: calc(0.334 * 16px) calc(0.334 * 16px) calc(0.334 * 16px) + calc(0.667 * 16px); + } + + .select.active, + .select:hover { + border-color: #000; + } + + .select.active.icon, + .select:hover .icon { + color: #000; + } + + .value { + display: inline-flex; + flex: 1; + height: 100%; + align-items: center; + line-height: 1; + padding: 0; + margin-right: 1.25em; + font-size: var(--select-font-size); + color: #000; + width: calc(100% - 1.25em); + } + + .value > :global(div), + .value > :global(div:hover) { + border-radius: 0; + background-color: transparent; + padding: 0; + margin: 0; + color: inherit; + } + + .placeholder { + color: #999; + } + + .icon { + position: absolute; + right: 4pt; + font-size: var(--select-font-size); + top: 50%; + bottom: 0; + transform: translateY(-50%) rotate(0deg); + pointer-events: none; + transition: transform 200ms ease; + display: flex; + align-items: center; + color: #666; + } + ", + "next": null, + "parent": [Circular], + "prev": null, + "type": "text", + }, + ], + "name": "style", + "namespace": "http://www.w3.org/1999/xhtml", + "next": null, + "parent": [Circular], + "prev": Node { + "attribs": Object { + "class": "value placeholder", + }, + "children": Array [ + Node { + "attribs": Object {}, + "children": Array [ + Node { + "attribs": Object {}, + "children": Array [ + Node { + "data": " + span { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + line-height: var(--select-height); + min-width: 0; + } + ", + "next": null, + "parent": [Circular], + "prev": null, + "type": "text", + }, + ], + "name": "style", + "namespace": "http://www.w3.org/1999/xhtml", + "next": null, + "parent": [Circular], + "prev": null, + "type": "style", + "x-attribsNamespace": Object {}, + "x-attribsPrefix": Object {}, + }, + ], + "name": "span", + "namespace": "http://www.w3.org/1999/xhtml", + "next": null, + "parent": [Circular], + "prev": null, + "type": "tag", + "x-attribsNamespace": Object {}, + "x-attribsPrefix": Object {}, + }, + ], + "name": "span", + "namespace": "http://www.w3.org/1999/xhtml", + "next": [Circular], + "parent": [Circular], + "prev": Node { + "attribs": Object {}, + "children": Array [ + Node { + "data": " + input { + position: fixed; + top: -10000px; + left: -10000px; + opacity: 0; + z-index: -1; + width: 0; + height: 0; + padding: 0; + font-size: 0; + border: none; + } + ", + "next": null, + "parent": [Circular], + "prev": null, + "type": "text", + }, + ], + "name": "style", + "namespace": "http://www.w3.org/1999/xhtml", + "next": [Circular], + "parent": [Circular], + "prev": Node { + "attribs": Object { + "aria-expanded": "false", + "aria-haspopup": "listbox", + "readonly": "", + "role": "combobox", + "type": "search", + "unselectable": "on", + }, + "children": Array [], + "name": "input", + "namespace": "http://www.w3.org/1999/xhtml", + "next": [Circular], + "parent": [Circular], + "prev": null, + "type": "tag", + "x-attribsNamespace": Object { + "aria-expanded": undefined, + "aria-haspopup": undefined, + "readonly": undefined, + "role": undefined, + "type": undefined, + "unselectable": undefined, + }, + "x-attribsPrefix": Object { + "aria-expanded": undefined, + "aria-haspopup": undefined, + "readonly": undefined, + "role": undefined, + "type": undefined, + "unselectable": undefined, + }, + }, + "type": "style", + "x-attribsNamespace": Object {}, + "x-attribsPrefix": Object {}, + }, + "type": "tag", + "x-attribsNamespace": Object { + "class": undefined, + }, + "x-attribsPrefix": Object { + "class": undefined, + }, + }, + "type": "style", + "x-attribsNamespace": Object {}, + "x-attribsPrefix": Object {}, + }, + ], + "name": "div", + "namespace": "http://www.w3.org/1999/xhtml", + "next": null, + "parent": Node { + "children": Array [ + [Circular], + ], + "name": "root", + "next": null, + "parent": null, + "prev": null, + "type": "root", + }, + "prev": null, + "type": "tag", + "x-attribsNamespace": Object { + "class": undefined, + }, + "x-attribsPrefix": Object { + "class": undefined, + }, + }, + "_root": [Circular], + "length": 1, + "options": Object { + "decodeEntities": true, + "xml": false, + }, +} +`; + +exports[`Select should work with different icons 2`] = ` +initialize { + "0": Node { + "attribs": Object { + "class": "select ", + }, + "children": Array [ + Node { + "attribs": Object { + "aria-expanded": "false", + "aria-haspopup": "listbox", + "readonly": "", + "role": "combobox", + "type": "search", + "unselectable": "on", + }, + "children": Array [], + "name": "input", + "namespace": "http://www.w3.org/1999/xhtml", + "next": Node { + "attribs": Object {}, + "children": Array [ + Node { + "data": " + input { + position: fixed; + top: -10000px; + left: -10000px; + opacity: 0; + z-index: -1; + width: 0; + height: 0; + padding: 0; + font-size: 0; + border: none; + } + ", + "next": null, + "parent": [Circular], + "prev": null, + "type": "text", + }, + ], + "name": "style", + "namespace": "http://www.w3.org/1999/xhtml", + "next": Node { + "attribs": Object { + "class": "value placeholder", + }, + "children": Array [ + Node { + "attribs": Object {}, + "children": Array [ + Node { + "attribs": Object {}, + "children": Array [ + Node { + "data": " + span { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + line-height: var(--select-height); + min-width: 0; + } + ", + "next": null, + "parent": [Circular], + "prev": null, + "type": "text", + }, + ], + "name": "style", + "namespace": "http://www.w3.org/1999/xhtml", + "next": null, + "parent": [Circular], + "prev": null, + "type": "style", + "x-attribsNamespace": Object {}, + "x-attribsPrefix": Object {}, + }, + ], + "name": "span", + "namespace": "http://www.w3.org/1999/xhtml", + "next": null, + "parent": [Circular], + "prev": null, + "type": "tag", + "x-attribsNamespace": Object {}, + "x-attribsPrefix": Object {}, + }, + ], + "name": "span", + "namespace": "http://www.w3.org/1999/xhtml", + "next": Node { + "attribs": Object { + "class": "icon", + }, + "children": Array [ + Node { + "attribs": Object {}, + "children": Array [ + Node { + "data": "icon", + "next": null, + "parent": [Circular], + "prev": null, + "type": "text", + }, + ], + "name": "span", + "namespace": "http://www.w3.org/1999/xhtml", + "next": null, + "parent": [Circular], + "prev": null, + "type": "tag", + "x-attribsNamespace": Object {}, + "x-attribsPrefix": Object {}, + }, + ], + "name": "div", + "namespace": "http://www.w3.org/1999/xhtml", + "next": Node { + "attribs": Object {}, + "children": Array [ + Node { + "data": " + .select { + display: inline-flex; + align-items: center; + user-select: none; + white-space: nowrap; + position: relative; + cursor: pointer; + max-width: 90vw; + overflow: hidden; + transition: border 150ms ease-in 0s, color 200ms ease-out 0s, + box-shadow 200ms ease 0s; + border: 1px solid #eaeaea; + border-radius: 5px; + + background-color: #fff; + --select-font-size: calc(0.875 * 16px); + --select-height: calc(2.25 * 16px); + min-width: 11.5em; + width: initial; + height: var(--select-height); + padding: 0 calc(0.334 * 16px) 0 + calc(0.667 * 16px); + margin: 0 0 0 0; + } + + .multiple { + height: auto; + min-height: var(--select-height); + padding: calc(0.334 * 16px) calc(0.334 * 16px) calc(0.334 * 16px) + calc(0.667 * 16px); + } + + .select.active, + .select:hover { + border-color: #000; + } + + .select.active.icon, + .select:hover .icon { + color: #000; + } + + .value { + display: inline-flex; + flex: 1; + height: 100%; + align-items: center; + line-height: 1; + padding: 0; + margin-right: 1.25em; + font-size: var(--select-font-size); + color: #000; + width: calc(100% - 1.25em); + } + + .value > :global(div), + .value > :global(div:hover) { + border-radius: 0; + background-color: transparent; + padding: 0; + margin: 0; + color: inherit; + } + + .placeholder { + color: #999; + } + + .icon { + position: absolute; + right: 4pt; + font-size: var(--select-font-size); + top: 50%; + bottom: 0; + transform: translateY(-50%) rotate(0deg); + pointer-events: none; + transition: transform 200ms ease; + display: flex; + align-items: center; + color: #666; + } + ", + "next": null, + "parent": [Circular], + "prev": null, + "type": "text", + }, + ], + "name": "style", + "namespace": "http://www.w3.org/1999/xhtml", + "next": null, + "parent": [Circular], + "prev": [Circular], + "type": "style", + "x-attribsNamespace": Object {}, + "x-attribsPrefix": Object {}, + }, + "parent": [Circular], + "prev": [Circular], + "type": "tag", + "x-attribsNamespace": Object { + "class": undefined, + }, + "x-attribsPrefix": Object { + "class": undefined, + }, + }, + "parent": [Circular], + "prev": [Circular], + "type": "tag", + "x-attribsNamespace": Object { + "class": undefined, + }, + "x-attribsPrefix": Object { + "class": undefined, + }, + }, + "parent": [Circular], + "prev": [Circular], + "type": "style", + "x-attribsNamespace": Object {}, + "x-attribsPrefix": Object {}, + }, + "parent": [Circular], + "prev": null, + "type": "tag", + "x-attribsNamespace": Object { + "aria-expanded": undefined, + "aria-haspopup": undefined, + "readonly": undefined, + "role": undefined, + "type": undefined, + "unselectable": undefined, + }, + "x-attribsPrefix": Object { + "aria-expanded": undefined, + "aria-haspopup": undefined, + "readonly": undefined, + "role": undefined, + "type": undefined, + "unselectable": undefined, + }, + }, + Node { + "attribs": Object {}, + "children": Array [ + Node { + "data": " + input { + position: fixed; + top: -10000px; + left: -10000px; + opacity: 0; + z-index: -1; + width: 0; + height: 0; + padding: 0; + font-size: 0; + border: none; + } + ", + "next": null, + "parent": [Circular], + "prev": null, + "type": "text", + }, + ], + "name": "style", + "namespace": "http://www.w3.org/1999/xhtml", + "next": Node { + "attribs": Object { + "class": "value placeholder", + }, + "children": Array [ + Node { + "attribs": Object {}, + "children": Array [ + Node { + "attribs": Object {}, + "children": Array [ + Node { + "data": " + span { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + line-height: var(--select-height); + min-width: 0; + } + ", + "next": null, + "parent": [Circular], + "prev": null, + "type": "text", + }, + ], + "name": "style", + "namespace": "http://www.w3.org/1999/xhtml", + "next": null, + "parent": [Circular], + "prev": null, + "type": "style", + "x-attribsNamespace": Object {}, + "x-attribsPrefix": Object {}, + }, + ], + "name": "span", + "namespace": "http://www.w3.org/1999/xhtml", + "next": null, + "parent": [Circular], + "prev": null, + "type": "tag", + "x-attribsNamespace": Object {}, + "x-attribsPrefix": Object {}, + }, + ], + "name": "span", + "namespace": "http://www.w3.org/1999/xhtml", + "next": Node { + "attribs": Object { + "class": "icon", + }, + "children": Array [ + Node { + "attribs": Object {}, + "children": Array [ + Node { + "data": "icon", + "next": null, + "parent": [Circular], + "prev": null, + "type": "text", + }, + ], + "name": "span", + "namespace": "http://www.w3.org/1999/xhtml", + "next": null, + "parent": [Circular], + "prev": null, + "type": "tag", + "x-attribsNamespace": Object {}, + "x-attribsPrefix": Object {}, + }, + ], + "name": "div", + "namespace": "http://www.w3.org/1999/xhtml", + "next": Node { + "attribs": Object {}, + "children": Array [ + Node { + "data": " + .select { + display: inline-flex; + align-items: center; + user-select: none; + white-space: nowrap; + position: relative; + cursor: pointer; + max-width: 90vw; + overflow: hidden; + transition: border 150ms ease-in 0s, color 200ms ease-out 0s, + box-shadow 200ms ease 0s; + border: 1px solid #eaeaea; + border-radius: 5px; + + background-color: #fff; + --select-font-size: calc(0.875 * 16px); + --select-height: calc(2.25 * 16px); + min-width: 11.5em; + width: initial; + height: var(--select-height); + padding: 0 calc(0.334 * 16px) 0 + calc(0.667 * 16px); + margin: 0 0 0 0; + } + + .multiple { + height: auto; + min-height: var(--select-height); + padding: calc(0.334 * 16px) calc(0.334 * 16px) calc(0.334 * 16px) + calc(0.667 * 16px); + } + + .select.active, + .select:hover { + border-color: #000; + } + + .select.active.icon, + .select:hover .icon { + color: #000; + } + + .value { + display: inline-flex; + flex: 1; + height: 100%; + align-items: center; + line-height: 1; + padding: 0; + margin-right: 1.25em; + font-size: var(--select-font-size); + color: #000; + width: calc(100% - 1.25em); + } + + .value > :global(div), + .value > :global(div:hover) { + border-radius: 0; + background-color: transparent; + padding: 0; + margin: 0; + color: inherit; + } + + .placeholder { + color: #999; + } + + .icon { + position: absolute; + right: 4pt; + font-size: var(--select-font-size); + top: 50%; + bottom: 0; + transform: translateY(-50%) rotate(0deg); + pointer-events: none; + transition: transform 200ms ease; + display: flex; + align-items: center; + color: #666; + } + ", + "next": null, + "parent": [Circular], + "prev": null, + "type": "text", + }, + ], + "name": "style", + "namespace": "http://www.w3.org/1999/xhtml", + "next": null, + "parent": [Circular], + "prev": [Circular], + "type": "style", + "x-attribsNamespace": Object {}, + "x-attribsPrefix": Object {}, + }, + "parent": [Circular], + "prev": [Circular], + "type": "tag", + "x-attribsNamespace": Object { + "class": undefined, + }, + "x-attribsPrefix": Object { + "class": undefined, + }, + }, + "parent": [Circular], + "prev": [Circular], + "type": "tag", + "x-attribsNamespace": Object { + "class": undefined, + }, + "x-attribsPrefix": Object { + "class": undefined, + }, + }, + "parent": [Circular], + "prev": Node { + "attribs": Object { + "aria-expanded": "false", + "aria-haspopup": "listbox", + "readonly": "", + "role": "combobox", + "type": "search", + "unselectable": "on", + }, + "children": Array [], + "name": "input", + "namespace": "http://www.w3.org/1999/xhtml", "next": [Circular], "parent": [Circular], "prev": null, "type": "tag", "x-attribsNamespace": Object { - "class": undefined, + "aria-expanded": undefined, + "aria-haspopup": undefined, + "readonly": undefined, + "role": undefined, + "type": undefined, + "unselectable": undefined, }, "x-attribsPrefix": Object { - "class": undefined, + "aria-expanded": undefined, + "aria-haspopup": undefined, + "readonly": undefined, + "role": undefined, + "type": undefined, + "unselectable": undefined, }, }, "type": "style", "x-attribsNamespace": Object {}, "x-attribsPrefix": Object {}, }, - ], - "name": "div", - "namespace": "http://www.w3.org/1999/xhtml", - "next": null, - "parent": Node { - "children": Array [ - [Circular], - ], - "name": "root", - "next": null, - "parent": null, - "prev": null, - "type": "root", - }, - "prev": null, - "type": "tag", - "x-attribsNamespace": Object { - "class": undefined, - }, - "x-attribsPrefix": Object { - "class": undefined, - }, - }, - "_root": [Circular], - "length": 1, - "options": Object { - "decodeEntities": true, - "xml": false, - }, -} -`; - -exports[`Select should work with different icons 2`] = ` -initialize { - "0": Node { - "attribs": Object { - "class": "select ", - }, - "children": Array [ Node { "attribs": Object { "class": "value placeholder", @@ -742,86 +1895,88 @@ initialize { "children": Array [ Node { "data": " - .select { - display: inline-flex; - align-items: center; - user-select: none; - white-space: nowrap; - position: relative; - cursor: pointer; - max-width: 90vw; - overflow: hidden; - transition: border 150ms ease-in 0s, color 200ms ease-out 0s, - box-shadow 200ms ease 0s; - border: 1px solid #eaeaea; - border-radius: 5px; - - background-color: #fff; - --select-font-size: calc(0.875 * 16px); - --select-height: calc(2.25 * 16px); - min-width: 11.5em; - width: initial; - height: var(--select-height); - padding: 0 calc(0.334 * 16px) 0 - calc(0.667 * 16px); - margin: 0 0 0 0; - } - - .multiple { - height: auto; - min-height: var(--select-height); - padding: calc(0.334 * 16px) calc(0.334 * 16px) calc(0.334 * 16px) - calc(0.667 * 16px); - } - - .select:hover { - border-color: #000; - } - - .select:hover .icon { - color: #000; - } - - .value { - display: inline-flex; - flex: 1; - height: 100%; - align-items: center; - line-height: 1; - padding: 0; - margin-right: 1.25em; - font-size: var(--select-font-size); - color: #000; - width: calc(100% - 1.25em); - } - - .value > :global(div), - .value > :global(div:hover) { - border-radius: 0; - background-color: transparent; - padding: 0; - margin: 0; - color: inherit; - } - - .placeholder { - color: #999; - } - - .icon { - position: absolute; - right: 4pt; - font-size: var(--select-font-size); - top: 50%; - bottom: 0; - transform: translateY(-50%) rotate(0deg); - pointer-events: none; - transition: transform 200ms ease; - display: flex; - align-items: center; - color: #666; - } - ", + .select { + display: inline-flex; + align-items: center; + user-select: none; + white-space: nowrap; + position: relative; + cursor: pointer; + max-width: 90vw; + overflow: hidden; + transition: border 150ms ease-in 0s, color 200ms ease-out 0s, + box-shadow 200ms ease 0s; + border: 1px solid #eaeaea; + border-radius: 5px; + + background-color: #fff; + --select-font-size: calc(0.875 * 16px); + --select-height: calc(2.25 * 16px); + min-width: 11.5em; + width: initial; + height: var(--select-height); + padding: 0 calc(0.334 * 16px) 0 + calc(0.667 * 16px); + margin: 0 0 0 0; + } + + .multiple { + height: auto; + min-height: var(--select-height); + padding: calc(0.334 * 16px) calc(0.334 * 16px) calc(0.334 * 16px) + calc(0.667 * 16px); + } + + .select.active, + .select:hover { + border-color: #000; + } + + .select.active.icon, + .select:hover .icon { + color: #000; + } + + .value { + display: inline-flex; + flex: 1; + height: 100%; + align-items: center; + line-height: 1; + padding: 0; + margin-right: 1.25em; + font-size: var(--select-font-size); + color: #000; + width: calc(100% - 1.25em); + } + + .value > :global(div), + .value > :global(div:hover) { + border-radius: 0; + background-color: transparent; + padding: 0; + margin: 0; + color: inherit; + } + + .placeholder { + color: #999; + } + + .icon { + position: absolute; + right: 4pt; + font-size: var(--select-font-size); + top: 50%; + bottom: 0; + transform: translateY(-50%) rotate(0deg); + pointer-events: none; + transition: transform 200ms ease; + display: flex; + align-items: center; + color: #666; + } + ", "next": null, "parent": [Circular], "prev": null, @@ -848,7 +2003,71 @@ initialize { }, }, "parent": [Circular], - "prev": null, + "prev": Node { + "attribs": Object {}, + "children": Array [ + Node { + "data": " + input { + position: fixed; + top: -10000px; + left: -10000px; + opacity: 0; + z-index: -1; + width: 0; + height: 0; + padding: 0; + font-size: 0; + border: none; + } + ", + "next": null, + "parent": [Circular], + "prev": null, + "type": "text", + }, + ], + "name": "style", + "namespace": "http://www.w3.org/1999/xhtml", + "next": [Circular], + "parent": [Circular], + "prev": Node { + "attribs": Object { + "aria-expanded": "false", + "aria-haspopup": "listbox", + "readonly": "", + "role": "combobox", + "type": "search", + "unselectable": "on", + }, + "children": Array [], + "name": "input", + "namespace": "http://www.w3.org/1999/xhtml", + "next": [Circular], + "parent": [Circular], + "prev": null, + "type": "tag", + "x-attribsNamespace": Object { + "aria-expanded": undefined, + "aria-haspopup": undefined, + "readonly": undefined, + "role": undefined, + "type": undefined, + "unselectable": undefined, + }, + "x-attribsPrefix": Object { + "aria-expanded": undefined, + "aria-haspopup": undefined, + "readonly": undefined, + "role": undefined, + "type": undefined, + "unselectable": undefined, + }, + }, + "type": "style", + "x-attribsNamespace": Object {}, + "x-attribsPrefix": Object {}, + }, "type": "tag", "x-attribsNamespace": Object { "class": undefined, @@ -890,86 +2109,88 @@ initialize { "children": Array [ Node { "data": " - .select { - display: inline-flex; - align-items: center; - user-select: none; - white-space: nowrap; - position: relative; - cursor: pointer; - max-width: 90vw; - overflow: hidden; - transition: border 150ms ease-in 0s, color 200ms ease-out 0s, - box-shadow 200ms ease 0s; - border: 1px solid #eaeaea; - border-radius: 5px; - - background-color: #fff; - --select-font-size: calc(0.875 * 16px); - --select-height: calc(2.25 * 16px); - min-width: 11.5em; - width: initial; - height: var(--select-height); - padding: 0 calc(0.334 * 16px) 0 - calc(0.667 * 16px); - margin: 0 0 0 0; - } - - .multiple { - height: auto; - min-height: var(--select-height); - padding: calc(0.334 * 16px) calc(0.334 * 16px) calc(0.334 * 16px) - calc(0.667 * 16px); - } - - .select:hover { - border-color: #000; - } - - .select:hover .icon { - color: #000; - } - - .value { - display: inline-flex; - flex: 1; - height: 100%; - align-items: center; - line-height: 1; - padding: 0; - margin-right: 1.25em; - font-size: var(--select-font-size); - color: #000; - width: calc(100% - 1.25em); - } - - .value > :global(div), - .value > :global(div:hover) { - border-radius: 0; - background-color: transparent; - padding: 0; - margin: 0; - color: inherit; - } - - .placeholder { - color: #999; - } - - .icon { - position: absolute; - right: 4pt; - font-size: var(--select-font-size); - top: 50%; - bottom: 0; - transform: translateY(-50%) rotate(0deg); - pointer-events: none; - transition: transform 200ms ease; - display: flex; - align-items: center; - color: #666; - } - ", + .select { + display: inline-flex; + align-items: center; + user-select: none; + white-space: nowrap; + position: relative; + cursor: pointer; + max-width: 90vw; + overflow: hidden; + transition: border 150ms ease-in 0s, color 200ms ease-out 0s, + box-shadow 200ms ease 0s; + border: 1px solid #eaeaea; + border-radius: 5px; + + background-color: #fff; + --select-font-size: calc(0.875 * 16px); + --select-height: calc(2.25 * 16px); + min-width: 11.5em; + width: initial; + height: var(--select-height); + padding: 0 calc(0.334 * 16px) 0 + calc(0.667 * 16px); + margin: 0 0 0 0; + } + + .multiple { + height: auto; + min-height: var(--select-height); + padding: calc(0.334 * 16px) calc(0.334 * 16px) calc(0.334 * 16px) + calc(0.667 * 16px); + } + + .select.active, + .select:hover { + border-color: #000; + } + + .select.active.icon, + .select:hover .icon { + color: #000; + } + + .value { + display: inline-flex; + flex: 1; + height: 100%; + align-items: center; + line-height: 1; + padding: 0; + margin-right: 1.25em; + font-size: var(--select-font-size); + color: #000; + width: calc(100% - 1.25em); + } + + .value > :global(div), + .value > :global(div:hover) { + border-radius: 0; + background-color: transparent; + padding: 0; + margin: 0; + color: inherit; + } + + .placeholder { + color: #999; + } + + .icon { + position: absolute; + right: 4pt; + font-size: var(--select-font-size); + top: 50%; + bottom: 0; + transform: translateY(-50%) rotate(0deg); + pointer-events: none; + transition: transform 200ms ease; + display: flex; + align-items: center; + color: #666; + } + ", "next": null, "parent": [Circular], "prev": null, @@ -1037,7 +2258,71 @@ initialize { "namespace": "http://www.w3.org/1999/xhtml", "next": [Circular], "parent": [Circular], - "prev": null, + "prev": Node { + "attribs": Object {}, + "children": Array [ + Node { + "data": " + input { + position: fixed; + top: -10000px; + left: -10000px; + opacity: 0; + z-index: -1; + width: 0; + height: 0; + padding: 0; + font-size: 0; + border: none; + } + ", + "next": null, + "parent": [Circular], + "prev": null, + "type": "text", + }, + ], + "name": "style", + "namespace": "http://www.w3.org/1999/xhtml", + "next": [Circular], + "parent": [Circular], + "prev": Node { + "attribs": Object { + "aria-expanded": "false", + "aria-haspopup": "listbox", + "readonly": "", + "role": "combobox", + "type": "search", + "unselectable": "on", + }, + "children": Array [], + "name": "input", + "namespace": "http://www.w3.org/1999/xhtml", + "next": [Circular], + "parent": [Circular], + "prev": null, + "type": "tag", + "x-attribsNamespace": Object { + "aria-expanded": undefined, + "aria-haspopup": undefined, + "readonly": undefined, + "role": undefined, + "type": undefined, + "unselectable": undefined, + }, + "x-attribsPrefix": Object { + "aria-expanded": undefined, + "aria-haspopup": undefined, + "readonly": undefined, + "role": undefined, + "type": undefined, + "unselectable": undefined, + }, + }, + "type": "style", + "x-attribsNamespace": Object {}, + "x-attribsPrefix": Object {}, + }, "type": "tag", "x-attribsNamespace": Object { "class": undefined, @@ -1059,86 +2344,88 @@ initialize { "children": Array [ Node { "data": " - .select { - display: inline-flex; - align-items: center; - user-select: none; - white-space: nowrap; - position: relative; - cursor: pointer; - max-width: 90vw; - overflow: hidden; - transition: border 150ms ease-in 0s, color 200ms ease-out 0s, - box-shadow 200ms ease 0s; - border: 1px solid #eaeaea; - border-radius: 5px; - - background-color: #fff; - --select-font-size: calc(0.875 * 16px); - --select-height: calc(2.25 * 16px); - min-width: 11.5em; - width: initial; - height: var(--select-height); - padding: 0 calc(0.334 * 16px) 0 - calc(0.667 * 16px); - margin: 0 0 0 0; - } - - .multiple { - height: auto; - min-height: var(--select-height); - padding: calc(0.334 * 16px) calc(0.334 * 16px) calc(0.334 * 16px) - calc(0.667 * 16px); - } - - .select:hover { - border-color: #000; - } - - .select:hover .icon { - color: #000; - } - - .value { - display: inline-flex; - flex: 1; - height: 100%; - align-items: center; - line-height: 1; - padding: 0; - margin-right: 1.25em; - font-size: var(--select-font-size); - color: #000; - width: calc(100% - 1.25em); - } - - .value > :global(div), - .value > :global(div:hover) { - border-radius: 0; - background-color: transparent; - padding: 0; - margin: 0; - color: inherit; - } - - .placeholder { - color: #999; - } - - .icon { - position: absolute; - right: 4pt; - font-size: var(--select-font-size); - top: 50%; - bottom: 0; - transform: translateY(-50%) rotate(0deg); - pointer-events: none; - transition: transform 200ms ease; - display: flex; - align-items: center; - color: #666; - } - ", + .select { + display: inline-flex; + align-items: center; + user-select: none; + white-space: nowrap; + position: relative; + cursor: pointer; + max-width: 90vw; + overflow: hidden; + transition: border 150ms ease-in 0s, color 200ms ease-out 0s, + box-shadow 200ms ease 0s; + border: 1px solid #eaeaea; + border-radius: 5px; + + background-color: #fff; + --select-font-size: calc(0.875 * 16px); + --select-height: calc(2.25 * 16px); + min-width: 11.5em; + width: initial; + height: var(--select-height); + padding: 0 calc(0.334 * 16px) 0 + calc(0.667 * 16px); + margin: 0 0 0 0; + } + + .multiple { + height: auto; + min-height: var(--select-height); + padding: calc(0.334 * 16px) calc(0.334 * 16px) calc(0.334 * 16px) + calc(0.667 * 16px); + } + + .select.active, + .select:hover { + border-color: #000; + } + + .select.active.icon, + .select:hover .icon { + color: #000; + } + + .value { + display: inline-flex; + flex: 1; + height: 100%; + align-items: center; + line-height: 1; + padding: 0; + margin-right: 1.25em; + font-size: var(--select-font-size); + color: #000; + width: calc(100% - 1.25em); + } + + .value > :global(div), + .value > :global(div:hover) { + border-radius: 0; + background-color: transparent; + padding: 0; + margin: 0; + color: inherit; + } + + .placeholder { + color: #999; + } + + .icon { + position: absolute; + right: 4pt; + font-size: var(--select-font-size); + top: 50%; + bottom: 0; + transform: translateY(-50%) rotate(0deg); + pointer-events: none; + transition: transform 200ms ease; + display: flex; + align-items: center; + color: #666; + } + ", "next": null, "parent": [Circular], "prev": null, @@ -1230,7 +2517,71 @@ initialize { "namespace": "http://www.w3.org/1999/xhtml", "next": [Circular], "parent": [Circular], - "prev": null, + "prev": Node { + "attribs": Object {}, + "children": Array [ + Node { + "data": " + input { + position: fixed; + top: -10000px; + left: -10000px; + opacity: 0; + z-index: -1; + width: 0; + height: 0; + padding: 0; + font-size: 0; + border: none; + } + ", + "next": null, + "parent": [Circular], + "prev": null, + "type": "text", + }, + ], + "name": "style", + "namespace": "http://www.w3.org/1999/xhtml", + "next": [Circular], + "parent": [Circular], + "prev": Node { + "attribs": Object { + "aria-expanded": "false", + "aria-haspopup": "listbox", + "readonly": "", + "role": "combobox", + "type": "search", + "unselectable": "on", + }, + "children": Array [], + "name": "input", + "namespace": "http://www.w3.org/1999/xhtml", + "next": [Circular], + "parent": [Circular], + "prev": null, + "type": "tag", + "x-attribsNamespace": Object { + "aria-expanded": undefined, + "aria-haspopup": undefined, + "readonly": undefined, + "role": undefined, + "type": undefined, + "unselectable": undefined, + }, + "x-attribsPrefix": Object { + "aria-expanded": undefined, + "aria-haspopup": undefined, + "readonly": undefined, + "role": undefined, + "type": undefined, + "unselectable": undefined, + }, + }, + "type": "style", + "x-attribsNamespace": Object {}, + "x-attribsPrefix": Object {}, + }, "type": "tag", "x-attribsNamespace": Object { "class": undefined, @@ -1284,7 +2635,20 @@ initialize { `; exports[`Select should work with different status 1`] = ` -"
" + .select { + display: inline-flex; + align-items: center; + user-select: none; + white-space: nowrap; + position: relative; + cursor: pointer; + max-width: 90vw; + overflow: hidden; + transition: border 150ms ease-in 0s, color 200ms ease-out 0s, + box-shadow 200ms ease 0s; + border: 1px solid #ff1a1a; + border-radius: 5px; + + background-color: #fff; + --select-font-size: calc(0.875 * 16px); + --select-height: calc(2.25 * 16px); + min-width: 11.5em; + width: initial; + height: var(--select-height); + padding: 0 calc(0.334 * 16px) 0 + calc(0.667 * 16px); + margin: 0 0 0 0; + } + + .multiple { + height: auto; + min-height: var(--select-height); + padding: calc(0.334 * 16px) calc(0.334 * 16px) calc(0.334 * 16px) + calc(0.667 * 16px); + } + + .select.active, + .select:hover { + border-color: #c50000; + } + + .select.active.icon, + .select:hover .icon { + color: #c50000; + } + + .value { + display: inline-flex; + flex: 1; + height: 100%; + align-items: center; + line-height: 1; + padding: 0; + margin-right: 1.25em; + font-size: var(--select-font-size); + color: #000; + width: calc(100% - 1.25em); + } + + .value > :global(div), + .value > :global(div:hover) { + border-radius: 0; + background-color: transparent; + padding: 0; + margin: 0; + color: inherit; + } + + .placeholder { + color: #e00; + } + + .icon { + position: absolute; + right: 4pt; + font-size: var(--select-font-size); + top: 50%; + bottom: 0; + transform: translateY(-50%) rotate(0deg); + pointer-events: none; + transition: transform 200ms ease; + display: flex; + align-items: center; + color: #e00; + } + " `; diff --git a/components/select/__tests__/__snapshots__/multiple.test.tsx.snap b/components/select/__tests__/__snapshots__/multiple.test.tsx.snap index 2c7224af2..ab7335949 100644 --- a/components/select/__tests__/__snapshots__/multiple.test.tsx.snap +++ b/components/select/__tests__/__snapshots__/multiple.test.tsx.snap @@ -1,7 +1,20 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Select Multiple should render correctly 1`] = ` -"
" + .select { + display: inline-flex; + align-items: center; + user-select: none; + white-space: nowrap; + position: relative; + cursor: pointer; + max-width: 90vw; + overflow: hidden; + transition: border 150ms ease-in 0s, color 200ms ease-out 0s, + box-shadow 200ms ease 0s; + border: 1px solid #eaeaea; + border-radius: 5px; + + background-color: #fff; + --select-font-size: calc(0.875 * 16px); + --select-height: calc(2.25 * 16px); + min-width: 11.5em; + width: initial; + height: var(--select-height); + padding: 0 calc(0.334 * 16px) 0 + calc(0.667 * 16px); + margin: 0 0 0 0; + } + + .multiple { + height: auto; + min-height: var(--select-height); + padding: calc(0.334 * 16px) calc(0.334 * 16px) calc(0.334 * 16px) + calc(0.667 * 16px); + } + + .select.active, + .select:hover { + border-color: #000; + } + + .select.active.icon, + .select:hover .icon { + color: #000; + } + + .value { + display: inline-flex; + flex: 1; + height: 100%; + align-items: center; + line-height: 1; + padding: 0; + margin-right: 1.25em; + font-size: var(--select-font-size); + color: #000; + width: calc(100% - 1.25em); + } + + .value > :global(div), + .value > :global(div:hover) { + border-radius: 0; + background-color: transparent; + padding: 0; + margin: 0; + color: inherit; + } + + .placeholder { + color: #999; + } + + .icon { + position: absolute; + right: 4pt; + font-size: var(--select-font-size); + top: 50%; + bottom: 0; + transform: translateY(-50%) rotate(0deg); + pointer-events: none; + transition: transform 200ms ease; + display: flex; + align-items: center; + color: #666; + } + " `; diff --git a/components/select/__tests__/events.test.tsx b/components/select/__tests__/events.test.tsx new file mode 100644 index 000000000..ff40e2f00 --- /dev/null +++ b/components/select/__tests__/events.test.tsx @@ -0,0 +1,66 @@ +import React from 'react' +import { mount } from 'enzyme' +import { Select } from 'components' +import { nativeEvent, updateWrapper } from 'tests/utils' + +describe('Select Events', () => { + let container: HTMLDivElement + + beforeEach(() => { + container = document.createElement('div') + document.body.append(container) + }) + + it('ref should be able to control the focus correctly', () => { + const ref = React.createRef() + const wrapper = mount(, { + attachTo: container, + }) + expect(visible).toBe(false) + expect(handler).not.toBeCalled() + wrapper.find('.select').simulate('click', nativeEvent) + await updateWrapper(wrapper, 300) + expect(visible).toBe(true) + expect(handler).toBeCalledTimes(1) + + wrapper.find('.dropdown').simulate('click', nativeEvent) + await updateWrapper(wrapper, 300) + expect(visible).toBe(true) + expect(handler).toBeCalledTimes(1) + }) + + it('scrollTo should be work correctly', async () => { + const ref = React.createRef() + const handler = jest.fn() + window.HTMLElement.prototype.scrollTo = jest.fn().mockImplementation(handler) + const wrapper = mount( + , + { attachTo: container }, + ) + wrapper.find('.select').simulate('click', nativeEvent) + await updateWrapper(wrapper, 300) + ref.current?.scrollTo({ top: 200 }) + expect(handler).toBeCalled() + expect(() => wrapper.unmount()).not.toThrow() + }) + + afterEach(() => { + document.body.removeChild(container!) + }) +}) diff --git a/components/select/__tests__/index.test.tsx b/components/select/__tests__/index.test.tsx index 2c666c6ae..f6d505ce7 100644 --- a/components/select/__tests__/index.test.tsx +++ b/components/select/__tests__/index.test.tsx @@ -32,6 +32,9 @@ describe('Select', () => { , @@ -108,6 +111,25 @@ describe('Select', () => { changeHandler.mockRestore() }) + it('should ignore option when prevent all', async () => { + let value = '' + const changeHandler = jest.fn().mockImplementation(val => (value = val)) + const wrapper = mount( + , + ) + wrapper.find('.select').simulate('click', nativeEvent) + wrapper.find('.select-dropdown').find('.option').at(1).simulate('click', nativeEvent) + await updateWrapper(wrapper, 350) + expect(changeHandler).not.toHaveBeenCalled() + expect(value).not.toEqual('2') + changeHandler.mockRestore() + }) + it('should be change when value changed', async () => { const wrapper = mount( + + + ) + }, +) + +SelectInput.displayName = 'GiestSelectInput' +export default SelectInput diff --git a/components/select/select-option.tsx b/components/select/select-option.tsx index 2547de2bf..09fd86536 100644 --- a/components/select/select-option.tsx +++ b/components/select/select-option.tsx @@ -77,16 +77,13 @@ const SelectOptionComponent: React.FC } return ( - <> -
- {children} -
- +
+ {children} - +
) } diff --git a/components/select/select.tsx b/components/select/select.tsx index 35a5e29af..33133b3a2 100644 --- a/components/select/select.tsx +++ b/components/select/select.tsx @@ -1,7 +1,13 @@ -import React, { CSSProperties, useEffect, useMemo, useRef, useState } from 'react' +import React, { + CSSProperties, + useEffect, + useImperativeHandle, + useMemo, + useRef, + useState, +} from 'react' import { NormalTypes } from '../utils/prop-types' import useTheme from '../use-theme' -import useClickAway from '../utils/use-click-away' import useCurrentState from '../utils/use-current-state' import { pickChildByProps } from '../utils/collections' import SelectIcon from './select-icon' @@ -11,8 +17,14 @@ import Grid from '../grid' import { SelectContext, SelectConfig } from './select-context' import { getColors } from './styles' import Ellipsis from '../shared/ellipsis' +import SelectInput from './select-input' import useScaleable, { withScaleable } from '../use-scaleable' +export type SelectRef = { + focus: () => void + blur: () => void + scrollTo?: (options?: ScrollToOptions) => void +} export type SelectTypes = NormalTypes interface Props { disabled?: boolean @@ -29,6 +41,7 @@ interface Props { dropdownClassName?: string dropdownStyle?: CSSProperties disableMatchWidth?: boolean + onDropdownVisibleChange?: (visible: boolean) => void getPopupContainer?: () => HTMLElement | null } @@ -41,220 +54,268 @@ const defaultProps = { clearable: true, className: '', disableMatchWidth: false, + onDropdownVisibleChange: () => {}, } type NativeAttrs = Omit, keyof Props> export type SelectProps = Props & NativeAttrs -const SelectComponent: React.FC> = ({ - children, - type, - disabled, - initialValue: init, - value: customValue, - icon: Icon, - onChange, - pure, - multiple, - clearable, - placeholder, - className, - dropdownClassName, - dropdownStyle, - disableMatchWidth, - getPopupContainer, - ...props -}: React.PropsWithChildren & typeof defaultProps) => { - const theme = useTheme() - const { SCALES } = useScaleable() - const ref = useRef(null) - const [visible, setVisible] = useState(false) - const [value, setValue, valueRef] = useCurrentState( - () => { - if (!multiple) return init - if (Array.isArray(init)) return init - return typeof init === 'undefined' ? [] : [init] - }, - ) - const isEmpty = useMemo(() => { - if (!Array.isArray(value)) return !value - return value.length === 0 - }, [value]) +const SelectComponent = React.forwardRef>( + ( + { + children, + type, + disabled, + initialValue: init, + value: customValue, + icon: Icon, + onChange, + pure, + multiple, + clearable, + placeholder, + className, + dropdownClassName, + dropdownStyle, + disableMatchWidth, + getPopupContainer, + onDropdownVisibleChange, + ...props + }: React.PropsWithChildren & typeof defaultProps, + selectRef, + ) => { + const theme = useTheme() + const { SCALES } = useScaleable() + const ref = useRef(null) + const inputRef = useRef(null) + const dropdownRef = useRef(null) + const [visible, setVisible] = useState(false) + const [selectFocus, setSelectFocus] = useState(false) + const [value, setValue, valueRef] = useCurrentState( + () => { + if (!multiple) return init + if (Array.isArray(init)) return init + return typeof init === 'undefined' ? [] : [init] + }, + ) + const isEmpty = useMemo(() => { + if (!Array.isArray(value)) return !value + return value.length === 0 + }, [value]) - const { border, borderHover, iconBorder, placeholderColor } = useMemo( - () => getColors(theme.palette, type), - [theme.palette, type], - ) + const { border, borderActive, iconBorder, placeholderColor } = useMemo( + () => getColors(theme.palette, type), + [theme.palette, type], + ) - const updateVisible = (next: boolean) => setVisible(next) - const updateValue = (next: string) => { - setValue(last => { - if (!Array.isArray(last)) return next - if (!last.includes(next)) return [...last, next] - return last.filter(item => item !== next) - }) - onChange && onChange(valueRef.current as string | string[]) - if (!multiple) { - setVisible(false) + const updateVisible = (next: boolean) => { + onDropdownVisibleChange(next) + setVisible(next) + } + const updateValue = (next: string) => { + setValue(last => { + if (!Array.isArray(last)) return next + if (!last.includes(next)) return [...last, next] + return last.filter(item => item !== next) + }) + onChange && onChange(valueRef.current as string | string[]) + if (!multiple) { + updateVisible(false) + } } - } - const initialValue: SelectConfig = useMemo( - () => ({ - value, - visible, - updateValue, - updateVisible, - ref, - disableAll: disabled, - }), - [visible, disabled, ref, value, multiple], - ) + const initialValue: SelectConfig = useMemo( + () => ({ + value, + visible, + updateValue, + updateVisible, + ref, + disableAll: disabled, + }), + [visible, disabled, ref, value, multiple], + ) + + const clickHandler = (event: React.MouseEvent) => { + event.stopPropagation() + event.nativeEvent.stopImmediatePropagation() + event.preventDefault() + if (disabled) return + + updateVisible(!visible) + event.preventDefault() + } + const mouseDownHandler = (event: React.MouseEvent) => { + /* istanbul ignore next */ + if (visible) { + event.preventDefault() + } + } - const clickHandler = (event: React.MouseEvent) => { - event.stopPropagation() - event.nativeEvent.stopImmediatePropagation() - event.preventDefault() - if (disabled) return - setVisible(!visible) - } + useEffect(() => { + if (customValue === undefined) return + setValue(customValue) + }, [customValue]) + useImperativeHandle( + selectRef, + () => ({ + focus: () => inputRef.current?.focus(), + blur: () => inputRef.current?.blur(), + scrollTo: options => dropdownRef.current?.scrollTo(options), + }), + [inputRef, dropdownRef], + ) - useClickAway(ref, () => setVisible(false)) - useEffect(() => { - if (customValue === undefined) return - setValue(customValue) - }, [customValue]) + const selectedChild = useMemo(() => { + const [, optionChildren] = pickChildByProps(children, 'value', value) + return React.Children.map(optionChildren, child => { + if (!React.isValidElement(child)) return null + const el = React.cloneElement(child, { preventAllEvents: true }) + if (!multiple) return el + return ( + updateValue(child.props.value) : null}> + {el} + + ) + }) + }, [value, children, multiple]) - const selectedChild = useMemo(() => { - const [, optionChildren] = pickChildByProps(children, 'value', value) - return React.Children.map(optionChildren, child => { - if (!React.isValidElement(child)) return null - const el = React.cloneElement(child, { preventAllEvents: true }) - if (!multiple) return el - return ( - updateValue(child.props.value) : null}> - {el} - - ) - }) - }, [value, children, multiple]) + const onInputBlur = () => { + updateVisible(false) + setSelectFocus(false) + } - return ( - -
- {isEmpty && ( - - {placeholder} - - )} - {value && !multiple && {selectedChild}} - {value && multiple && {selectedChild}} - - {children} - - {!pure && ( -
- -
- )} - -
-
- ) -} + .icon { + position: absolute; + right: ${theme.layout.gapQuarter}; + font-size: var(--select-font-size); + top: 50%; + bottom: 0; + transform: translateY(-50%) rotate(${visible ? '180' : '0'}deg); + pointer-events: none; + transition: transform 200ms ease; + display: flex; + align-items: center; + color: ${iconBorder}; + } + `} + + + ) + }, +) SelectComponent.defaultProps = defaultProps SelectComponent.displayName = 'GeistSelect' diff --git a/components/select/styles.ts b/components/select/styles.ts index 607f20965..eb2cf0b24 100644 --- a/components/select/styles.ts +++ b/components/select/styles.ts @@ -3,7 +3,7 @@ import { GeistUIThemesPalette } from '../themes/presets' export type SelectColor = { border: string - borderHover: string + borderActive: string iconBorder: string placeholderColor: string } @@ -15,34 +15,32 @@ export const getColors = ( const colors: { [key in NormalTypes]: SelectColor } = { default: { border: palette.border, - borderHover: palette.foreground, + borderActive: palette.foreground, iconBorder: palette.accents_5, placeholderColor: palette.accents_3, }, secondary: { border: palette.border, - borderHover: palette.foreground, + borderActive: palette.foreground, iconBorder: palette.accents_5, placeholderColor: palette.accents_3, }, success: { - border: palette.success, - borderHover: palette.successDark, + border: palette.successLight, + borderActive: palette.successDark, iconBorder: palette.success, placeholderColor: palette.accents_3, }, warning: { - border: palette.warning, - borderHover: palette.warningDark, + border: palette.warningLight, + borderActive: palette.warningDark, iconBorder: palette.warning, - placeholderColor: palette.accents_3, }, error: { - border: palette.error, - borderHover: palette.errorDark, + border: palette.errorLight, + borderActive: palette.errorDark, iconBorder: palette.error, - placeholderColor: palette.error, }, } diff --git a/components/shared/dropdown.tsx b/components/shared/dropdown.tsx index 019066f08..9cc726817 100644 --- a/components/shared/dropdown.tsx +++ b/components/shared/dropdown.tsx @@ -102,6 +102,10 @@ const Dropdown: React.FC> = React.memo( const clickHandler = (event: React.MouseEvent) => { event.stopPropagation() + event.nativeEvent.stopImmediatePropagation() + event.preventDefault() + } + const mouseDownHandler = (event: React.MouseEvent) => { event.preventDefault() } @@ -110,7 +114,8 @@ const Dropdown: React.FC> = React.memo(
+ onClick={clickHandler} + onMouseDown={mouseDownHandler}> {children} diff --git a/lib/components/sidebar/active-link.tsx b/lib/components/sidebar/active-link.tsx index 12bd69466..4b662dae0 100644 --- a/lib/components/sidebar/active-link.tsx +++ b/lib/components/sidebar/active-link.tsx @@ -20,73 +20,55 @@ const ActiveLink: React.FC = React.memo(({ href, text }) => { const isActive = pathname === href return ( -
+ <> - + {title} {subtitle &&  {subtitle}} -
+ ) }) diff --git a/lib/components/sidebar/tabbar-mobile.tsx b/lib/components/sidebar/tabbar-mobile.tsx index 15ae2889f..d7171d23e 100644 --- a/lib/components/sidebar/tabbar-mobile.tsx +++ b/lib/components/sidebar/tabbar-mobile.tsx @@ -17,7 +17,7 @@ const TabbarMobile: React.FC = ({ onClick }) => { - Geist UI + Geist