Skip to content

Commit

Permalink
Merge pull request #4 from MAPC/forms
Browse files Browse the repository at this point in the history
Forms
  • Loading branch information
anaximander authored Jun 6, 2024
2 parents 8fb6e05 + 02117b7 commit 7fe5df2
Show file tree
Hide file tree
Showing 27 changed files with 1,197 additions and 3 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
"arcgis-pbf-parser": "^0.0.4",
"idb": "^8.0.0",
"leaflet": "^1.9.4",
"react-bootstrap-icons": "^1.11.4",
"react-leaflet": "^4.2.1"
}
}
2 changes: 1 addition & 1 deletion src/components/ContentCards/ContentCards.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export const ContentCards = ({ CardsData, Themes, Height }) => {

generateCards(CardsData);
}, [CardsData]);
console.log(Themes);

return (
<CardsContainer height={Height}>
<ThemeProvider theme={Themes}>{CardsSections}</ThemeProvider>
Expand Down
40 changes: 40 additions & 0 deletions src/components/Forms/DatePicker/DatePicker.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import styled, { ThemeProvider } from "styled-components";
import React, { useEffect, useState } from "react";

import Form from "react-bootstrap/Form";

import * as style_theme from "../../themes/styles";

const StyledDatePicker = styled(Form.Control)`
width: 14rem;
color: ${(props) => props.theme.secondary};
&:focus {
box-shadow: 0 0 0 0.2rem ${(props) => props.theme.tertiary};
}
.form-control {
color: ${(props) => props.theme.secondary};
}
`;

export const DatePicker = ({ text, date, disabled = false, isValid = false, isInvalid = false, onChange = () => {}, theme }) => {
const [dateValue, setDateValue] = useState(date ? new Date(date).toISOString().split("T")[0] : new Date().toISOString().split("T")[0]);

return (
<StyledDatePicker
name={text}
placeholder={text}
value={dateValue}
type="date"
theme={theme}
onChange={(e) => {
setDateValue(e.target.value);
onChange(e.target.value);
}}
isValid={isValid}
isInvalid={isInvalid}
disabled={disabled}
/>
);
};

export default DatePicker;
30 changes: 30 additions & 0 deletions src/components/Forms/DatePicker/DatePicker.stories.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from "react";
import { Meta, StoryObj } from "@storybook/react";
import { MemoryRouter } from "react-router-dom";
import { DatePicker } from "./DatePicker";

import * as theme from "../../../themes/colors";

{
/* decorators for decorating component with router capabilities, not necessary for this component */
}
const meta = {
component: DatePicker,
title: "Components/Forms/DatePicker",
};
export default meta;

function handleChange(value) {
console.log(value);
}

export const DatePickerStory = (args) => <DatePicker {...args} />;
DatePickerStory.args = {
text: "Due Date",
date: new Date("June 10, 2010").toISOString().split("T")[0],
onChange: handleChange,
theme: theme.greenTheme,
isValid: false,
isInvalid: false,
disabled: false,
};
1 change: 1 addition & 0 deletions src/components/Forms/DatePicker/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { DatePicker } from "./DatePicker";
269 changes: 269 additions & 0 deletions src/components/Forms/MultiThumbSlider/MultiThumbSlider.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
import styled, { ThemeProvider } from "styled-components";
import React, { useEffect, useState } from "react";
import Form from "react-bootstrap/Form";
import Button from "react-bootstrap/Button";
import InputGroup from "react-bootstrap/InputGroup";
import { CaretLeftFill, CaretRightFill } from "react-bootstrap-icons";

const ControlContainer = styled.div`
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
color: ${(props) => props.theme.secondary};
`;
const RangeControl = styled.div`
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
width: 100%;
gap: 1rem;
input.form-range {
flex: 7;
}
`;

const RangeValue = styled(Form.Control)`
width: calc(4rem + ${(props) => String(props.max).length}ch) !important;
margin-bottom: 0.75rem;
`;

const RangeTerminus = styled.div`
flex: 1;
padding-top: 4px;
text-align: center;
`;

const RangeWrapper = styled.div`
position: relative;
width: 100%;
flex: 7;
margin-bottom: 1rem;
`;

const Range = styled.input.attrs({ type: "range" })`
flex: 1;
width: 100%;
pointer-events: auto;
position: absolute;
-webkit-appearance: none; // Necessary to remove default styling
appearance: none;
height: 8px;
border-radius: 4px;
background-color: ${(props) => (props.slider ? props.theme.tertiary : "transparent")};
/* Styling the slider thumb for WebKit browsers */
&::-webkit-slider-thumb {
-webkit-appearance: none; // Necessary to remove default styling
appearance: none;
height: 20px;
width: 20px;
position: relative;
top: -6px;
z-index: 10;
pointer-events: auto;
background: ${(props) => (props.disabled ? props.theme.disabled.special : props.theme.special)};
border-radius: 50%; // Circular thumb
cursor: ${(props) => (props.disabled ? "cursor" : "pointer")};
transition: background-color 0.3s ease; // Smooth transition for hover effect
}
&::-moz-range-thumb {
-moz-appearance: none; // Necessary to remove default styling
appearance: none;
height: 20px;
width: 20px;
position: relative;
top: -6px;
z-index: 10;
pointer-events: auto;
background: ${(props) => (props.disabled ? props.theme.disabled.special : props.theme.special)};
border-radius: 50%; // Circular thumb
border-color: ${(props) => (props.disabled ? props.theme.disabled.primary : props.theme.primary)};
cursor: ${(props) => (props.disabled ? "cursor" : "pointer")};
transition: background-color 0.3s ease; // Smooth transition for hover effect
}
/* Styling the slider track for WebKit browsers */
&::-webkit-slider-runnable-track {
-webkit-appearance: none; // Necessary to remove default styling
appearance: none;
width: 100%;
height: 8px;
border: none;
border-radius: 4px;
pointer-events: none !important;
background-color: ${(props) => (props.slider ? (props.disabled ? props.theme.disabled.primary : props.theme.tertiary) : "transparent")};
}
&::-moz-range-track {
-moz-appearance: none; // Necessary to remove default styling
appearance: none;
width: 100%;
height: 8px;
border: none;
border-radius: 4px;
pointer-events: none !important;
background-color: ${(props) => (props.slider ? (props.disabled ? props.theme.disabled.primary : props.theme.tertiary) : "transparent")};
}
`;

const NumericControl = styled(InputGroup)`
.input-group-text {
color: ${(props) => props.theme.secondary};
}
width: initial;
/*
width: fit-content;
min-width: fit-content;
max-width: fit-content;
*/
`;

const InputDecoration = styled(InputGroup.Text)`
.form-control {
color: ${(props) => props.theme.secondary};
}
height: 100%;
`;

export const MultiThumbSlider = ({
min = 0,
max = 100,
leftValue = 0,
rightValue = 100,
disabled = false,
percentage = false,
onChange = () => {},
theme,
numericControl = true,
step = 1,
}) => {
const [leftInputValue, setLeftInputValue] = useState(Number(leftValue));
const [rightInputValue, setRightInputValue] = useState(Number(rightValue));

if (percentage) {
max = 1;
}

function handleChange(data) {
onChange(data);
}

return (
<ControlContainer theme={theme}>
<RangeControl>
<RangeTerminus>{min}</RangeTerminus>

<RangeWrapper>
<Range
type="range"
min={min}
max={max}
value={rightInputValue}
disabled={disabled}
step={max <= 1 ? "0.01" : step}
onChange={(e) => {
if (Number(e.target.value) > leftInputValue) {
setRightInputValue(Number(e.target.value));
handleChange(Number(e.target.value));
}
}}
slider
theme={theme}
/>
<Range
type="range"
min={min}
max={max}
value={leftInputValue}
disabled={disabled}
step={max <= 1 ? "0.01" : step}
onChange={(e) => {
if (Number(e.target.value) < rightInputValue) {
setLeftInputValue(Number(e.target.value));
handleChange(Number(e.target.value));
}
}}
slider={false}
theme={theme}
/>
</RangeWrapper>

<RangeTerminus>{max}</RangeTerminus>
</RangeControl>

{numericControl && (
<NumericControl
size="sm"
theme={theme}
>
<InputDecoration theme={theme}>At least</InputDecoration>
<RangeValue
type="number"
value={
percentage
? (Number(leftInputValue) * 100).toLocaleString(undefined, {
maximumFractionDigits: 0,
})
: Number(leftInputValue).toString()
}
disabled={disabled}
max={max}
min={min}
onChange={(e) => {
let newValue;
if (percentage) {
newValue = Number(e.target.value.replace(/%/g, "")) / 100;
} else {
newValue = Number(e.target.value);
}
if (newValue > max) {
newValue = max;
} else if (newValue < min) {
newValue = min;
}
handleChange(newValue);
}}
/>
<InputDecoration theme={theme}>At most</InputDecoration>
<RangeValue
type="number"
value={
percentage
? (Number(rightInputValue) * 100).toLocaleString(undefined, {
maximumFractionDigits: 0,
})
: Number(rightInputValue).toString()
}
disabled={disabled}
max={max}
min={min}
onChange={(e) => {
let newValue;
if (percentage) {
newValue = Number(e.target.value.replace(/%/g, "")) / 100;
} else {
newValue = Number(e.target.value);
}
if (newValue > max) {
newValue = max;
} else if (newValue < min) {
newValue = min;
}
handleChange(newValue);
}}
/>
{percentage && <InputDecoration theme={theme}>%</InputDecoration>}
</NumericControl>
)}
</ControlContainer>
);
};

export default MultiThumbSlider;
Loading

0 comments on commit 7fe5df2

Please sign in to comment.