mirror of
https://github.com/wassname/talk.git
synced 2026-07-07 05:04:22 +08:00
feat: Implement SelectField
This commit is contained in:
@@ -1,17 +1,5 @@
|
||||
@font-face {
|
||||
font-family: "Material Icons";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local("Material Icons"), local("MaterialIcons-Regular"),
|
||||
url(material-design-icons/iconfont/MaterialIcons-Regular.woff2)
|
||||
format("woff2"),
|
||||
url(material-design-icons/iconfont/MaterialIcons-Regular.woff)
|
||||
format("woff"),
|
||||
url(material-design-icons/iconfont/MaterialIcons-Regular.ttf)
|
||||
format("truetype");
|
||||
}
|
||||
|
||||
.root {
|
||||
composes: icon from "talk-ui/shared/icon.css";
|
||||
font-family: "Material Icons";
|
||||
speak: none;
|
||||
font-style: normal;
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import React from "react";
|
||||
import TestRenderer from "react-test-renderer";
|
||||
|
||||
import { PropTypesOf } from "talk-ui/types";
|
||||
import OptGroup from "./OptGroup";
|
||||
|
||||
it("renders correctly", () => {
|
||||
const props: PropTypesOf<typeof OptGroup> = {
|
||||
label: "mamals",
|
||||
children: <span />,
|
||||
};
|
||||
const renderer = TestRenderer.create(<OptGroup {...props} />);
|
||||
expect(renderer.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
import React from "react";
|
||||
import { StatelessComponent } from "react";
|
||||
|
||||
export interface OptGroupProps {
|
||||
label: string;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
const OptionGroup: StatelessComponent<OptGroupProps> = props => {
|
||||
return <optgroup {...props} />;
|
||||
};
|
||||
|
||||
export default OptionGroup;
|
||||
@@ -0,0 +1,16 @@
|
||||
import React from "react";
|
||||
import TestRenderer from "react-test-renderer";
|
||||
|
||||
import { PropTypesOf } from "talk-ui/types";
|
||||
import Option from "./Option";
|
||||
|
||||
it("renders correctly", () => {
|
||||
const props: PropTypesOf<typeof Option> = {
|
||||
disabled: false,
|
||||
hidden: false,
|
||||
value: "apple",
|
||||
children: "Apple",
|
||||
};
|
||||
const renderer = TestRenderer.create(<Option {...props} />);
|
||||
expect(renderer.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
import React from "react";
|
||||
import { StatelessComponent } from "react";
|
||||
|
||||
export interface OptionProps {
|
||||
value?: string;
|
||||
disabled?: boolean;
|
||||
hidden?: boolean;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
const Option: StatelessComponent<OptionProps> = props => {
|
||||
return <option {...props} />;
|
||||
};
|
||||
|
||||
export default Option;
|
||||
@@ -0,0 +1,65 @@
|
||||
.root {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.iconWrapper {
|
||||
position: absolute;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
right: calc(0.5 * var(--spacing-unit));
|
||||
height: 100%;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.keyboardFocus:focus {
|
||||
outline-width: 3px;
|
||||
outline-color: Highlight;
|
||||
outline-color: -webkit-focus-ring-color;
|
||||
outline-style: auto;
|
||||
}
|
||||
|
||||
.select {
|
||||
composes: menuItem from "talk-ui/shared/typography.css";
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
outline: none;
|
||||
color: var(--palette-text-primary);
|
||||
|
||||
&::-moz-focus-inner {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
&:-moz-focusring {
|
||||
color: transparent;
|
||||
text-shadow: 0 0 0 #000;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background-color: var(--palette-grey-lightest);
|
||||
border-color: var(--palette-text-secondary);
|
||||
color: var(--palette-text-secondary);
|
||||
}
|
||||
|
||||
border: 1px solid var(--palette-text-primary);
|
||||
border-radius: var(--round-corners);
|
||||
background-color: var(--palette-common-white);
|
||||
|
||||
padding: calc(0.5 * var(--spacing-unit)) calc(2 * var(--spacing-unit))
|
||||
calc(0.5 * var(--spacing-unit)) var(--spacing-unit);
|
||||
|
||||
&::-ms-expand {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.fullWidth {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.iconWrapperDisabled {
|
||||
color: var(--palette-text-secondary);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
---
|
||||
name: SelectField
|
||||
menu: UI Kit
|
||||
---
|
||||
|
||||
import { Playground, PropsTable } from 'docz'
|
||||
import SelectField from './SelectField.tsx'
|
||||
import Option from './Option.tsx'
|
||||
import OptGroup from './OptGroup.tsx'
|
||||
import HorizontalGutter from '../HorizontalGutter'
|
||||
import Container from "react-with-state-props"
|
||||
|
||||
# SelectField
|
||||
|
||||
## Basic Use
|
||||
<Playground>
|
||||
<Container
|
||||
state={{ value: "blue" }}
|
||||
render={props => (
|
||||
<SelectField value={props.value} onChange={e => props.setValue(e.value)}>
|
||||
<Option value="red">Red Pill</Option>
|
||||
<Option value="blue">Blue Pill</Option>
|
||||
</SelectField>
|
||||
)}/>
|
||||
</Playground>
|
||||
|
||||
## Groups
|
||||
<Playground>
|
||||
<Container
|
||||
state={{ value: "dog" }}
|
||||
render={props => (
|
||||
<SelectField value={props.value} onChange={e => props.setValue(e.value)}>
|
||||
<OptGroup label="mammals">
|
||||
<Option value="dog">Dog</Option>
|
||||
<Option value="cat">Cat</Option>
|
||||
</OptGroup>
|
||||
<OptGroup label="fish">
|
||||
<Option value="salmon">Salmon</Option>
|
||||
<Option value="tuna">Tuna</Option>
|
||||
</OptGroup>
|
||||
</SelectField>
|
||||
)}/>
|
||||
</Playground>
|
||||
|
||||
|
||||
## Disabled
|
||||
<Playground>
|
||||
<SelectField disabled>
|
||||
<Option value="red">Red Pill</Option>
|
||||
<Option value="blue">Blue Pill</Option>
|
||||
</SelectField>
|
||||
</Playground>
|
||||
@@ -0,0 +1,21 @@
|
||||
import { noop } from "lodash";
|
||||
import React from "react";
|
||||
import TestRenderer from "react-test-renderer";
|
||||
|
||||
import { PropTypesOf } from "talk-ui/types";
|
||||
import SelectField from "./SelectField";
|
||||
|
||||
it("renders correctly", () => {
|
||||
const props: PropTypesOf<typeof SelectField> = {
|
||||
id: "selectID",
|
||||
autofocus: true,
|
||||
name: "selectName",
|
||||
value: "pie",
|
||||
className: "customClassName",
|
||||
fullWidth: true,
|
||||
onChange: noop,
|
||||
disabled: true,
|
||||
};
|
||||
const renderer = TestRenderer.create(<SelectField {...props} />);
|
||||
expect(renderer.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
@@ -0,0 +1,81 @@
|
||||
import cn from "classnames";
|
||||
import React, {
|
||||
ChangeEvent,
|
||||
EventHandler,
|
||||
FocusEvent,
|
||||
MouseEvent,
|
||||
} from "react";
|
||||
import { StatelessComponent } from "react";
|
||||
|
||||
import { withKeyboardFocus, withStyles } from "talk-ui/hocs";
|
||||
import Icon from "../Icon";
|
||||
|
||||
import * as styles from "./SelectField.css";
|
||||
|
||||
export interface SelectFieldProps {
|
||||
id?: string;
|
||||
autofocus?: boolean;
|
||||
/**
|
||||
* Name
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* Value that has been selected.
|
||||
*/
|
||||
value?: string;
|
||||
/**
|
||||
* Convenient prop to override the root styling.
|
||||
*/
|
||||
className?: string;
|
||||
/**
|
||||
* Override or extend the styles applied to the component.
|
||||
*/
|
||||
classes: typeof styles;
|
||||
/*
|
||||
* If set renders a full width button
|
||||
*/
|
||||
fullWidth?: boolean;
|
||||
|
||||
onChange?: EventHandler<ChangeEvent<HTMLSelectElement>>;
|
||||
onFocus: EventHandler<FocusEvent<HTMLSelectElement>>;
|
||||
onBlur: EventHandler<FocusEvent<HTMLSelectElement>>;
|
||||
onMouseDown: EventHandler<MouseEvent<HTMLSelectElement>>;
|
||||
|
||||
disabled?: boolean;
|
||||
keyboardFocus: boolean;
|
||||
}
|
||||
|
||||
const SelectField: StatelessComponent<SelectFieldProps> = props => {
|
||||
const {
|
||||
className,
|
||||
classes,
|
||||
fullWidth,
|
||||
keyboardFocus,
|
||||
children,
|
||||
disabled,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
const selectClassName = cn(classes.select, {
|
||||
[classes.fullWidth]: fullWidth,
|
||||
[classes.keyboardFocus]: keyboardFocus,
|
||||
});
|
||||
|
||||
const iconWrapperClassName = cn(classes.iconWrapper, {
|
||||
[classes.iconWrapperDisabled]: disabled,
|
||||
});
|
||||
|
||||
return (
|
||||
<span className={cn(classes.root, className)}>
|
||||
<select className={selectClassName} disabled={disabled} {...rest}>
|
||||
{children}
|
||||
</select>
|
||||
<span className={iconWrapperClassName} aria-hidden>
|
||||
<Icon>expand_more</Icon>
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
const enhanced = withStyles(styles)(withKeyboardFocus(SelectField));
|
||||
export default enhanced;
|
||||
@@ -0,0 +1,9 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<optgroup
|
||||
label="mamals"
|
||||
>
|
||||
<span />
|
||||
</optgroup>
|
||||
`;
|
||||
@@ -0,0 +1,11 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<option
|
||||
disabled={false}
|
||||
hidden={false}
|
||||
value="apple"
|
||||
>
|
||||
Apple
|
||||
</option>
|
||||
`;
|
||||
@@ -0,0 +1,31 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<span
|
||||
className="SelectField-root customClassName"
|
||||
>
|
||||
<select
|
||||
autofocus={true}
|
||||
className="SelectField-select SelectField-fullWidth"
|
||||
disabled={true}
|
||||
id="selectID"
|
||||
name="selectName"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
value="pie"
|
||||
/>
|
||||
<span
|
||||
aria-hidden={true}
|
||||
className="SelectField-iconWrapper SelectField-iconWrapperDisabled"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm"
|
||||
>
|
||||
expand_more
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
`;
|
||||
@@ -0,0 +1,3 @@
|
||||
export { default, default as SelectField } from "./SelectField";
|
||||
export { default as Option } from "./Option";
|
||||
export { default as OptGroup } from "./OptGroup";
|
||||
@@ -7,6 +7,7 @@ menu: UI Kit
|
||||
|
||||
### Examples
|
||||
|
||||
import { Playground } from 'docz'
|
||||
import { Flex, Step, StepBar } from './index'
|
||||
|
||||
<Playground>
|
||||
|
||||
@@ -23,3 +23,4 @@ export { default as AriaInfo } from "./AriaInfo";
|
||||
export { default as Message, MessageIcon } from "./Message";
|
||||
export { Tab, TabBar, TabContent, TabPane } from "./Tabs";
|
||||
export { StepBar, Step } from "./Steps";
|
||||
export { SelectField, Option, OptGroup } from "./SelectField";
|
||||
|
||||
@@ -63,7 +63,4 @@ const withKeyboardFocus = hoistStatics<InjectedProps>(
|
||||
}
|
||||
);
|
||||
|
||||
// TODO: workaround, add bug link.
|
||||
export default withKeyboardFocus as <P extends Partial<InjectedProps>>(
|
||||
BaseComponent: React.ComponentType<P>
|
||||
) => React.ComponentType<P>;
|
||||
export default withKeyboardFocus;
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
@font-face {
|
||||
font-family: "Material Icons";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local("Material Icons"), local("MaterialIcons-Regular"),
|
||||
url(material-design-icons/iconfont/MaterialIcons-Regular.woff2)
|
||||
format("woff2"),
|
||||
url(material-design-icons/iconfont/MaterialIcons-Regular.woff)
|
||||
format("woff"),
|
||||
url(material-design-icons/iconfont/MaterialIcons-Regular.ttf)
|
||||
format("truetype");
|
||||
}
|
||||
|
||||
.icon {
|
||||
font-family: "Material Icons";
|
||||
speak: none;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
font-variant: normal;
|
||||
text-transform: none;
|
||||
line-height: 1;
|
||||
overflow: hidden;
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
letter-spacing: 0;
|
||||
|
||||
/* Enable Ligatures */
|
||||
font-feature-settings: "liga";
|
||||
font-variant-ligatures: "discretionary-ligatures";
|
||||
|
||||
/* Support for Safari and Chrome. */
|
||||
text-rendering: optimizeLegibility;
|
||||
|
||||
/* Better Font Rendering */
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
@@ -190,3 +190,12 @@
|
||||
letter-spacing: calc(0.2em / 18);
|
||||
color: var(--palette-text-primary);
|
||||
}
|
||||
|
||||
.menuItem {
|
||||
font-size: calc(16rem / var(--rem-base));
|
||||
font-weight: var(--font-weight-regular);
|
||||
font-family: var(--font-family-sans-serif);
|
||||
line-height: calc(19em / 16);
|
||||
letter-spacing: calc(0.2em / 16);
|
||||
color: var(--palette-text-primary);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user