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
3 changes: 3 additions & 0 deletions build-tools/utils/custom-css-properties.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ const customCssPropertiesList = [
'styleBoxShadowDefault',
'styleBoxShadowDisabled',
'styleBoxShadowHover',
'stylePaddingInline',
'stylePaddingInlineStart',
'stylePaddingInlineEnd',
// Readonly state
'styleBackgroundReadonly',
'styleBorderColorReadonly',
Expand Down
81 changes: 80 additions & 1 deletion src/input/__tests__/styles.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,40 @@
// SPDX-License-Identifier: Apache-2.0
import customCssProps from '../../internal/generated/custom-css-properties';
import { getInputStyles } from '../styles';
import { parsePaddingInline } from '../utils';

// Mock the environment module
jest.mock('../../internal/environment', () => ({
SYSTEM: 'core',
}));

describe('parsePaddingInline', () => {
test('returns undefined for both start and end when input is undefined', () => {
expect(parsePaddingInline(undefined)).toEqual({ start: undefined, end: undefined });
});

test('handles single value - applies to both start and end', () => {
expect(parsePaddingInline('10px')).toEqual({ start: '10px', end: '10px' });
expect(parsePaddingInline('1rem')).toEqual({ start: '1rem', end: '1rem' });
expect(parsePaddingInline('0')).toEqual({ start: '0', end: '0' });
});

test('handles shorthand notation - first value is start, second is end', () => {
expect(parsePaddingInline('10px 20px')).toEqual({ start: '10px', end: '20px' });
expect(parsePaddingInline('1rem 2rem')).toEqual({ start: '1rem', end: '2rem' });
expect(parsePaddingInline('5px 10px')).toEqual({ start: '5px', end: '10px' });
});

test('handles extra whitespace', () => {
expect(parsePaddingInline(' 10px 20px ')).toEqual({ start: '10px', end: '20px' });
expect(parsePaddingInline('10px 20px')).toEqual({ start: '10px', end: '20px' });
});

test('ignores values beyond the first two', () => {
expect(parsePaddingInline('10px 20px 30px 40px')).toEqual({ start: '10px', end: '20px' });
});
});

describe('getInputStyles', () => {
afterEach(() => {
jest.resetModules();
Expand Down Expand Up @@ -68,7 +96,9 @@ describe('getInputStyles', () => {
fontSize: '14px',
fontWeight: '400',
paddingBlock: '8px',
paddingInline: '12px',
[customCssProps.stylePaddingInline]: '12px',
[customCssProps.stylePaddingInlineStart]: '12px',
[customCssProps.stylePaddingInlineEnd]: '12px',
[customCssProps.styleBackgroundDefault]: '#ffffff',
[customCssProps.styleBackgroundDisabled]: '#f0f0f0',
[customCssProps.styleBackgroundHover]: '#fafafa',
Expand Down Expand Up @@ -96,6 +126,55 @@ describe('getInputStyles', () => {
});
});

test('handles shorthand paddingInline values correctly', () => {
const styleWithShorthand = {
root: {
paddingInline: '10px 20px',
},
};

const result = getInputStyles(styleWithShorthand);

expect(result).toEqual({
[customCssProps.stylePaddingInline]: '10px 20px',
[customCssProps.stylePaddingInlineStart]: '10px',
[customCssProps.stylePaddingInlineEnd]: '20px',
});
});

test('handles single value paddingInline correctly', () => {
const styleWithSingleValue = {
root: {
paddingInline: '15px',
},
};

const result = getInputStyles(styleWithSingleValue);

expect(result).toEqual({
[customCssProps.stylePaddingInline]: '15px',
[customCssProps.stylePaddingInlineStart]: '15px',
[customCssProps.stylePaddingInlineEnd]: '15px',
});
});

test('does not add padding properties when paddingInline is not provided', () => {
const styleWithoutPadding = {
root: {
fontSize: '14px',
},
};

const result = getInputStyles(styleWithoutPadding);

expect(result).toEqual({
fontSize: '14px',
});
expect(result).not.toHaveProperty(customCssProps.stylePaddingInline);
expect(result).not.toHaveProperty(customCssProps.stylePaddingInlineStart);
expect(result).not.toHaveProperty(customCssProps.stylePaddingInlineEnd);
});

test('returns undefined when SYSTEM is not core', async () => {
jest.resetModules();
jest.doMock('../../internal/environment', () => ({
Expand Down
30 changes: 21 additions & 9 deletions src/input/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
.input {
@include styles.styles-reset;
padding-block: styles.$control-padding-vertical;
padding-inline: styles.$control-padding-horizontal;
padding-inline: var(#{custom-props.$stylePaddingInline}, #{styles.$control-padding-horizontal});
color: var(#{custom-props.$styleColorDefault}, awsui.$color-text-body-default);
inline-size: 100%;
cursor: text;
Expand Down Expand Up @@ -128,19 +128,25 @@
&.input-invalid {
@include styles.form-invalid-control();
&.input-has-icon-left {
padding-inline-start: calc(
#{styles.$control-icon-horizontal-padding} -
(#{styles.$invalid-control-left-border} - #{awsui.$border-width-field})
padding-inline-start: max(
var(#{custom-props.$stylePaddingInlineStart}, #{styles.$control-padding-horizontal}),
calc(
#{styles.$control-icon-horizontal-padding} -
(#{styles.$invalid-control-left-border} - #{awsui.$border-width-field})
)
);
}
}

&.input-warning {
@include styles.form-warning-control();
&.input-has-icon-left {
padding-inline-start: calc(
#{styles.$control-icon-horizontal-padding} -
(#{styles.$invalid-control-left-border} - #{awsui.$border-width-field})
padding-inline-start: max(
var(#{custom-props.$stylePaddingInlineStart}, #{styles.$control-padding-horizontal}),
calc(
#{styles.$control-icon-horizontal-padding} -
(#{styles.$invalid-control-left-border} - #{awsui.$border-width-field})
)
);
}
}
Expand All @@ -159,10 +165,16 @@
}
}
&.input-has-icon-left {
padding-inline-start: styles.$control-icon-horizontal-padding;
padding-inline-start: max(
var(#{custom-props.$stylePaddingInlineStart}, #{styles.$control-padding-horizontal}),
#{styles.$control-icon-horizontal-padding}
);
}
&.input-has-icon-right {
padding-inline-end: styles.$control-icon-horizontal-padding;
padding-inline-end: max(
var(#{custom-props.$stylePaddingInlineEnd}, #{styles.$control-padding-horizontal}),
#{styles.$control-icon-horizontal-padding}
);
}
&.input-has-no-border-radius {
border-start-start-radius: awsui.$border-radius-dropdown;
Expand Down
16 changes: 15 additions & 1 deletion src/input/styles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,32 @@
import { SYSTEM } from '../internal/environment';
import customCssProps from '../internal/generated/custom-css-properties';
import { InputProps } from './interfaces';
import { parsePaddingInline } from './utils';

export function getInputStyles(style: InputProps['style']) {
let properties = {};

if (style?.root && SYSTEM === 'core') {
// We are only supporting logical shorthand properties for padding (e.g. "10px 30px"),
// so wen ened to deconstruct this into start- and end-padding to be able to style
// them separately.
const { start: paddingStart, end: paddingEnd } = parsePaddingInline(style?.root?.paddingInline);

properties = {
borderRadius: style?.root?.borderRadius,
borderWidth: style?.root?.borderWidth,
fontSize: style?.root?.fontSize,
fontWeight: style?.root?.fontWeight,
paddingBlock: style?.root?.paddingBlock,
paddingInline: style?.root?.paddingInline,
...(style?.root?.paddingInline && {
[customCssProps.stylePaddingInline]: style.root.paddingInline,
}),
...(paddingStart && {
[customCssProps.stylePaddingInlineStart]: paddingStart,
}),
...(paddingEnd && {
[customCssProps.stylePaddingInlineEnd]: paddingEnd,
}),
...(style?.root?.backgroundColor && {
[customCssProps.styleBackgroundDefault]: style.root.backgroundColor?.default,
[customCssProps.styleBackgroundDisabled]: style.root.backgroundColor?.disabled,
Expand Down
15 changes: 15 additions & 0 deletions src/input/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,18 @@ export const convertAutoComplete = (propertyValue: boolean | string = false): st
}
return propertyValue || 'off';
};

/**
* Parses CSS paddingInline value into separate start and end values.
* Handles both single values ('10px') and shorthand notation ('10px 20px').
*/
export const parsePaddingInline = (
paddingInline: string | undefined
): { start: string | undefined; end: string | undefined } => {
if (!paddingInline) {
return { start: undefined, end: undefined };
}

const [start, end = start] = paddingInline.trim().split(/\s+/);
return { start, end };
};
Loading