Skip to content
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

Add error styling updates #58

Merged
merged 5 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Changelog

All notable changes to this project will be documented in this file.

## [v1.10.7] - 2024-10-22

- Create custom styled checkbox
- Update primary and light checkbox styles
- Update color for checkbox label
- Add disabled state style
- Add error state style
- Add error prop to checkbox
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@openstax/ui-components",
"version": "1.10.6",
"version": "1.10.7",
"license": "MIT",
"source": "./src/index.ts",
"types": "./dist/index.d.ts",
Expand Down
79 changes: 63 additions & 16 deletions src/components/Checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,84 @@ import { LabelHTMLAttributes, PropsWithChildren } from "react";
import { colors } from "../theme";
import styled from "styled-components";
import { InputHTMLAttributes } from "react";
import { whiteCheckmark, grayCheckmark, redCheckmark } from "./svgs/checkmarksvgs";

type CheckboxVariant = keyof typeof checkboxVariants;
type CheckboxSize = 1.4 | 1.6 | 1.8 | 2;
export type CheckboxVariant = keyof typeof checkboxVariants;
export type CheckboxSize = 1.4 | 1.6 | 1.8 | 2;

export const checkboxVariants = {
primary: {
accentColor: colors.palette.mediumBlue,
boxShadow: 'none',
backgroundColor: colors.palette.mediumBlue,
color: 'inherit',
unCheckedBorder: `1px solid ${colors.palette.neutralThin}`,
checkedBorder: `1px solid ${colors.palette.mediumBlue}`,
backgroundImage: whiteCheckmark
},
light: {
accentColor: colors.palette.white,
boxShadow: '0 0 1px 0',
backgroundColor: colors.palette.white,
color: 'inherit',
unCheckedBorder: `1px solid ${colors.palette.pale}`,
checkedBorder: `1px solid ${colors.palette.pale}`,
backgroundImage: grayCheckmark
},
error: {
backgroundColor: colors.palette.paleRed,
color: colors.palette.darkRed,
unCheckedBorder: `1px solid ${colors.palette.lightRed}`,
checkedBorder: `1px solid ${colors.palette.lightRed}`,
backgroundImage: redCheckmark
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would you mind creating an icons file somewhere (unless it already exists) and importing the svgs as named icons

disabled: {
backgroundColor: colors.palette.white,
color: 'inherit',
unCheckedBorder: `1px solid ${colors.palette.pale}`,
checkedBorder: `1px solid ${colors.palette.pale}`,
backgroundImage: 'none'
}
} as const;

const StyledLabel = styled.label<{ bold: boolean; }>`
const StyledLabel = styled.label<{ bold: boolean; variant: CheckboxVariant; isDisabled?: boolean; }>`
font-size: 1.4rem;
display: flex;
align-items: center;
font-weight: ${props => props.bold ? 700 : 400}
font-weight: ${props => props.bold ? 700 : 400};
color: ${(props => props.isDisabled ? colors.palette.neutralLight : checkboxVariants[props.variant].color)};
`;

const StyledInput = styled.input<{ variant: CheckboxVariant; checkboxSize: CheckboxSize; }>`
accent-color: ${props => checkboxVariants[props.variant].accentColor};
// https://moderncss.dev/pure-css-custom-checkbox-style/
const StyledInput = styled.input<{ variant: CheckboxVariant; checkboxSize: CheckboxSize; isDisabled?: boolean; }>`
appearance: none;
/* For iOS < 15 to remove gradient background */
background-color: ${colors.palette.white};
opacity: ${(props => props.isDisabled ? '0.4' : '1')};
border: ${props => props.isDisabled ? `1px solid ${colors.palette.pale}` : checkboxVariants[props.variant].unCheckedBorder};
border-radius: 0.2rem;
transform: translateY(-0.075em);
width: ${props => props.checkboxSize}rem;
height: ${props => props.checkboxSize}rem;
margin: 0 1.6rem 0 0;
&:checked {
box-shadow: ${props => checkboxVariants[props.variant].boxShadow};
display: grid;
place-content: center;

&::before {
content: "";
width: ${props => props.checkboxSize}rem;
height: ${props => props.checkboxSize}rem;
border: ${props => checkboxVariants[props.variant].checkedBorder};
border-radius: 0.2rem;
transform: scale(0);
background-color: ${props => checkboxVariants[props.variant].backgroundColor};
background-image: url('${props => checkboxVariants[props.variant].backgroundImage}');
background-size: 80%;
background-position: center;
background-repeat: no-repeat;
}
`;

&:checked::before {
transform: scale(1);
opacity: ${(props => props.isDisabled ? 0 : 1)};
}
`;

type CheckboxProps = PropsWithChildren<
Omit<InputHTMLAttributes<HTMLInputElement>, 'type'> & {
Expand All @@ -42,10 +89,10 @@ type CheckboxProps = PropsWithChildren<
labelProps?: LabelHTMLAttributes<HTMLLabelElement>;
}>;

export const Checkbox = ({ children, variant = 'primary', bold = false, size = 1.6, labelProps, ...props }: CheckboxProps) => {
export const Checkbox = ({ children, disabled, variant = 'primary', bold = false, size = 1.6, labelProps, ...props }: CheckboxProps) => {
return (
<StyledLabel bold={bold} {...labelProps}>
<StyledInput {...props} type="checkbox" variant={variant} checkboxSize={size} />
<StyledLabel bold={bold} variant={variant} isDisabled={disabled} {...labelProps}>
<StyledInput {...props} type="checkbox" variant={variant} checkboxSize={size} isDisabled={disabled} />
{children}
</StyledLabel>
);
Expand Down
12 changes: 6 additions & 6 deletions src/components/__snapshots__/Checkbox.spec.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
exports[`Checkbox allows setting props on label 1`] = `
<label
aria-label="custom label"
className="sc-bczRLJ bYTfrb"
className="sc-bczRLJ brUlbM"
>
<input
className="sc-gsnTZi hvmWuC"
className="sc-gsnTZi gDsstp"
type="checkbox"
/>
Click Me
Expand All @@ -15,10 +15,10 @@ exports[`Checkbox allows setting props on label 1`] = `

exports[`Checkbox handles options 1`] = `
<label
className="sc-bczRLJ bYTfrb"
className="sc-bczRLJ brUlbM"
>
<input
className="sc-gsnTZi hvmWuC"
className="sc-gsnTZi gDsstp"
type="checkbox"
/>
Click Me
Expand All @@ -27,10 +27,10 @@ exports[`Checkbox handles options 1`] = `

exports[`Checkbox matches snapshot 1`] = `
<label
className="sc-bczRLJ bYTeUU"
className="sc-bczRLJ gtxOzv"
>
<input
className="sc-gsnTZi ljReaQ"
className="sc-gsnTZi bBmYov"
type="checkbox"
/>
Click Me
Expand Down
42 changes: 42 additions & 0 deletions src/components/forms/uncontrolled/inputType.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import styled from "styled-components";
import { Checkbox } from "./inputTypes";


const CheckboxGroup = styled.div`
& + & {
margin-top: 3.2rem;
}
> * + * {
margin-top: 0.5rem;
}
`;

type CheckboxProps = React.ComponentProps<typeof Checkbox>;
const renderCheckboxes = (props: CheckboxProps) =>
<CheckboxGroup>
<h2>Size {props.size}</h2>
<Checkbox {...props} defaultChecked></Checkbox>
<Checkbox {...props} defaultChecked></Checkbox>
<Checkbox {...props} defaultChecked></Checkbox>
</CheckboxGroup>;

export const defaultCheckbox = () => <>
{renderCheckboxes({error: [], label: 'Checkbox Label', variant: 'primary', size: 1.4})}
{renderCheckboxes({error: [], label: 'Checkbox Label', variant: 'primary', size: 1.6})}
{renderCheckboxes({error: undefined, label: 'Checkbox Label', variant: 'primary', size: 1.8})}
{renderCheckboxes({error: undefined, label: 'Checkbox Label', variant: 'primary', size: 2.0})}
</>;

export const errorCheckbox = () => <>
{renderCheckboxes({error: ['Error messages go here'], label: 'Checkbox Label', variant: 'error', size: 1.4})}
{renderCheckboxes({error: ['Error messages go here'], label: 'Checkbox Label', variant: 'error', size: 1.6})}
{renderCheckboxes({error: ['Error messages go here'], label: 'Checkbox Label', variant: 'error', size: 1.8})}
{renderCheckboxes({error: ['Error messages go here'], label: 'Checkbox Label', variant: 'error', size: 2.0})}
</>;

export const disabledCheckbox = () => <>
{renderCheckboxes({error: [], disabled: true, label: 'Checkbox Label', variant: 'disabled', size: 1.4})}
{renderCheckboxes({error: [], disabled: true, label: 'Checkbox Label', variant: 'disabled', size: 1.6})}
{renderCheckboxes({error: [], disabled: true, label: 'Checkbox Label', variant: 'disabled', size: 1.8})}
{renderCheckboxes({error: [], disabled: true, label: 'Checkbox Label', variant: 'disabled', size: 2.0})}
</>;
23 changes: 20 additions & 3 deletions src/components/forms/uncontrolled/inputTypes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { FormInputWrapper, FormLabelText, HelpText, InputProps, RequiredIndicato
import { AbstractFormData } from "../controlled/hooks";
import { partitionSequence } from "@openstax/ts-utils/misc/partitionSequence";
import { Radio as StyledRadio } from "../../Radio";
import { Checkbox as StyledCheckbox, CheckboxSize, CheckboxVariant} from "../../Checkbox"

/*
* input element
Expand Down Expand Up @@ -195,28 +196,44 @@ export const Radio = ({
type CheckboxProps = React.ComponentPropsWithoutRef<'input'> & InputProps & {
onChangeValue?: (value: boolean | undefined) => void;
wrapperProps?: React.ComponentPropsWithoutRef<'label'>;
error?: string[];
size?: CheckboxSize;
variant?: CheckboxVariant;
};
const CheckboxLine = styled.div`
flex-direction: row;
display: flex;
align-items: center;
`;
const StyledErrorMessage = styled.p`
color: #C22032;
font-size: 1.4rem;
margin: 0;
padding: 0;
line-height: 2.5rem;
`
export const Checkbox = ({
label,
help,
wrapperProps,
error,
onChangeValue,
...props
}: CheckboxProps) => {
return <FormInputWrapper {...wrapperProps}>
<CheckboxLine>
<input type="checkbox" {...props} onChange={e => {
<StyledCheckbox {...props} onChange={e => {
onChangeValue?.(!!e.target.checked);
props.onChange?.(e);
}}/>
<FormLabelText><RequiredIndicator show={props.required} />{label}</FormLabelText>
}}
>
<FormLabelText><RequiredIndicator show={props.required} />{label}</FormLabelText>
</StyledCheckbox>
</CheckboxLine>
<HelpText value={help} />
{error !== undefined && error.length > 0 &&
<StyledErrorMessage>This activity has been deleted. Please deselect this activity to remove it from this assignment and avoid errors.</StyledErrorMessage>
}
</FormInputWrapper>;
};

Expand Down
5 changes: 5 additions & 0 deletions src/components/svgs/checkmarksvgs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const whiteCheckmark = 'data:image/svg+xml,<svg height="125px" width="125px" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 17.837 17.837" xml:space="preserve" fill="%23000000"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"><g><path style="fill:%23fff;" d="M16.145,2.571c-0.272-0.273-0.718-0.273-0.99,0L6.92,10.804l-4.241-4.27 c-0.272-0.274-0.715-0.274-0.989,0L0.204,8.019c-0.272,0.271-0.272,0.717,0,0.99l6.217,6.258c0.272,0.271,0.715,0.271,0.99,0 L17.63,5.047c0.276-0.273,0.276-0.72,0-0.994L16.145,2.571z"></path></g></g></svg>'

export const grayCheckmark = 'data:image/svg+xml,<svg height="125px" width="125px" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 17.837 17.837" xml:space="preserve" fill="%23000000"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"><g><path style="fill:%235e5e5e;" d="M16.145,2.571c-0.272-0.273-0.718-0.273-0.99,0L6.92,10.804l-4.241-4.27 c-0.272-0.274-0.715-0.274-0.989,0L0.204,8.019c-0.272,0.271-0.272,0.717,0,0.99l6.217,6.258c0.272,0.271,0.715,0.271,0.99,0 L17.63,5.047c0.276-0.273,0.276-0.72,0-0.994L16.145,2.571z"></path></g></g></svg>'

export const redCheckmark = 'data:image/svg+xml,<svg height="125px" width="125px" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 17.837 17.837" xml:space="preserve" fill="%23000000"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"><g><path style="fill:%23C22032;" d="M16.145,2.571c-0.272-0.273-0.718-0.273-0.99,0L6.92,10.804l-4.241-4.27 c-0.272-0.274-0.715-0.274-0.989,0L0.204,8.019c-0.272,0.271-0.272,0.717,0,0.99l6.217,6.258c0.272,0.271,0.715,0.271,0.99,0 L17.63,5.047c0.276-0.273,0.276-0.72,0-0.994L16.145,2.571z"></path></g></g></svg>'
Loading