-
Notifications
You must be signed in to change notification settings - Fork 897
fix(site): fix floating number on duration fields #13209
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 3 commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
6d359fd
Add DurationField component
BrunoQuaresma 5ac0602
Add empty story
BrunoQuaresma 95e4f44
Avoid negative values
BrunoQuaresma 4a48102
Use duration field time_til_dormant_ms
BrunoQuaresma 09ddb8f
Fix parent updates
BrunoQuaresma 3427160
Fix helper text
BrunoQuaresma eef159c
Support errors
BrunoQuaresma b3042c6
Use valueMs to make the value is in miliseconds
BrunoQuaresma e6101cf
Replace useState by useReducer
BrunoQuaresma c82fc8f
Add 10 to base int
BrunoQuaresma 1b77488
Merge branch 'main' of https://github.com/coder/coder into bq/fix-flo…
BrunoQuaresma 9036465
Use a number mask
BrunoQuaresma cac3a89
Make number input full width
BrunoQuaresma d89ec94
Add duration field to auto deletion
BrunoQuaresma 0a93c9f
Apply duration field to failure clean up
BrunoQuaresma 5a35e1a
Merge branch 'main' of https://github.com/coder/coder into bq/fix-flo…
BrunoQuaresma 45a5eb5
Add extra tests to storybook
BrunoQuaresma a6ff426
Fix tests
BrunoQuaresma File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
50 changes: 50 additions & 0 deletions
50
site/src/components/DurationField/DurationField.stories.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import type { Meta, StoryObj } from "@storybook/react"; | ||
import { useState } from "react"; | ||
import { DurationField } from "./DurationField"; | ||
|
||
const meta: Meta<typeof DurationField> = { | ||
title: "components/DurationField", | ||
component: DurationField, | ||
args: { | ||
label: "Duration", | ||
}, | ||
render: function RenderComponent(args) { | ||
const [value, setValue] = useState<number | undefined>(args.value); | ||
return ( | ||
<DurationField | ||
{...args} | ||
value={value} | ||
onChange={(value) => setValue(value)} | ||
/> | ||
); | ||
}, | ||
}; | ||
|
||
export default meta; | ||
type Story = StoryObj<typeof DurationField>; | ||
|
||
export const Empty: Story = { | ||
args: { | ||
value: undefined, | ||
}, | ||
}; | ||
|
||
export const Hours: Story = { | ||
args: { | ||
value: hoursToMs(16), | ||
}, | ||
}; | ||
|
||
export const Days: Story = { | ||
args: { | ||
value: daysToMs(2), | ||
}, | ||
}; | ||
BrunoQuaresma marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
function hoursToMs(hours: number): number { | ||
return hours * 60 * 60 * 1000; | ||
} | ||
|
||
function daysToMs(days: number): number { | ||
return days * 24 * 60 * 60 * 1000; | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
import KeyboardArrowDown from "@mui/icons-material/KeyboardArrowDown"; | ||
import MenuItem from "@mui/material/MenuItem"; | ||
import Select from "@mui/material/Select"; | ||
import TextField from "@mui/material/TextField"; | ||
import { useState, type FC } from "react"; | ||
|
||
type TimeUnit = "days" | "hours"; | ||
|
||
// Value should be in milliseconds or undefined. Undefined means no value. | ||
type DurationValue = number | undefined; | ||
|
||
type DurationFieldProps = { | ||
label: string; | ||
value: DurationValue; | ||
onChange: (value: DurationValue) => void; | ||
}; | ||
|
||
export const DurationField: FC<DurationFieldProps> = (props) => { | ||
const { label, value, onChange } = props; | ||
const [timeUnit, setTimeUnit] = useState<TimeUnit>(() => { | ||
if (!value) { | ||
return "hours"; | ||
} | ||
|
||
return Number.isInteger(durationToDays(value)) ? "days" : "hours"; | ||
}); | ||
BrunoQuaresma marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
return ( | ||
<div | ||
css={{ | ||
display: "flex", | ||
gap: 8, | ||
}} | ||
> | ||
<TextField | ||
css={{ maxWidth: 160 }} | ||
label={label} | ||
value={ | ||
!value | ||
? "" | ||
: timeUnit === "hours" | ||
? durationToHours(value) | ||
: durationToDays(value) | ||
} | ||
BrunoQuaresma marked this conversation as resolved.
Show resolved
Hide resolved
|
||
onChange={(e) => { | ||
if (e.target.value === "") { | ||
onChange(undefined); | ||
} | ||
|
||
let value = parseInt(e.target.value); | ||
|
||
if (Number.isNaN(value)) { | ||
return; | ||
} | ||
|
||
// Avoid negative values | ||
value = Math.abs(value); | ||
|
||
onChange( | ||
timeUnit === "hours" | ||
? hoursToDuration(value) | ||
: daysToDuration(value), | ||
); | ||
}} | ||
inputProps={{ | ||
step: 1, | ||
type: "number", | ||
}} | ||
/> | ||
<Select | ||
css={{ width: 120, "& .MuiSelect-icon": { padding: 2 } }} | ||
value={timeUnit} | ||
onChange={(e) => { | ||
setTimeUnit(e.target.value as TimeUnit); | ||
}} | ||
inputProps={{ "aria-label": "Time unit" }} | ||
IconComponent={KeyboardArrowDown} | ||
> | ||
<MenuItem | ||
value="hours" | ||
disabled={Boolean(value && !canConvertDurationToHours(value))} | ||
BrunoQuaresma marked this conversation as resolved.
Show resolved
Hide resolved
|
||
> | ||
Hours | ||
</MenuItem> | ||
<MenuItem | ||
value="days" | ||
disabled={Boolean(value && !canConvertDurationToDays(value))} | ||
> | ||
Days | ||
</MenuItem> | ||
</Select> | ||
</div> | ||
); | ||
}; | ||
|
||
function durationToHours(duration: number): number { | ||
return duration / 1000 / 60 / 60; | ||
} | ||
|
||
function hoursToDuration(hours: number): number { | ||
return hours * 60 * 60 * 1000; | ||
} | ||
|
||
function durationToDays(duration: number): number { | ||
return duration / 1000 / 60 / 60 / 24; | ||
} | ||
|
||
function daysToDuration(days: number): number { | ||
return days * 24 * 60 * 60 * 1000; | ||
} | ||
|
||
function canConvertDurationToDays(duration: number): boolean { | ||
return Number.isInteger(durationToDays(duration)); | ||
} | ||
|
||
function canConvertDurationToHours(duration: number): boolean { | ||
return Number.isInteger(durationToHours(duration)); | ||
} | ||
BrunoQuaresma marked this conversation as resolved.
Show resolved
Hide resolved
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.