Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -670,7 +670,7 @@ A placeholder date that influences the format of the placeholder shown when no v

> `optional` **short**: `boolean`

Defined in: [packages/framework/esm-styleguide/src/datepicker/openmrs-date-picker.component.tsx:32](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-styleguide/src/datepicker/openmrs-date-picker.component.tsx#L32)
Defined in: [packages/framework/esm-styleguide/src/datepicker/openmrs-date-picker.component.tsx:36](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-styleguide/src/datepicker/openmrs-date-picker.component.tsx#L36)

'true' to use the short version.

Expand Down Expand Up @@ -715,9 +715,11 @@ By default, this is determined by the user's locale.

> `optional` **size**: `"sm"` \| `"md"` \| `"lg"`

Defined in: [packages/framework/esm-styleguide/src/datepicker/openmrs-date-picker.component.tsx:30](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-styleguide/src/datepicker/openmrs-date-picker.component.tsx#L30)
Defined in: [packages/framework/esm-styleguide/src/datepicker/openmrs-date-picker.component.tsx:34](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-styleguide/src/datepicker/openmrs-date-picker.component.tsx#L34)

Specifies the size of the input. Currently supports either `sm`, `md`, or `lg` as an option
Specifies the size of the input. Currently supports either `sm`, `md`, or `lg` as an option.
When omitted, the size is taken from the surrounding Carbon layout context, falling back to
`md` when there is none.

***

Expand Down Expand Up @@ -803,6 +805,6 @@ or invalid via ARIA.

> `optional` **value**: `DateInputValue`

Defined in: [packages/framework/esm-styleguide/src/datepicker/openmrs-date-picker.component.tsx:34](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-styleguide/src/datepicker/openmrs-date-picker.component.tsx#L34)
Defined in: [packages/framework/esm-styleguide/src/datepicker/openmrs-date-picker.component.tsx:38](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-styleguide/src/datepicker/openmrs-date-picker.component.tsx#L38)

The value (controlled)
Original file line number Diff line number Diff line change
Expand Up @@ -720,9 +720,11 @@ By default, this is determined by the user's locale.

> `optional` **size**: `"sm"` \| `"md"` \| `"lg"`

Defined in: [packages/framework/esm-styleguide/src/datepicker/openmrs-date-range-picker.component.tsx:36](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-styleguide/src/datepicker/openmrs-date-range-picker.component.tsx#L36)
Defined in: [packages/framework/esm-styleguide/src/datepicker/openmrs-date-range-picker.component.tsx:40](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-styleguide/src/datepicker/openmrs-date-range-picker.component.tsx#L40)

Specifies the size of the input. Currently supports either `sm`, `md`, or `lg` as an option
Specifies the size of the input. Currently supports either `sm`, `md`, or `lg` as an option.
When omitted, the size is taken from the surrounding Carbon layout context, falling back to
`md` when there is none.

***

Expand Down Expand Up @@ -822,6 +824,6 @@ or invalid via ARIA.

> `optional` **value**: \[`DateInputValue`, `DateInputValue`\]

Defined in: [packages/framework/esm-styleguide/src/datepicker/openmrs-date-range-picker.component.tsx:38](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-styleguide/src/datepicker/openmrs-date-range-picker.component.tsx#L38)
Defined in: [packages/framework/esm-styleguide/src/datepicker/openmrs-date-range-picker.component.tsx:42](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-styleguide/src/datepicker/openmrs-date-range-picker.component.tsx#L42)

The value (controlled)
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@

> `const` **OpenmrsDatePicker**: `ForwardRefExoticComponent`\<[`OpenmrsDatePickerProps`](../interfaces/OpenmrsDatePickerProps.md) & `RefAttributes`\<`HTMLDivElement`\>\>

Defined in: [packages/framework/esm-styleguide/src/datepicker/openmrs-date-picker.component.tsx:45](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-styleguide/src/datepicker/openmrs-date-picker.component.tsx#L45)
Defined in: [packages/framework/esm-styleguide/src/datepicker/openmrs-date-picker.component.tsx:48](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-styleguide/src/datepicker/openmrs-date-picker.component.tsx#L48)

A date picker component to select a single date. Based on React Aria, but styled to look like Carbon.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@

> `const` **OpenmrsDateRangePicker**: `ForwardRefExoticComponent`\<[`OpenmrsDateRangePickerProps`](../interfaces/OpenmrsDateRangePickerProps.md) & `RefAttributes`\<`HTMLDivElement`\>\>

Defined in: [packages/framework/esm-styleguide/src/datepicker/openmrs-date-range-picker.component.tsx:44](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-styleguide/src/datepicker/openmrs-date-range-picker.component.tsx#L44)
Defined in: [packages/framework/esm-styleguide/src/datepicker/openmrs-date-range-picker.component.tsx:48](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-styleguide/src/datepicker/openmrs-date-range-picker.component.tsx#L48)

A date range picker to enter or select a date and time range. Based on React Aria, but styled to look like Carbon.
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,11 @@ export const CalendarPopover = /*#__PURE__*/ forwardRef<HTMLDivElement, Calendar
<AutoCloseDialog>
<CalendarComponent {...calendarProps}>
<header className={styles.header}>
<Button className={classNames(styles.flatButton, styles.flatButtonMd)} slot="previous">
<Button className={classNames(styles.flatButton, styles.calendarNavButton)} slot="previous">
<ChevronLeftIcon size={16} />
</Button>
<MonthYear className={styles.monthYear} />
<Button className={classNames(styles.flatButton, styles.flatButtonMd)} slot="next">
<Button className={classNames(styles.flatButton, styles.calendarNavButton)} slot="next">
<ChevronRightIcon size={16} />
</Button>
</header>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,38 +154,42 @@ describe('OpenmrsDateRangePicker', () => {
});

describe('size prop', () => {
/* eslint-disable testing-library/no-container, testing-library/no-node-access */
const getInputsWrapper = (container: HTMLElement) =>
Array.from(container.querySelectorAll('div')).find((el) => el.className.includes('inputsWrapper')) ?? null;

it('should apply md size classes by default', () => {
const { container } = render(<OpenmrsDateRangePicker aria-label="datepicker" />);
const wrapper = getInputsWrapper(container)!;
expect(wrapper).toHaveClass(styles.inputsWrapperMd);
expect(screen.getByRole('button')).toHaveClass(styles.flatButtonMd);
});

it('should apply sm size classes when size="sm"', () => {
const { container } = render(<OpenmrsDateRangePicker aria-label="datepicker" size="sm" />);
const wrapper = getInputsWrapper(container)!;
expect(wrapper).toHaveClass(styles.inputsWrapperSm);
expect(screen.getByRole('button')).toHaveClass(styles.flatButtonSm);
});

it('should apply md size classes when size="md"', () => {
const { container } = render(<OpenmrsDateRangePicker aria-label="datepicker" size="md" />);
const wrapper = getInputsWrapper(container)!;
expect(wrapper).toHaveClass(styles.inputsWrapperMd);
expect(screen.getByRole('button')).toHaveClass(styles.flatButtonMd);
});

it('should apply lg size classes when size="lg"', () => {
const { container } = render(<OpenmrsDateRangePicker aria-label="datepicker" size="lg" />);
const wrapper = getInputsWrapper(container)!;
expect(wrapper).toHaveClass(styles.inputsWrapperLg);
expect(screen.getByRole('button')).toHaveClass(styles.flatButtonLg);
// See the note in datepicker.test.tsx: the group owns both the height and the bottom border,
// and gets its height from Carbon's layout size tokens via these classes.
it.each([
['sm' as const, 'cds--layout--size-sm'],
['md' as const, 'cds--layout--size-md'],
['lg' as const, 'cds--layout--size-lg'],
])('should size the input group with the Carbon layout class for size=%s', (size, expectedClass) => {
render(<OpenmrsDateRangePicker aria-label="datepicker" size={size} />);
const [inputGroup] = screen.getAllByRole('group');
expect(inputGroup).toHaveClass(styles.inputGroup);
expect(inputGroup).toHaveClass(expectedClass);
});

it('should not set a layout class when no size is given', () => {
render(<OpenmrsDateRangePicker aria-label="datepicker" />);
const [inputGroup] = screen.getAllByRole('group');
expect(inputGroup).toHaveClass(styles.inputGroup);
expect(inputGroup.className).not.toMatch(/cds--layout--size-/);
});

// See the note in datepicker.test.tsx: untyped JavaScript callers can pass anything, and none
// of it may reach the DOM as a `cds--layout--size-*` token.
it.each([

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ditto.

['null', null],
['an empty string', ''],
['an unsupported step', 'xl'],
['the wrong case', 'Md'],
['a number', 40],
['an object', {}],
['a value that would inject a second class', 'md cds--layout--size-lg'],
])('should render without a layout class when size is %s', (_label, size) => {
render(<OpenmrsDateRangePicker aria-label="datepicker" size={size as unknown as 'md'} />);
const [inputGroup] = screen.getAllByRole('group');
expect(inputGroup).toHaveClass(styles.inputGroup);
expect(inputGroup.className).not.toMatch(/cds--layout--size-/);
});
/* eslint-enable testing-library/no-container, testing-library/no-node-access */
});

describe('calendar popover', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,40 @@
@use '@carbon/styles/scss/utilities/box-shadow';
@use '@carbon/styles/scss/utilities/component-reset';
@use '@carbon/styles/scss/utilities/focus-outline' as focus;
@use '@carbon/styles/scss/utilities/layout' as carbon-layout;
@use '../vars' as *;

// Text field for dates
//
// Carbon puts the control height and the 1px bottom border on the *same* border-box element
// (see `.cds--text-input` / `.cds--date-picker__input`), so the border is included in the
// height: an md input is 40px in total, not 40px plus a border. This group has to do the same,
// otherwise it renders one pixel taller than every Carbon input beside it in a form row.
//
// The height itself comes from Carbon's layout size tokens (sm 32px / md 40px / lg 48px), which is
// exactly how Carbon's own inputs are sized, so we follow Carbon if those values ever change. The
// step is taken from the surrounding layout context — either the `cds--layout--size-*` class the
// components set when a `size` prop is given, or one on an ancestor — and falls back to `$default`
// when there is none. `$min`/`$max` match `.cds--text-input`, so a context outside sm..lg clamps
// the same way Carbon's inputs do.
.inputGroup {
@include carbon-layout.use('size', $default: 'md', $min: 'sm', $max: 'lg');

align-items: center;
background-color: var(--cds-field);
block-size: carbon-layout.size('height');
border-block-end: 1px solid var(--cds-border-strong);
box-sizing: border-box;
color: var(--cds-text-primary);
display: flex;
position: relative;

// The trigger button fills the group instead of defining its height.
& > .flatButton {
block-size: 100%;
inline-size: carbon-layout.size('height');
}

&:focus-within,
&.cds--focused {
@include focus.focus-outline('outline');
Expand Down Expand Up @@ -98,6 +121,7 @@

// Single date picker input
.inputWrapper {
block-size: 100%;
inline-size: 15rem;
margin-left: layout.$spacing-05;
min-width: layout.$spacing-13;
Expand All @@ -110,39 +134,16 @@
@extend %readonlyPlaceholderDimming;
}

.inputWrapperMd {
block-size: layout.$spacing-08;
}

.inputWrapperSm {
block-size: layout.$spacing-07;
}

.inputWrapperLg {
block-size: layout.$spacing-09;
}

// Range date picker inputs
.inputsWrapper {
block-size: 100%;
display: flex;
min-width: 10rem;
max-width: 15rem;
margin-inline-start: 1rem;
inline-size: 15rem;
}

.inputsWrapperSm {
block-size: layout.$spacing-07;
}

.inputsWrapperMd {
block-size: layout.$spacing-08;
}

.inputsWrapperLg {
block-size: layout.$spacing-09;
}

.dateInputWrapper {
@extend .inputWrapper;
min-width: unset;
Expand Down Expand Up @@ -251,21 +252,14 @@
--omrs-icon-fill: #{theme.$support-error};
}

.flatButtonMd {
// The calendar popover's previous/next month buttons. These are a fixed 2.5rem square regardless
// of the field size: the popover is not part of the field, so it does not scale with it. The
// field's own trigger button is sized by `.inputGroup` above.
.calendarNavButton {
height: layout.$spacing-08;
width: layout.$spacing-08;
}

.flatButtonSm {
height: layout.$spacing-07;
width: layout.$spacing-07;
}

.flatButtonLg {
height: layout.$spacing-09;
width: layout.$spacing-09;
}

.monthYear {
@include component-reset.reset;
@include type.type-style('heading-compact-01');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,39 +194,51 @@ describe('OpenmrsDatePicker', () => {
});

describe('size prop', () => {
/* eslint-disable testing-library/no-container, testing-library/no-node-access */
it('should apply md size classes by default', () => {
const { container } = render(<OpenmrsDatePicker aria-label="datepicker" />);
const wrapper = container.querySelector('.cds--date-picker-input__wrapper');
expect(wrapper).not.toBeNull();
expect(wrapper).toHaveClass(styles.inputWrapperMd);
expect(screen.getByRole('button')).toHaveClass(styles.flatButtonMd);
});

it('should apply sm size classes when size="sm"', () => {
const { container } = render(<OpenmrsDatePicker aria-label="datepicker" size="sm" />);
const wrapper = container.querySelector('.cds--date-picker-input__wrapper');
expect(wrapper).not.toBeNull();
expect(wrapper).toHaveClass(styles.inputWrapperSm);
expect(screen.getByRole('button')).toHaveClass(styles.flatButtonSm);
});

it('should apply md size classes when size="md"', () => {
const { container } = render(<OpenmrsDatePicker aria-label="datepicker" size="md" />);
const wrapper = container.querySelector('.cds--date-picker-input__wrapper');
expect(wrapper).not.toBeNull();
expect(wrapper).toHaveClass(styles.inputWrapperMd);
expect(screen.getByRole('button')).toHaveClass(styles.flatButtonMd);
});

it('should apply lg size classes when size="lg"', () => {
const { container } = render(<OpenmrsDatePicker aria-label="datepicker" size="lg" />);
const wrapper = container.querySelector('.cds--date-picker-input__wrapper');
expect(wrapper).not.toBeNull();
expect(wrapper).toHaveClass(styles.inputWrapperLg);
expect(screen.getByRole('button')).toHaveClass(styles.flatButtonLg);
// The input group is the element that carries both the control height and the bottom border,
// and it takes that height from Carbon's layout size tokens through these classes, the same
// way `cds--text-input` does. Asserting on the class is the closest we can get here: happy-dom
// has no layout engine, so it cannot report a rendered height. The heights themselves
// (sm 32px / md 40px / lg 48px, matching Carbon's inputs) are only verifiable in a real
// browser.
it.each([
['sm' as const, 'cds--layout--size-sm'],
['md' as const, 'cds--layout--size-md'],
['lg' as const, 'cds--layout--size-lg'],
])('should size the input group with the Carbon layout class for size=%s', (size, expectedClass) => {
render(<OpenmrsDatePicker aria-label="datepicker" size={size} />);
const [inputGroup] = screen.getAllByRole('group');
expect(inputGroup).toHaveClass(styles.inputGroup);
expect(inputGroup).toHaveClass(expectedClass);
});

// Without a size the group must carry no layout class at all, so that it inherits the
// surrounding Carbon layout context the way an unsized `TextInput` does. The `md` fallback
// then comes from `layout.use()`'s `$default` in the stylesheet, not from a class.
it('should not set a layout class when no size is given', () => {
render(<OpenmrsDatePicker aria-label="datepicker" />);
const [inputGroup] = screen.getAllByRole('group');
expect(inputGroup).toHaveClass(styles.inputGroup);
expect(inputGroup.className).not.toMatch(/cds--layout--size-/);
});

// The prop is typed, but the framework is consumed from untyped JavaScript, so the component
// has to survive whatever a caller actually passes. Nothing outside the three supported steps
// may reach the DOM as a `cds--layout--size-*` token: the group must stay unsized and inherit
// the layout context instead.
it.each([

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nit: this invalid-input matrix now runs three times: once against getLayoutSizeClass directly in utils.test.ts, and once through a full component render in each picker's test file. Since both components just delegate to the helper, utils.test.ts can own the full matrix and a single representative case here (say, the whitespace-injection one) would prove the wiring. Would cut about 40 lines across the two component test files with no coverage loss. Can be ignored.

['null', null],
['an empty string', ''],
['an unsupported step', 'xl'],
['the wrong case', 'Md'],
['a number', 40],
['an object', {}],
['a value that would inject a second class', 'md cds--layout--size-lg'],
])('should render without a layout class when size is %s', (_label, size) => {
render(<OpenmrsDatePicker aria-label="datepicker" size={size as unknown as 'md'} />);
const [inputGroup] = screen.getAllByRole('group');
expect(inputGroup).toHaveClass(styles.inputGroup);
expect(inputGroup.className).not.toMatch(/cds--layout--size-/);
});
/* eslint-enable testing-library/no-container, testing-library/no-node-access */
});

describe('CSS class variants', () => {
Expand Down
Loading
Loading