Skip to content

Add appearance-readonly to nimble-select and nimble-combobox #2602

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

Merged
merged 13 commits into from
May 20, 2025
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "Add appearance-readonly to nimble-select and nimble-combobox",
"packageName": "@ni/nimble-components",
"email": "[email protected]",
"dependentChangeType": "patch"
}
3 changes: 3 additions & 0 deletions packages/nimble-components/src/combobox/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ export class Combobox
@attr
public appearance: DropdownAppearance = DropdownAppearance.underline;

@attr({ attribute: 'appearance-readonly', mode: 'boolean' })
public appearanceReadOnly = false;

/**
* The autocomplete attribute.
*/
Expand Down
26 changes: 18 additions & 8 deletions packages/nimble-components/src/combobox/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import {
borderRgbPartialColor,
smallPadding,
borderHoverColor,
borderWidth
borderWidth,
placeholderFontColor
} from '../theme-provider/design-tokens';

import { styles as dropdownStyles } from '../patterns/dropdown/styles';
Expand All @@ -14,7 +15,6 @@ import { styles as requiredVisibleStyles } from '../patterns/required-visible/st
import { focusVisible } from '../utilities/style/focus';
import { appearanceBehavior } from '../utilities/style/appearance';
import { DropdownAppearance } from '../select/types';
import { userSelectNone } from '../utilities/style/user-select';

export const styles = css`
${dropdownStyles}
Expand All @@ -29,12 +29,6 @@ export const styles = css`
);
}

:host([disabled]) *,
:host([disabled]) {
${userSelectNone}
color: ${bodyDisabledFontColor};
}

.control {
bottom-border-width: var(--ni-private-bottom-border-width);
}
Expand All @@ -61,6 +55,18 @@ export const styles = css`
outline: none;
}

.selected-value::placeholder {
color: ${placeholderFontColor};
}

:host([disabled]) .selected-value::placeholder {
color: ${bodyDisabledFontColor};
}

:host([disabled][appearance-readonly]) .selected-value::placeholder {
color: ${placeholderFontColor};
}

[part='indicator'] {
display: none;
}
Expand All @@ -71,6 +77,10 @@ export const styles = css`
padding-right: ${smallPadding};
}

:host([disabled][appearance-readonly]) .end-slot-container {
display: none;
}

.separator {
display: inline;
width: 2px;
Expand Down
24 changes: 18 additions & 6 deletions packages/nimble-components/src/patterns/dropdown/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ export const styles = css`
color: ${controlLabelDisabledFontColor};
}

:host([disabled][appearance-readonly]) .label {
color: ${controlLabelFontColor};
}

.control {
align-items: center;
cursor: pointer;
Expand All @@ -138,6 +142,11 @@ export const styles = css`
border-color: rgba(${borderRgbPartialColor}, 0.1);
}

:host([disabled][appearance-readonly]) .control {
color: ${bodyFontColor};
border-color: rgba(${borderRgbPartialColor}, 0.3);
}

:host([error-visible]) .control,
:host([error-visible][open]) .control,
:host([error-visible][disabled]) .control {
Expand All @@ -159,8 +168,11 @@ export const styles = css`
padding-left: ${mediumPadding};
}

.selected-value[disabled]::placeholder {
color: ${bodyDisabledFontColor};
:host([disabled][appearance-readonly]) .selected-value {
cursor: text;
user-select: text;
-webkit-user-select: text;
padding-right: ${smallPadding};
}

.indicator {
Expand All @@ -172,6 +184,10 @@ export const styles = css`
align-items: center;
}

:host([disabled][appearance-readonly]) .indicator {
display: none;
}

.indicator slot[name='indicator'] svg {
width: ${iconSize};
height: ${iconSize};
Expand Down Expand Up @@ -276,10 +292,6 @@ export const styles = css`
border-bottom-width: ${borderWidth};
padding-bottom: 0;
}

:host([disabled]) .control {
border-color: rgba(${borderRgbPartialColor}, 0.1);
}
`
),
appearanceBehavior(
Expand Down
3 changes: 3 additions & 0 deletions packages/nimble-components/src/select/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ export class Select
@attr
public appearance: DropdownAppearance = DropdownAppearance.underline;

@attr({ attribute: 'appearance-readonly', mode: 'boolean' })
public appearanceReadOnly = false;

/**
* Reflects the placement for the listbox when the select is open.
*
Expand Down
4 changes: 4 additions & 0 deletions packages/nimble-components/src/select/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ export const styles = css`
color: ${bodyDisabledFontColor};
}

:host([disabled][appearance-readonly]) .selected-value.placeholder {
color: ${placeholderFontColor};
}

.selected-value {
order: 1;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ import {
sharedMatrixParameters
} from '../../utilities/matrix';
import {
disabledStates,
type DisabledState,
errorStates,
type ErrorState,
type RequiredVisibleState,
requiredVisibleStates
requiredVisibleStates,
type OnlyReadOnlyAbsentState,
onlyReadOnlyAbsentStates
} from '../../utilities/states';
import { hiddenWrapper } from '../../utilities/hidden';
import { loremIpsum } from '../../utilities/lorem-ipsum';
Expand Down Expand Up @@ -46,14 +46,15 @@ export default metadata;

// prettier-ignore
const component = (
[requiredVisibleName, requiredVisible]: RequiredVisibleState,
[disabledName, disabled]: DisabledState,
[disabledReadOnlyName, _readOnly, disabled, appearanceReadOnly]: OnlyReadOnlyAbsentState,
[appearanceName, appearance]: AppearanceState,
[requiredVisibleName, requiredVisible]: RequiredVisibleState,
[errorName, errorVisible, errorText]: ErrorState,
[valueName, value, placeholder]: ValueState
): ViewTemplate => html`
<${comboboxTag}
?disabled="${() => disabled}"
?appearance-readonly="${() => appearanceReadOnly}"
appearance="${() => appearance}"
?error-visible="${() => errorVisible}"
error-text="${() => errorText}"
Expand All @@ -62,7 +63,7 @@ const component = (
?required-visible="${() => requiredVisible}"
style="width: 250px; margin: var(${standardPadding.cssCustomProperty});"
>
${() => disabledName}
${() => disabledReadOnlyName}
${() => appearanceName}
${() => errorName}
${() => valueName}
Expand All @@ -76,9 +77,9 @@ const component = (

export const themeMatrix: StoryFn = createMatrixThemeStory(
createMatrix(component, [
requiredVisibleStates,
disabledStates,
onlyReadOnlyAbsentStates,
appearanceStates,
requiredVisibleStates,
errorStates,
valueStates
])
Expand Down
13 changes: 12 additions & 1 deletion packages/storybook/src/nimble/combobox/combobox.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
import {
apiCategory,
appearanceDescription,
appearanceReadOnlyDescription,
createUserSelectedThemeStory,
disableStorybookZoomTransform,
disabledDescription,
Expand All @@ -38,6 +39,7 @@ interface ComboboxArgs {
placeholder: string;
change: undefined;
input: undefined;
appearanceReadOnly: boolean;
}

interface OptionArgs {
Expand Down Expand Up @@ -111,6 +113,7 @@ const metadata: Meta<ComboboxArgs> = {
value="${x => x.currentValue}"
placeholder="${x => x.placeholder}"
?required-visible="${x => x.requiredVisible}"
?appearance-readonly="${x => x.appearanceReadOnly}"
style="min-width: 250px;"
>
${x => x.label}
Expand Down Expand Up @@ -153,6 +156,13 @@ const metadata: Meta<ComboboxArgs> = {
description: disabledDescription({ componentName: 'combobox' }),
table: { category: apiCategory.attributes }
},
appearanceReadOnly: {
name: 'appearance-readonly',
description: appearanceReadOnlyDescription({
componentName: 'combobox'
}),
table: { category: apiCategory.attributes }
},
errorText: {
name: 'error-text',
description: errorTextDescription,
Expand Down Expand Up @@ -209,7 +219,8 @@ const metadata: Meta<ComboboxArgs> = {
appearance: DropdownAppearance.underline,
placeholder: 'Select value...',
optionsType: ExampleOptionsType.simpleOptions,
requiredVisible: false
requiredVisible: false,
appearanceReadOnly: false
}
};

Expand Down
58 changes: 48 additions & 10 deletions packages/storybook/src/nimble/select/select-matrix.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,20 @@ import { listOptionGroupTag } from '../../../../nimble-components/src/list-optio
import { selectTag } from '../../../../nimble-components/src/select';
import { DropdownAppearance } from '../../../../nimble-components/src/patterns/dropdown/types';
import { waitForUpdatesAsync } from '../../../../nimble-components/src/testing/async-helpers';
import { createStory } from '../../utilities/storybook';
import { createFixedThemeStory, createStory } from '../../utilities/storybook';
import {
createMatrixThemeStory,
createMatrix,
sharedMatrixParameters
} from '../../utilities/matrix';
import {
disabledStates,
type DisabledState,
type ErrorState,
errorStates,
requiredVisibleStates,
type RequiredVisibleState
type RequiredVisibleState,
backgroundStates,
type OnlyReadOnlyAbsentState,
onlyReadOnlyAbsentStates
} from '../../utilities/states';
import { hiddenWrapper } from '../../utilities/hidden';
import { textCustomizationWrapper } from '../../utilities/text-customization';
Expand Down Expand Up @@ -56,9 +57,9 @@ export default metadata;

// prettier-ignore
const component = (
[requiredVisibleName, requiredVisible]: RequiredVisibleState,
[disabledName, disabled]: DisabledState,
[disabledReadOnlyName, _readOnly, disabled, appearanceReadOnly]: OnlyReadOnlyAbsentState,
[appearanceName, appearance]: AppearanceState,
[requiredVisibleName, requiredVisible]: RequiredVisibleState,
[errorName, errorVisible, errorText]: ErrorState,
[valueName, selectedValue]: ValueState,
[clearableName, clearable]: ClearableState
Expand All @@ -67,13 +68,14 @@ const component = (
?error-visible="${() => errorVisible}"
error-text="${() => errorText}"
?disabled="${() => disabled}"
?appearance-readonly="${() => appearanceReadOnly}"
?clearable="${() => clearable}"
appearance="${() => appearance}"
?required-visible="${() => requiredVisible}"
current-value="${() => selectedValue}"
style="width: 250px; margin: var(${standardPadding.cssCustomProperty});"
>
${() => errorName} ${() => disabledName} ${() => appearanceName} ${() => valueName} ${() => clearableName} ${() => requiredVisibleName}
${() => errorName} ${() => disabledReadOnlyName} ${() => appearanceName} ${() => valueName} ${() => clearableName} ${() => requiredVisibleName}
<${listOptionTag} value="1">Option 1</${listOptionTag}>
<${listOptionTag} value="2">${loremIpsum}</${listOptionTag}>
<${listOptionTag} value="3" disabled>Option 3</${listOptionTag}>
Expand All @@ -82,15 +84,51 @@ const component = (
</${selectTag}>
`;

export const themeMatrix: StoryFn = createMatrixThemeStory(
const [
lightThemeWhiteBackground,
colorThemeDarkGreenBackground,
darkThemeBlackBackground,
...remaining
] = backgroundStates;

if (remaining.length > 0) {
throw new Error('New backgrounds need to be supported');
}

export const lightTheme: StoryFn = createFixedThemeStory(
createMatrix(component, [
onlyReadOnlyAbsentStates,
appearanceStates,
requiredVisibleStates,
errorStates,
valueStates,
clearableStates
]),
lightThemeWhiteBackground
);

export const colorTheme: StoryFn = createFixedThemeStory(
createMatrix(component, [
onlyReadOnlyAbsentStates,
appearanceStates,
requiredVisibleStates,
disabledStates,
errorStates,
valueStates,
clearableStates
]),
colorThemeDarkGreenBackground
);

export const darkTheme: StoryFn = createFixedThemeStory(
createMatrix(component, [
onlyReadOnlyAbsentStates,
appearanceStates,
requiredVisibleStates,
errorStates,
valueStates,
clearableStates
])
]),
darkThemeBlackBackground
);

export const hidden: StoryFn = createStory(
Expand Down
Loading