Skip to content

Commit

Permalink
feat(TextInput): make it possible to add a suffix
Browse files Browse the repository at this point in the history
  • Loading branch information
filiptammergard committed Apr 21, 2023
1 parent 47802e2 commit 90eccf4
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 7 deletions.
5 changes: 5 additions & 0 deletions .changeset/tame-boats-vanish.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@einride/ui": minor
---

TextInput: Make it possible to add a suffix.
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import styled from "@emotion/styled"
import { ComponentPropsWithoutRef, ElementType, ReactNode, forwardRef, useId } from "react"
import { ContentColor } from "../../../../lib/theme/types"
import {
ComponentPropsWithoutRef,
ElementType,
ReactNode,
forwardRef,
useCallback,
useId,
useState,
} from "react"
import { useTheme } from "../../../../hooks/useTheme"
import { ContentColor, Theme } from "../../../../lib/theme/types"
import { Box, BoxProps } from "../../../layout/Box/Box"
import { Caption, CaptionProps } from "../../../typography/Caption/Caption"

Expand Down Expand Up @@ -29,15 +38,27 @@ export interface BaseInputProps extends ComponentPropsWithoutRef<"input"> {
/** Status of the input, controlling color and icon. */
status?: Status | undefined

/** Suffix shown after input value. For example `kg`. */
suffix?: ReactNode

/** Props passed to root element. */
wrapperProps?: BoxProps
}

export const BaseInput = forwardRef<HTMLInputElement, BaseInputProps>(
({ label, labelProps, leftIcon, message, rightIcon, status, wrapperProps, ...props }, ref) => {
(
{ label, labelProps, leftIcon, message, rightIcon, status, suffix, wrapperProps, ...props },
ref,
) => {
const id = useId()
const messageId = useId()

const [suffixInlineSizePx, setSuffixInlineSizePx] = useState(0)
const suffixRef = useCallback((node: HTMLDivElement | null) => {
setSuffixInlineSizePx(node ? node.offsetWidth : 0)
}, [])
const theme = useTheme()
const inlineEndOffsetRem = getInlineEndOffsetRem(suffixInlineSizePx, !!rightIcon, theme)
console.log(suffixInlineSizePx, inlineEndOffsetRem)
return (
<Box {...wrapperProps}>
{label && (
Expand All @@ -55,9 +76,15 @@ export const BaseInput = forwardRef<HTMLInputElement, BaseInputProps>(
hasLabel={!!label}
leftIcon={!!leftIcon}
rightIcon={!!rightIcon}
inlineEndOffsetRem={inlineEndOffsetRem}
id={id}
ref={ref}
/>
{suffix && (
<Suffix rightIcon={!!rightIcon} ref={suffixRef}>
{suffix}
</Suffix>
)}
{rightIcon && <RightIconWrapper>{rightIcon}</RightIconWrapper>}
</InputWrapper>
{message && (
Expand All @@ -74,6 +101,23 @@ export interface MessageProps extends Omit<CaptionProps, "children"> {
"data-testid"?: string
}

const getInlineEndOffsetRem = (
suffixInlineSizePx: number,
rightIcon: boolean,
theme: Theme,
): number => {
if (suffixInlineSizePx && rightIcon) {
return suffixInlineSizePx / 16 + 5 * theme.spacingBase
}
if (suffixInlineSizePx) {
return suffixInlineSizePx / 16 + 2 * theme.spacingBase
}
if (rightIcon) {
return 4 * theme.spacingBase
}
return 0
}

const StyledLabel = styled.label`
display: inline-block;
font-family: ${({ theme }) => theme.fonts.body};
Expand Down Expand Up @@ -112,6 +156,7 @@ interface StyledInputProps {
hasLabel: boolean
leftIcon: boolean
rightIcon: boolean
inlineEndOffsetRem: number
}

const StyledInput = styled.input<StyledInputProps>`
Expand All @@ -124,11 +169,12 @@ const StyledInput = styled.input<StyledInputProps>`
inline-size: 100%;
display: block;
padding-block: ${({ theme }) => 1.5 * theme.spacingBase}rem;
padding-inline: ${({ theme }) => 2 * theme.spacingBase}rem;
padding-inline-start: ${({ theme }) => 2 * theme.spacingBase}rem;
padding-inline-end: ${({ inlineEndOffsetRem, theme }) =>
inlineEndOffsetRem + 2 * theme.spacingBase}rem;
border-radius: ${({ hasLabel, theme }) =>
hasLabel ? theme.borderRadii.sm : theme.borderRadii.xl};
${({ leftIcon, theme }) => leftIcon && `padding-inline-start: ${4.5 * theme.spacingBase}rem`};
${({ rightIcon, theme }) => rightIcon && `padding-inline-end: ${6 * theme.spacingBase}rem`};
&:read-only {
padding-inline-start: 0;
Expand Down Expand Up @@ -160,6 +206,16 @@ const StyledInput = styled.input<StyledInputProps>`
}
`

const Suffix = styled.div<{ rightIcon: boolean }>`
position: absolute;
inset-block: 0;
inset-inline-end: ${({ rightIcon, theme }) => (rightIcon ? 5 : 2) * theme.spacingBase}rem;
color: ${({ theme }) => theme.colors.content.secondary};
display: flex;
align-items: center;
pointer-events: none;
`

const getMessageColor = (status: Status | undefined): ContentColor => {
switch (status) {
case "success":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,26 @@ export const SuccessMessage = {
},
} satisfies Story

export const Suffix = {
args: {
...Basic.args,
suffix: <>kg</>,
},
play: async ({ canvasElement, step }) => {
const canvas = within(canvasElement)
const input = canvas.getByRole("textbox", { name: Suffix.args.label })
const suffix = canvas.getByText(Suffix.args.suffix.props.children)

await step("Except no input value initially", async () => {
expect(input).toHaveValue("")
})

await step("Expect suffix to show", async () => {
expect(suffix).toBeInTheDocument()
})
},
} satisfies Story

export const ErrorMessage = {
args: {
...Basic.args,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ interface TextInputBaseProps extends ComponentPropsWithoutRef<"input"> {
/** Status of the input, controlling color and icon. */
status?: Status | undefined

/** Suffix shown after input value. For example `kg`. */
suffix?: ReactNode

/** Controlled input value. */
value?: string

Expand All @@ -41,13 +44,14 @@ export type TextInputProps = TextInputBaseProps &
(TextInputWithLabelProps | TextInputWithoutLabelProps)

export const TextInput = forwardRef<HTMLInputElement, TextInputProps>(
({ message, status, ...props }, ref) => {
({ message, suffix, status, ...props }, ref) => {
return (
<BaseInput
{...props}
message={message}
rightIcon={getStatusIcon(status)}
status={status}
suffix={suffix}
ref={ref}
/>
)
Expand Down

0 comments on commit 90eccf4

Please sign in to comment.