slider component

This commit is contained in:
Martina 2020-07-27 16:56:03 -07:00
parent e65b00579f
commit 4e5a619b36
6 changed files with 587 additions and 12 deletions

View File

@ -247,6 +247,7 @@ export default class SystemPage extends React.Component {
<SidebarLink url={url} href="/system/checkboxes" title="Checkboxes" />
<SidebarLink url={url} href="/system/radios" title="Radios" />
<SidebarLink url={url} href="/system/toggles" title="Toggles" />
<SidebarLink url={url} href="/system/sliders" title="Sliders" />
<SidebarLink url={url} href="/system/inputs" title="Inputs" />
<SidebarLink url={url} href="/system/dropdowns" title="Dropdowns" />
<SidebarLink url={url} href="/system/datepicker" title="Datepicker" />

View File

@ -149,7 +149,6 @@ export class Input extends React.Component {
};
render() {
console.log(this._unit);
return (
<div
css={

View File

@ -0,0 +1,242 @@
import * as React from "react";
import * as Constants from "~/common/constants";
import { css } from "@emotion/react";
import { DescriptionGroup } from "~/components/system/components/fragments/DescriptionGroup";
import { Input } from "~/components/system/components/Input";
import Draggable from "react-draggable";
const STYLES_BAR_CONTAINER = css`
height: 48px;
display: grid;
grid-template-columns: 1fr auto;
align-items: center;
`;
const STYLES_SLIDER_BAR = css`
width: calc(100% - 24px);
height: 4px;
border-radius: 2px;
background-color: ${Constants.system.gray};
position: absolute;
top: 10px;
left: 12px;
box-sizing: border-box;
`;
const STYLES_ACTIVE_SLIDER_BAR = css`
height: 4px;
border-radius: 2px 0px 0px 2px;
background-color: ${Constants.system.brand};
position: absolute;
top: 10px;
left: 12px;
`;
const STYLES_BUBBLE = css`
font-family: ${Constants.font.text};
font-size: 0.9em;
padding: 4px;
border-radius: 4px;
background-color: ${Constants.system.gray};
display: inline-block;
position: absolute;
top: -2.5em;
transform: translateX(calc(-50% + 12px));
cursor: default;
::selection {
color: none;
background: none;
}
`;
const STYLES_SLIDER_HANDLE = css`
width: 24px;
height: 24px;
border-radius: 50%;
background-color: ${Constants.system.brand};
position: absolute;
top: 0px;
cursor: pointer;
:hover {
box-shadow: 0 0 0 4px rgba(0, 72, 255, 0.1);
}
:active {
box-shadow: 0 0 0 8px rgba(0, 72, 255, 0.2);
}
`;
export class Slider extends React.Component {
_bar;
static defaultProps = {
step: 1,
min: 0,
max: 100,
};
state = {
value: 0,
width: 0,
input: this.props.value,
};
componentDidMount = () => {
this.setState(
{
decimals: this.getDecimals(),
value:
((this.props.value - this.props.min) * this._bar.offsetWidth) /
(this.props.max - this.props.min),
},
this.updateDimensions
);
window.addEventListener("resize", this.updateDimensions);
};
componentWillUnmount = () => {
window.removeEventListener("resize", this.updateDimensions);
};
updateDimensions = () => {
let conversion = (this.props.max - this.props.min) / this._bar.offsetWidth;
this.setState({
width: this._bar.offsetWidth,
conversion,
step: this.props.step / conversion,
});
};
/* Gets how many decimal places the step has. To help deal with javascript rounding errors. */
getDecimals = () => {
let step = this.props.step.toString();
let place = step.indexOf(".");
if (place === -1) {
return 0;
}
return step.length - step.indexOf(".") - 1;
};
/* Converts it to increments of this.props.step while accounting for javascript rounding errors */
formatNum = (num) => {
return (Math.round(num / this.props.step) * this.props.step).toFixed(
this.state.decimals
);
};
/* Converts from px width to return value */
toValue = (px) => {
return px * this.state.conversion + this.props.min;
};
/* Converts from return value to px width */
toPx = (value) => {
return (value - this.props.min) / this.state.conversion;
};
/* NOTE (Martina): make sure you only query this.state.value and ui.deltaX once and save the value. Querying
it twice in one function call will give two different values since it changes so rapidly */
_handleDrag = (e, ui) => {
let px = this.state.value + ui.deltaX;
let value = this.formatNum(this.toValue(px));
this.props.onChange({
target: { name: this.props.name, value },
});
this.setState({ value: px, input: value });
};
_handleChange = (e) => {
let val = e.target.value;
if (isNaN(e.target.value)) {
val = this.props.min;
}
let value = Math.min(Math.max(val, this.props.min), this.props.max);
value = this.formatNum(value);
this.props.onChange({
target: { name: this.props.name, value },
});
let px = this.toPx(value);
this.setState({ value: px, input: e.target.value });
};
_handleBlur = (e) => {
this.setState({ input: this.props.value });
};
render() {
return (
<div>
<DescriptionGroup
full={this.props.full}
tooltip={this.props.tooltip}
label={this.props.label}
description={this.props.description}
/>
<div
css={STYLES_BAR_CONTAINER}
style={{
marginTop: this.props.bubble ? "32px" : "0px",
...this.props.containerStyle,
}}
>
<div style={{ height: "24px", position: "relative" }}>
<div
css={STYLES_SLIDER_BAR}
ref={(c) => {
this._bar = c;
}}
/>
<div
css={STYLES_ACTIVE_SLIDER_BAR}
style={{
width: `${this.state.value}px`,
}}
/>
<Draggable
axis="x"
position={{ x: this.state.value, y: 0 }}
bounds={{ left: 0, right: this.state.width }}
grid={
this.props.discrete ? [this.state.step, this.state.step] : null
}
onDrag={this._handleDrag}
handle="strong"
>
<div
style={{ position: "relative", width: "24px", height: "24px" }}
>
<strong>
<div css={STYLES_SLIDER_HANDLE} />
</strong>
{this.props.bubble ? (
<div css={STYLES_BUBBLE}>{this.props.value}</div>
) : null}
</div>
</Draggable>
</div>
{this.props.inputBox ? (
<Input
value={this.state.input}
onChange={this._handleChange}
onBlur={this._handleBlur}
pattern="^[\.\d-]*$"
style={{
width: "80px",
paddingLeft: "8px",
paddingRight: "8px",
textOverflow: "clip",
marginLeft: "16px",
...this.props.inputStyle,
}}
containerStyle={{ minWidth: "80px" }}
/>
) : null}
</div>
</div>
);
}
}

View File

@ -46,7 +46,7 @@ import {
LoaderProgress,
LoaderSpinner,
} from "~/components/system/components/Loaders";
import { Slider } from "~/components/system/components/Slider";
import {
SelectCountryMenu,
SelectMenu,
@ -115,6 +115,7 @@ export {
RadioGroup,
SelectCountryMenu,
SelectMenu,
Slider,
StatUpload,
StatDownload,
TabGroup,

View File

@ -273,9 +273,8 @@ export class FilecoinSettings extends React.Component {
description="How long you would like your files stored for, before the deal must be renewed or the file will be removed from the Filecoin network."
tooltip="Duration in epochs (~25 seconds)."
name="settings_cold_default_duration"
type="number"
unit="epochs"
pattern="[0-9]"
pattern="^\d*$"
value={this.state.settings_cold_default_duration}
onChange={this._handleChange}
/>
@ -288,9 +287,8 @@ export class FilecoinSettings extends React.Component {
tooltip="A higher replication factor means your files are more secure against loss, but also costs more."
name="settings_cold_default_replication_factor"
value={this.state.settings_cold_default_replication_factor}
type="number"
unit="miners"
pattern="[0-9]"
pattern="^\d*$"
onChange={this._handleChange}
/>
@ -301,8 +299,7 @@ export class FilecoinSettings extends React.Component {
description="The maximum price in Filecoin you're willing to pay to store 1 GB for 1 Epoch (~25 seconds)."
tooltip="Slate will always try to find you the best price, regardless of how high you set this."
name="settings_cold_default_max_price"
type="number"
pattern="[0-9]"
pattern="^\d*$"
value={this.state.settings_cold_default_max_price}
unit="FIL/GB/epoch"
onChange={this._handleChange}
@ -335,8 +332,7 @@ export class FilecoinSettings extends React.Component {
description="How long before a deal expires should it auto renew."
tooltip="Placeholder."
name="settings_cold_default_auto_renew_threshold"
type="number"
pattern="[0-9]"
pattern="^\d*$"
value={
this.state.settings_cold_default_auto_renew_threshold
}
@ -399,8 +395,7 @@ export class FilecoinSettings extends React.Component {
full
containerStyle={{ marginTop: 24 }}
label="Add timeout"
type="number"
pattern="\d"
pattern="^\d*$"
description="How many seconds Slate will search for a file in hot storage before executing an unfreeze retrieval deal to get it from cold storage."
tooltip="Placeholder."
name="settings_hot_ipfs_add_timeout"

337
pages/system/sliders.js Normal file
View File

@ -0,0 +1,337 @@
import * as React from "react";
import * as System from "~/components/system";
import * as Constants from "~/common/constants";
import Group from "~/components/system/Group";
import SystemPage from "~/components/system/SystemPage";
import ViewSourceLink from "~/components/system/ViewSourceLink";
import CodeBlock from "~/components/system/CodeBlock";
export default class SystemPageSliders extends React.Component {
state = {
one: 0,
two: 0,
three: 9500,
four: 0,
};
_handleChange = (e) => {
this.setState({ [e.target.name]: e.target.value });
};
render() {
return (
<SystemPage
title="SDS: Sliders"
description="..."
url="https://slate.host/system/sliders"
>
<System.H1>
Sliders <ViewSourceLink file="system/sliders.js" />
</System.H1>
<br />
<br />
<System.P>
The Slider component is used to select from a range of numeric values.
</System.P>
<br />
<br />
<br />
<System.H2>Imports</System.H2>
<hr />
<br />
<System.P>Import React and the Slider Component.</System.P>
<br />
<br />
<CodeBlock>
{`import * as React from 'react';
import { Slider } from 'slate-react-system';`}
</CodeBlock>
<br />
<br />
<System.H2>Slider</System.H2>
<hr />
<br />
<System.Slider
discrete
label="Discrete Slider"
value={this.state.one}
name="one"
min={0}
max={100}
step={20}
onChange={this._handleChange}
/>
<br />
<System.Slider
label="Continuous Slider"
value={this.state.two}
name="two"
min={0}
max={100}
step={20}
onChange={this._handleChange}
/>
<br />
<System.P>
Declare the Slider component, specifying a{" "}
<System.CodeText>min</System.CodeText>,{" "}
<System.CodeText>max</System.CodeText>, and{" "}
<System.CodeText>step</System.CodeText>. Declaring{" "}
<System.CodeText>discrete</System.CodeText> true will yield a slider
that snaps to each step.
</System.P>
<br />
<br />
<CodeBlock>
{`class ExampleOne extends React.Component {
state = {
one: 0,
two: 0
}
_handleChange = e => this.setState(
{ [e.target.name]: e.target.value }
);
render() {
return(
<div>
<System.Slider
discrete
label="Discrete Slider"
value={this.state.one}
name="one"
min={0}
max={100}
step={20}
onChange={this._handleChange}
/>
<System.Slider
label="Continuous Slider"
value={this.state.two}
name="two"
min={0}
max={100}
step={20}
onChange={this._handleChange}
/>
</div>
)
}
}`}
</CodeBlock>
<br />
<br />
<br />
<System.H2>Slider with display</System.H2>
<hr />
<br />
<System.Slider
inputBox
label="Slider with Input Box"
min={9000}
max={10000}
step={100}
value={this.state.three}
name="three"
onChange={this._handleChange}
inputStyle={{ width: "60px" }}
/>
<br />
<System.Slider
bubble
label="Slider with Display Bubble"
min={-10}
max={10}
step={0.5}
value={this.state.four}
name="four"
onChange={this._handleChange}
/>
<br />
<System.P>
You can declare the Slider component with{" "}
<System.CodeText>inputBox</System.CodeText> or{" "}
<System.CodeText>bubble</System.CodeText> to include a display of the
value. Values can be entered in the input box and the input box can be
styled with <System.CodeText>inputStyle</System.CodeText>.
</System.P>
<br />
<br />
<CodeBlock>
{`class ExampleTwo extends React.Component {
state = {
three: 9500,
four: 0
}
_handleChange = e => this.setState(
{ [e.target.name]: e.target.value }
);
render() {
return(
<div>
<System.Slider
inputBox
label="Slider with Input Box"
min={9000}
max={10000}
step={100}
value={this.state.one}
name="three"
onChange={this._handleChange}
inputStyle={{ width: "60px" }}
/>
<System.Slider
bubble
label="Slider with Display Bubble"
min={-10}
max={10}
step={0.5}
value={this.state.four}
name="four"
onChange={this._handleChange}
/>
</div>
)
}
}`}
</CodeBlock>
<br />
<br />
<br />
<System.H2>Accepted React Properties</System.H2>
<hr />
<br />
<Group title="Toggles">
<System.Table
data={{
columns: [
{ key: "a", name: "Name", width: "128px" },
{ key: "b", name: "Type", width: "88px", type: "OBJECT_TYPE" },
{ key: "c", name: "Default", width: "88px" },
{ key: "d", name: "Description", width: "100%" },
],
rows: [
{
id: 1,
a: (
<span style={{ fontFamily: Constants.font.semiBold }}>
onChange
</span>
),
b: "function",
c: "null",
d: "Function called upon an onChange event",
},
{
id: 2,
a: (
<span style={{ fontFamily: Constants.font.semiBold }}>
value
</span>
),
b: "number",
c: "null",
d:
"The value that the slider takes. Can be used to assign default values as well.",
},
{
id: 3,
a: "name",
b: "string",
c: "null",
d: "Slider name.",
},
{
id: 4,
a: "label",
b: "string",
c: "null",
d: "Label text.",
},
{
id: 5,
a: "description",
b: "string",
c: "null",
d: "Description text.",
},
{
id: 6,
a: "tooltip",
b: "string",
c: "null",
d: "Tooltip text.",
},
{
id: 7,
a: "min",
b: "number",
c: "0",
d: "Lower end of range",
},
{
id: 8,
a: "max",
b: "number",
c: "100",
d: "Higher end of range",
},
{
id: 9,
a: "step",
b: "number",
c: "1",
d: "Increments in which values can be selected.",
},
{
id: 10,
a: "discrete",
b: "boolean",
c: "false",
d:
"If true, slider will snap to steps specified. Otherwise, slider is continuous",
},
{
id: 11,
a: "bubble",
b: "boolean",
c: "false",
d:
"If true, a bubble floating above the handle will be displayed with the selected value.",
},
{
id: 12,
a: "inputBox",
b: "boolean",
c: "false",
d:
"If true, an input box will be displayed with the selected value. The value can be edited using the input box.",
},
{
id: 13,
a: "inputStyle",
b: "Object",
c: "null",
d:
"Style applied to the input box (useful for specifying width, etc).",
},
{
id: 14,
a: "containerStyle",
b: "Object",
c: "null",
d:
"Style applied to the container holding the slider and input box (useful for specifying margin or height, etc).",
},
],
}}
/>
</Group>
</SystemPage>
);
}
}