Skip to content
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
28 changes: 28 additions & 0 deletions .changeset/polite-apes-create.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
'@commercetools-uikit/date-range-input': minor
'@commercetools-uikit/date-time-input': minor
'@commercetools-uikit/date-input': minor
'@commercetools-uikit/calendar-utils': minor
---

feat: add `appearance` prop with 'filter' option to date input components

To use the date filters, there are some visual modifications that need to happen in the different date inputs to support the designs and ux of the filters pattern. Most of these changes are dependent on new props to set these options when the component is used in a filter component.

Add support for `appearance: 'filter'` to DateInput, DateTimeInput, and DateRangeInput components. When set to 'filter', the components:

- Remove borders and box shadows for a clean, inline appearance
- Keep the calendar always open (when not disabled or read-only)
- Maintain transparent backgrounds to blend seamlessly with filter UIs

This follows the same design pattern established in select input components and enables date inputs to be used effectively within filter components and search interfaces.

**New Props:**
- `appearance?: 'default' | 'filter'` - Controls the visual styling of the date input

**Examples:**
```jsx
<DateInput appearance="filter" value="2024-01-15" />
<DateTimeInput appearance="filter" value="2024-01-15T10:30:00Z" />
<DateRangeInput appearance="filter" value={['2024-01-15', '2024-01-20']} />
```
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,13 @@ const getClearSectionStyles = () => {
};

type TState = {
isFocused: boolean;
isFocused?: boolean;
};

const getIconBorderColor = (props: TCalendarBody, state: TState) => {
if (props.appearance === 'filter') {
return designTokens.colorTransparent;
}
if (props.isDisabled) {
return designTokens.borderColorForInputWhenDisabled;
}
Expand Down Expand Up @@ -89,7 +92,9 @@ const getCalendarIconContainerStyles = (
&:active,
&:hover:not(:disabled)&:not(:read-only),
&:focus {
border-color: ${designTokens.borderColorForInputWhenFocused};
border-color: ${props.appearance === 'filter'
? designTokens.colorTransparent
: designTokens.borderColorForInputWhenFocused};
}
`,
];
Expand All @@ -108,7 +113,7 @@ const getInputBorderColor = (props: TCalendarBody, state: TState) => {
if (props.isReadOnly) {
return designTokens.borderColorForInputWhenReadonly;
}
if ((props.isOpen || state.isFocused) && !props.isReadOnly) {
if (props.isOpen || state.isFocused) {
return designTokens.borderColorForInputWhenFocused;
}
return designTokens.borderColorForInput;
Expand All @@ -131,6 +136,9 @@ const getInputFontColor = (props: TCalendarBody) => {
};

const getInputContainerBackgroundColor = (props: TCalendarBody) => {
if (props.appearance === 'filter') {
return designTokens.colorTransparent;
}
if (props.isDisabled) {
return designTokens.backgroundColorForInputWhenDisabled;
}
Expand Down Expand Up @@ -164,7 +172,9 @@ const getInputContainerStyles = (props: TCalendarBody, state: TState) => {

&:hover:not(:focus) {
background-color: ${!props.isDisabled && !props.isReadOnly
? designTokens.backgroundColorForInputWhenHovered
? props.appearance === 'filter'
? designTokens.colorTransparent
: designTokens.backgroundColorForInputWhenHovered
: null};
}
&:focus {
Expand All @@ -174,10 +184,13 @@ const getInputContainerStyles = (props: TCalendarBody, state: TState) => {
props.isReadOnly ||
((props.isOpen || state.isFocused) && !props.isReadOnly)
? ''
: props.appearance === 'filter'
? designTokens.colorTransparent
: designTokens.borderColorForInputWhenFocused};
}
`,
!props.isReadOnly &&
props.appearance !== 'filter' &&
css`
&:focus-within {
border-color: ${designTokens.borderColorForInputWhenFocused};
Expand All @@ -189,6 +202,7 @@ const getInputContainerStyles = (props: TCalendarBody, state: TState) => {
}
`,
(props.hasError || props.hasWarning) &&
props.appearance !== 'filter' &&
css`
box-shadow: inset 0 0 0 1px;
`,
Expand Down
78 changes: 44 additions & 34 deletions packages/calendar-utils/src/calendar-body/calendar-body.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ export type TCalendarBody = {
placeholder?: string;
/** @deprecated */
theme?: Theme;
/**
* Indicates the appearance of the input.
* Filter appearance removes borders and box shadows for use in filter components.
*/
appearance?: 'default' | 'filter';
};

export const CalendarBody = ({
Expand Down Expand Up @@ -151,42 +156,47 @@ export const CalendarBody = ({
onBlur={handleInputBlur}
aria-readonly={props.isReadOnly}
/>
{!disabledOrReadOnly && props.hasSelection && isClearable && (
<ClearSection
isCondensed={props.isCondensed}
hasError={props.hasError}
hasWarning={props.hasWarning}
isFocused={isFocused}
isOpen={props.isOpen}
onClear={props.onClear}
/>
)}
<button
type="button"
css={getCalendarIconContainerStyles(
{
isClearable,
...props,
},
{ isFocused }
)}
{...props.toggleButtonProps}
onFocus={handleToggleFocus}
onBlur={handleToggleBlur}
disabled={disabledOrReadOnly}
onKeyDown={props.inputProps?.onKeyDown}
/* keyboard users don't need this button */
tabIndex={-1}
>
{props.icon === 'clock' ? (
<ClockIcon color="neutral60" />
) : (
<CalendarIcon
color="neutral60"
size={props.isCondensed ? 'medium' : 'big'}
{!disabledOrReadOnly &&
props.hasSelection &&
isClearable &&
props.appearance !== 'filter' && (
<ClearSection
isCondensed={props.isCondensed}
hasError={props.hasError}
hasWarning={props.hasWarning}
isFocused={isFocused}
isOpen={props.isOpen}
onClear={props.onClear}
/>
)}
</button>
{props.appearance !== 'filter' && (
<button
type="button"
css={getCalendarIconContainerStyles(
{
isClearable,
...props,
},
{ isFocused }
)}
{...props.toggleButtonProps}
onFocus={handleToggleFocus}
onBlur={handleToggleBlur}
disabled={disabledOrReadOnly}
onKeyDown={props.inputProps?.onKeyDown}
/* keyboard users don't need this button */
tabIndex={-1}
>
{props.icon === 'clock' ? (
<ClockIcon color="neutral60" />
) : (
<CalendarIcon
color="neutral60"
size={props.isCondensed ? 'medium' : 'big'}
/>
)}
</button>
)}
</div>
</Inline>
);
Expand Down
23 changes: 19 additions & 4 deletions packages/calendar-utils/src/calendar-menu/calendar-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,23 @@ type TCalendarMenu = {
hasError?: boolean;
hasWarning?: boolean;
footer?: ReactNode;
/**
* Indicates the appearance of the calendar menu.
* Filter appearance removes box shadows and positioning for inline display.
*/
appearance?: 'default' | 'filter';
};

export default class CalendarMenu extends Component<TCalendarMenu> {
static displayName = 'CalendarMenu';
render() {
const { hasFooter, hasWarning, hasError, ...rest } = this.props;
const {
hasFooter,
hasWarning,
hasError,
appearance = 'default',
...rest
} = this.props;

return (
<div
Expand All @@ -24,16 +35,20 @@ export default class CalendarMenu extends Component<TCalendarMenu> {
color: ${designTokens.colorSolid};
font-family: inherit;
border: none;
box-shadow: 0 2px 5px 0px rgba(0, 0, 0, 0.15);
box-shadow: ${appearance === 'filter'
? 'none'
: '0 2px 5px 0px rgba(0, 0, 0, 0.15)'};
border-radius: ${designTokens.borderRadiusForInput};
margin-top: ${designTokens.spacing10};
font-size: ${designTokens.fontSize30};
position: absolute;
position: ${appearance === 'filter' ? 'inherit' : 'absolute'};
box-sizing: border-box;
width: 100%;
background-color: ${designTokens.colorSurface};
min-width: ${designTokens.constraint5};
z-index: 99999; /* copied from flatpickr */
z-index: ${appearance === 'filter'
? 'inherit'
: '99999'}; /* copied from flatpickr */
`,
!hasFooter &&
css`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ export const menuStyles = css`
flex-direction: column;
align-items: flex-start;
gap: ${designTokens.spacing30};
width: ${designTokens.constraint6};
width: ${designTokens.constraint7};
max-height: ${designTokens.constraint10};
padding: ${designTokens.spacing20} ${designTokens.spacing30};
background-color: ${designTokens.colorSurface};
Expand Down
Loading
Loading