Skip to content

Commit

Permalink
saving validation picker
Browse files Browse the repository at this point in the history
add validation to picker + dropdown button

PR comments
  • Loading branch information
eunicekokor committed May 17, 2023
1 parent af65a0a commit 5c4be73
Show file tree
Hide file tree
Showing 7 changed files with 180 additions and 28 deletions.
58 changes: 53 additions & 5 deletions src/dropdown/DropdownButton.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
import React, { ReactNode, CSSProperties } from 'react';
import { css } from '@emotion/react';
import { css, keyframes } from '@emotion/react';
import { classNames } from '../utils/classNames';
import { mergeProps } from '@react-aria/utils';
import { useButton } from '@react-aria/button';
import { useHover } from '@react-aria/interactions';
import { FocusableRef } from '../types';
import { FocusableRef, Validation } from '../types';
import { useFocusableRef } from '../utils/useDOMRef';
import theme from '../theme';
import { AddonBefore } from '../field';
import { Icon, ArrowIosDownwardOutline } from '../icon';
import { Icon, ArrowIosDownwardOutline, AlertCircleOutline } from '../icon';
import { AddonableProps } from '../types';
import { FocusRing } from '@react-aria/focus';
import { textSizeCSS } from '../content/styles';

export interface DropdownButtonProps extends AddonableProps {
const appearKeyframes = keyframes`
0% { opacity: 0; }
100% { opacity: 1; }
`;
export interface DropdownButtonProps extends AddonableProps, Validation {
/**
* Whether the button should be displayed with a quiet style.
* @default false
Expand Down Expand Up @@ -49,7 +53,7 @@ const buttonBaseCSS = css`
overflow: hidden;
color: var(--ac-field-text-color-override, ${theme.textColors.white90});
}
.ac-icon-wrap {
.ac-icon-wrap:not(.ac-dropdown-button__validation-icon) {
margin: 10px 0 10px 10px;
flex: fixed;
width: 16px;
Expand All @@ -59,6 +63,15 @@ const buttonBaseCSS = css`
&[disabled] {
opacity: ${theme.opacity.disabled};
}
.ac-dropdown-button__validation-icon {
/* Animate in the icon */
animation: ${appearKeyframes} ${0.2}s forwards ease-in-out;
top: ${theme.spacing.padding8}px;
right: 0;
&.ac-dropdown-button__validation-icon--invalid {
color: var(--ac-global-color-danger);
}
}
`;

/**
Expand Down Expand Up @@ -86,6 +99,17 @@ const quietButtonCSS = css`
cursor: default;
border-bottom: 1px solid ${theme.components.dropdown.borderColor};
}
&.ac-dropdown-button--invalid {
border-color: var(--ac-global-color-danger);
div.ac-dropdown__content {
color: var(--ac-global-color-danger);
}
}
// Make room for the invalid icon
&.ac-dropdown-button--invalid > div.ac-dropdown__content {
padding-right: ${theme.spacing.padding24}px;
}
`;

/**
Expand Down Expand Up @@ -119,6 +143,18 @@ const nonQuietButtonCSS = css`
margin: ${theme.spacing.margin8}px ${theme.spacing.margin8}px
${theme.spacing.margin8}px ${theme.spacing.margin16}px;
}
&.ac-dropdown-button--invalid {
border: 1px solid var(--ac-global-color-danger);
div.ac-dropdown__content {
color: var(--ac-global-color-danger);
}
}
// Make room for the invalid icon (outer padding + icon width + inner padding)
&.ac-dropdown-button--invalid > div.ac-dropdown__content {
padding-right: ${theme.spacing.padding8 + 24 + theme.spacing.padding4}px;
}
`;

/**
Expand All @@ -139,13 +175,22 @@ function DropdownButton(
children,
style,
addonBefore,
validationState,
// TODO: add support for autoFocus
// autoFocus,
...otherProps
} = props;
const { buttonProps, isPressed } = useButton(props, domRef);
const { hoverProps, isHovered } = useHover({ isDisabled });
const isInvalid = validationState === 'invalid';

const validation = (
<Icon
key="validation-icon"
className={`ac-dropdown-button__validation-icon ac-dropdown-button__validation-icon--${validationState}`}
svg={<AlertCircleOutline />}
/>
);
return (
<FocusRing focusRingClass="focus-ring">
<button
Expand All @@ -156,6 +201,8 @@ function DropdownButton(
'is-active': isActive || isPressed,
'is-disabled': isDisabled,
'is-hovered': isHovered,
'ac-dropdown-button--invalid': isInvalid,
'ac-dropdown-button--valid': validationState === 'valid',
})}
style={style}
css={css(
Expand All @@ -176,6 +223,7 @@ function DropdownButton(
>
{children}
</span>
{validationState && validationState === 'invalid' ? validation : null}
<Icon svg={<ArrowIosDownwardOutline />} />
</button>
</FocusRing>
Expand Down
2 changes: 1 addition & 1 deletion src/field/HelpText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ function HelpText(props: HelpTextComponentProps, ref: DOMRef<HTMLDivElement>) {
padding: ${theme.spacing.padding4}px 0 0;
color: ${theme.textColors.white50};
&.ac-help-text--danger {
color: ${theme.colors.statusDanger};
color: var(--ac-global-color-danger);
}
.ac-help-text__text {
font-size: ${theme.typography.sizes.small.fontSize}px;
Expand Down
3 changes: 3 additions & 0 deletions src/picker/Picker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ function Picker<T extends object>(
autoFocus,
addonBefore,
menuWidth,
validationState,
} = props;

let state = useSelectState(props);
Expand Down Expand Up @@ -179,6 +180,7 @@ function Picker<T extends object>(
<span
className={classNames({
'is-placeholder': state.selectedItem == null,
'ac-dropdown__content': true,
})}
>
{contents}
Expand Down Expand Up @@ -225,6 +227,7 @@ function Picker<T extends object>(
className={classNames('ac-dropdown-trigger', {
'is-hovered': isHovered,
})}
validationState={validationState}
{...{ addonBefore }}
>
<div>{contents}</div>
Expand Down
59 changes: 59 additions & 0 deletions src/provider/GlobalStyles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,65 @@ export const mediumRootCSS = css`
`;

export const globalCSS = css`
:root {
--ac-global-dimension-static-size-0: 0px;
--ac-global-dimension-static-size-10: 1px;
--ac-global-dimension-static-size-25: 2px;
--ac-global-dimension-static-size-50: 4px;
--ac-global-dimension-static-size-40: 3px;
--ac-global-dimension-static-size-65: 5px;
--ac-global-dimension-static-size-100: 8px;
--ac-global-dimension-static-size-115: 9px;
--ac-global-dimension-static-size-125: 10px;
--ac-global-dimension-static-size-130: 11px;
--ac-global-dimension-static-size-150: 12px;
--ac-global-dimension-static-size-160: 13px;
--ac-global-dimension-static-size-175: 14px;
--ac-global-dimension-static-size-200: 16px;
--ac-global-dimension-static-size-225: 18px;
--ac-global-dimension-static-size-250: 20px;
--ac-global-dimension-static-size-300: 24px;
--ac-global-dimension-static-size-400: 32px;
--ac-global-dimension-static-size-450: 36px;
--ac-global-dimension-static-size-500: 40px;
--ac-global-dimension-static-size-550: 44px;
--ac-global-dimension-static-size-600: 48px;
--ac-global-dimension-static-size-700: 56px;
--ac-global-dimension-static-size-800: 64px;
--ac-global-dimension-static-size-900: 72px;
--ac-global-dimension-static-size-1000: 80px;
--ac-global-dimension-static-size-1200: 96px;
--ac-global-dimension-static-size-1700: 136px;
--ac-global-dimension-static-size-2400: 192px;
--ac-global-dimension-static-size-2600: 208px;
--ac-global-dimension-static-size-3400: 272px;
--ac-global-dimension-static-size-3600: 288px;
--ac-global-dimension-static-size-4600: 368px;
--ac-global-dimension-static-size-5000: 400px;
--ac-global-dimension-static-size-6000: 480px;
--ac-global-dimension-static-font-size-50: 11px;
--ac-global-dimension-static-font-size-75: 12px;
--ac-global-dimension-static-font-size-100: 14px;
--ac-global-dimension-static-font-size-150: 15px;
--ac-global-dimension-static-font-size-200: 16px;
--ac-global-dimension-static-font-size-300: 18px;
--ac-global-dimension-static-font-size-400: 20px;
--ac-global-dimension-static-font-size-500: 22px;
--ac-global-dimension-static-font-size-600: 25px;
--ac-global-dimension-static-font-size-700: 28px;
--ac-global-dimension-static-font-size-800: 32px;
--ac-global-dimension-static-font-size-900: 36px;
--ac-global-dimension-static-font-size-1000: 40px;
--ac-global-dimension-static-percent-50: 50%;
--ac-global-dimension-static-percent-100: 100%;
--ac-global-dimension-static-breakpoint-xsmall: 304px;
--ac-global-dimension-static-breakpoint-small: 768px;
--ac-global-dimension-static-breakpoint-medium: 1280px;
--ac-global-dimension-static-breakpoint-large: 1768px;
--ac-global-dimension-static-breakpoint-xlarge: 2160px;
--ac-global-dimension-static-grid-columns: 12;
--ac-global-dimension-static-grid-fluid-width: 100%;
--ac-global-dimension-static-grid-fixed-max-width: 1280px;
:root {
--ac-global-dimension-static-size-0: 0px;
--ac-global-dimension-static-size-10: 1px;
Expand Down
26 changes: 14 additions & 12 deletions src/textfield/TextFieldBase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,11 @@ const textFieldBaseCSS = (styleProps: StyleProps) => css`
flex-direction: row;
position: relative;
align-items: center;
min-width: ${styleProps.width
? dimensionValue(styleProps.width)
: dimensionValue('static-size-3400')};
min-width: ${
styleProps.width
? dimensionValue(styleProps.width)
: dimensionValue('static-size-3400')
};
width: ${styleProps.width ? dimensionValue(styleProps.width) : '100%'};
transition: all 0.2s ease-in-out;
Expand Down Expand Up @@ -185,12 +187,12 @@ const quietTextfieldBaseCSS = css`
opacity: ${theme.opacity.disabled};
}
&.ac-textfield--invalid:not(.is-disabled) {
border-bottom: 1px solid ${theme.colors.statusDanger};
border-bottom: 1px solid var(--ac-global-color-danger);
}
&.ac-textfield--invalid.ac-textfield__input {
// Make room for the invalid icon
padding-right: 24px;
color: ${theme.colors.statusDanger};
color: var(--ac-global-color-danger);
}
.ac-textfield__validation-icon {
Expand All @@ -200,7 +202,7 @@ const quietTextfieldBaseCSS = css`
right: 0;
position: absolute;
&.ac-textfield__validation-icon--invalid {
color: ${theme.colors.statusDanger};
color: var(--ac-global-color-danger);
}
}
`;
Expand All @@ -222,9 +224,9 @@ const standardTextfieldBaseCSS = css`
border: 1px solid ${theme.components.textField.activeBorderColor};
background-color: ${theme.components.textField.activeBackgroundColor};
&.ac-textfield--invalid {
border: 1px solid ${theme.colors.statusDanger};
border: 1px solid var(--ac-global-color-danger);
.ac-textfield__input {
color: ${theme.colors.statusDanger};
color: var(--ac-global-color-danger);
}
}
}
Expand All @@ -241,16 +243,16 @@ const standardTextfieldBaseCSS = css`
}
&.ac-textfield--invalid:not(.is-disabled) {
border: 1px solid ${theme.colors.statusDanger};
border: 1px solid var(--ac-global-color-danger);
.ac-textfield__input {
color: ${theme.colors.statusDanger};
color: var(--ac-global-color-danger);
}
}
&.ac-textfield--invalid .ac-textfield__input {
// Make room for the invalid icon (outer padding + icon width + inner padding)
padding-right: ${theme.spacing.padding8 + 24 + theme.spacing.padding4}px;
color: ${theme.colors.statusDanger};
color: var(--ac-global-color-danger);
}
.ac-textfield__validation-icon {
Expand All @@ -260,7 +262,7 @@ const standardTextfieldBaseCSS = css`
right: ${theme.spacing.padding8}px;
position: absolute;
&.ac-textfield__validation-icon--invalid {
color: ${theme.colors.statusDanger};
color: var(--ac-global-color-danger);
}
}
`;
Expand Down
10 changes: 7 additions & 3 deletions src/types/select.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { LabelableProps, Alignment, AddonableProps } from './labelable';
import { CollectionBase } from './collection';
import { InputBase, TextInputBase } from './input';
import { HelpTextProps, InputBase, TextInputBase } from './input';
import { AriaLabelingProps, DOMProps, FocusableDOMProps } from './dom';
import { SingleSelection } from './selection';
import { FocusableProps } from './events';
import { StyleProps } from './style';
import { DimensionValue } from './core';
import { Validation, AriaValidationProps } from '../types';

export interface SelectProps<T>
extends CollectionBase<T>,
Expand All @@ -31,7 +32,8 @@ export interface AriaSelectProps<T>
extends SelectProps<T>,
DOMProps,
AriaLabelingProps,
FocusableDOMProps {
FocusableDOMProps,
AriaValidationProps {
/**
* Describes the type of autocomplete functionality the input should provide if any. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#htmlattrdefautocomplete).
*/
Expand All @@ -45,7 +47,9 @@ export interface AriaSelectProps<T>
export interface PickerProps<T>
extends AriaSelectProps<T>,
AddonableProps,
StyleProps {
StyleProps,
Validation,
HelpTextProps {
/**
* Whether the picker should be displayed with a quiet style.
* @default false
Expand Down
Loading

0 comments on commit 5c4be73

Please sign in to comment.