Skip to content

Commit cffc8cc

Browse files
authored
fix: Icon position on inputs and search selects (#123)
1 parent 1779190 commit cffc8cc

6 files changed

Lines changed: 55 additions & 58 deletions

File tree

packages/studiocms_ui/src/components/Input/Input.astro

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ interface Props extends HTMLAttributes<'input'> {
5151
| {
5252
name: AvailableIcons;
5353
position: 'left' | 'right';
54-
};
54+
};
5555
/**
5656
* A description to be shown below the input
5757
*/
@@ -88,29 +88,31 @@ const iconPos = icon
8888
---
8989

9090
<label for={name} class="sui-input-label" class:list={[disabled && "disabled"]}>
91-
{label && (
91+
{label && (
9292
<span class="label">
9393
{label} <span class="req-star">{isRequired && "*"}</span>
9494
</span>
9595
)}
96-
{typeof icon === "string" && (
97-
<Icon name={icon} width={20} height={20} class="input-icon icon-left" />
98-
)}
99-
{typeof icon === "object" && (
100-
<Icon name={icon.name} width={20} height={20} class="input-icon" class:list={[`icon-${iconPos}`]} />
101-
)}
102-
<input
103-
placeholder={placeholder}
104-
name={name}
105-
id={name}
106-
type={type}
107-
class="sui-input"
108-
class:list={[className, iconPos && `has-icon icon-${iconPos}`]}
109-
required={isRequired}
110-
disabled={disabled}
111-
value={defaultValue}
112-
{...props}
113-
/>
96+
<div class="sui-input-wrapper">
97+
{typeof icon === "object" && (
98+
<Icon name={icon.name} width={20} height={20} class="input-icon" class:list={[`icon-${iconPos}`]} />
99+
)}
100+
{typeof icon === "string" && (
101+
<Icon name={icon} width={20} height={20} class="input-icon icon-left" />
102+
)}
103+
<input
104+
placeholder={placeholder}
105+
name={name}
106+
id={name}
107+
type={type}
108+
class="sui-input"
109+
class:list={[className, iconPos && `icon-${iconPos}`]}
110+
required={isRequired}
111+
disabled={disabled}
112+
value={defaultValue}
113+
{...props}
114+
/>
115+
</div>
114116
{description && (
115117
<span class="sui-input-desc">{description}</span>
116118
)}

packages/studiocms_ui/src/components/Input/input.css

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
background: var(--background-step-2);
2424
color: var(--text-normal);
2525
transition: all .15s ease;
26+
width: 100%;
2627
}
2728

2829
.sui-input:hover {
@@ -56,29 +57,22 @@
5657
}
5758

5859
.input-icon.icon-left {
59-
left: 0.75rem;
60+
left: 0.5rem;
6061
}
6162

6263
.input-icon.icon-right {
63-
right: 0.75rem;
64-
}
65-
66-
.label + .input-icon {
67-
top: calc(50% + 14px);
64+
right: 0.5rem;
6865
}
6966

70-
.label + .input-icon:has(~ .sui-input-desc) {
71-
top: calc(50%);
67+
.sui-input.icon-left {
68+
padding-left: calc(1.25rem + 20px);
7269
}
7370

74-
.input-icon:has(~ .sui-input-desc):not(.label + .input-icon) {
75-
top: calc(50% - 14px);
71+
.sui-input.icon-right {
72+
padding-right: calc(1.25rem + 20px);
7673
}
7774

78-
.sui-input.has-icon.icon-left {
79-
padding-left: calc(1.5rem + 20px);
80-
}
81-
82-
.sui-input.has-icon.icon-right {
83-
padding-right: calc(1.5rem + 20px);
75+
.sui-input-wrapper {
76+
position: relative;
77+
width: 100%;
8478
}

packages/studiocms_ui/src/components/SearchSelect/SearchSelect.astro

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,12 @@ const defaultLabel = selected
151151
tabindex={disabled ? -1 : 0}
152152
label={label || ''}
153153
isRequired={isRequired || false}
154+
name={name ? `${name}-input` : undefined}
155+
icon={{
156+
name: "heroicons:chevron-up-down",
157+
position: "right"
158+
}}
154159
/>
155-
<Icon name="heroicons:chevron-up-down" class="sui-search-select-indicator" width={24} height={24} />
156160
</div>
157161
<div class="sui-search-select-dropdown">
158162
<ul class="sui-search-select-dropdown-list" role="listbox" id={`${name}-dropdown`}>
@@ -189,7 +193,7 @@ const defaultLabel = selected
189193
const isSelected = Array.isArray(selected)
190194
? selected.map((y) => y && y.value).includes(x.value)
191195
: selected?.value === x.value;
192-
196+
193197
return (
194198
<option
195199
value={x.value}
@@ -208,10 +212,10 @@ const defaultLabel = selected
208212
</span>
209213
)}
210214
{
211-
multiple && Array.isArray(selected ?? []) && (
215+
multiple && Array.isArray(selected ?? []) && (
212216
<div class="sui-search-select-badge-container">
213217
{
214-
((selected ?? []) as SearchSelectOption[]).map((s) => s &&
218+
((selected ?? []) as SearchSelectOption[]).map((s) => s &&
215219
<Badge class="sui-search-select-badge" data-value={s.value} size="sm" label={s.label} iconPosition="right" icon="heroicons:x-mark" />
216220
)
217221
}

packages/studiocms_ui/test/components/SearchSelect.test.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,8 @@ const mockProps = {
1212
],
1313
};
1414

15-
describe.todo('SearchSelect Component', () => {
16-
// SearchSelect input component current uses a dynamic input instead of a static one
17-
// which makes snapshot testing difficult. This test is a placeholder for future implementation.
18-
19-
// TODO Add a name prop to the SearchSelect Input element to make it easier to target in tests
20-
// and then enable this test.
21-
22-
test.todo('renders SearchSelect component correctly', async ({ renderComponent }) => {
15+
describe('SearchSelect Component', () => {
16+
test('renders SearchSelect component correctly', async ({ renderComponent }) => {
2317
const result = await renderComponent(SearchSelect, 'SearchSelect', { props: mockProps });
2418
expect(result).toMatchSnapshot();
2519
});
Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
22

3-
exports[`Input Component > renders Input component correctly 1`] = `"<label for="input-test" class="sui-input-label"> <input name="input-test" id="input-test" type="text" class="sui-input"> </label>"`;
3+
exports[`Input Component > renders Input component correctly 1`] = `"<label for="input-test" class="sui-input-label"> <div class="sui-input-wrapper"> <input name="input-test" id="input-test" type="text" class="sui-input"> </div> </label>"`;
44
5-
exports[`Input Component > renders Input with "{ defaultValue: 'test-value' }" 1`] = `"<label for="input-test" class="sui-input-label"> <input name="input-test" id="input-test" type="text" class="sui-input" value="test-value"> </label>"`;
5+
exports[`Input Component > renders Input with "{ defaultValue: 'test-value' }" 1`] = `"<label for="input-test" class="sui-input-label"> <div class="sui-input-wrapper"> <input name="input-test" id="input-test" type="text" class="sui-input" value="test-value"> </div> </label>"`;
66
7-
exports[`Input Component > renders Input with "{ description: 'This is a test input' }" 1`] = `"<label for="input-test" class="sui-input-label"> <input name="input-test" id="input-test" type="text" class="sui-input"> <span class="sui-input-desc">This is a test input</span> </label>"`;
7+
exports[`Input Component > renders Input with "{ description: 'This is a test input' }" 1`] = `"<label for="input-test" class="sui-input-label"> <div class="sui-input-wrapper"> <input name="input-test" id="input-test" type="text" class="sui-input"> </div> <span class="sui-input-desc">This is a test input</span> </label>"`;
88
9-
exports[`Input Component > renders Input with "{ disabled: true }" 1`] = `"<label for="input-test" class="sui-input-label disabled"> <input name="input-test" id="input-test" type="text" class="sui-input" disabled> </label>"`;
9+
exports[`Input Component > renders Input with "{ disabled: true }" 1`] = `"<label for="input-test" class="sui-input-label disabled"> <div class="sui-input-wrapper"> <input name="input-test" id="input-test" type="text" class="sui-input" disabled> </div> </label>"`;
1010
11-
exports[`Input Component > renders Input with "{ icon: 'heroicons:academic-cap' }" 1`] = `"<label for="input-test" class="sui-input-label"> <svg style="min-width: 20px" xmlns="http://www.w3.org/2000/svg" prefix="heroicons" class="input-icon icon-left" height="20" width="20" viewbox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M4.26 10.147a60 60 0 0 0-.491 6.347A48.6 48.6 0 0 1 12 20.904a48.6 48.6 0 0 1 8.232-4.41a61 61 0 0 0-.491-6.347m-15.482 0a51 51 0 0 0-2.658-.813A60 60 0 0 1 12 3.493a60 60 0 0 1 10.399 5.84q-1.345.372-2.658.814m-15.482 0A51 51 0 0 1 12 13.489a50.7 50.7 0 0 1 7.74-3.342M6.75 15a.75.75 0 1 0 0-1.5a.75.75 0 0 0 0 1.5m0 0v-3.675A55 55 0 0 1 12 8.443m-7.007 11.55A5.98 5.98 0 0 0 6.75 15.75v-1.5"/></svg> <input name="input-test" id="input-test" type="text" class="sui-input has-icon icon-left"> </label>"`;
11+
exports[`Input Component > renders Input with "{ icon: 'heroicons:academic-cap' }" 1`] = `"<label for="input-test" class="sui-input-label"> <div class="sui-input-wrapper"> <svg style="min-width: 20px" xmlns="http://www.w3.org/2000/svg" prefix="heroicons" class="input-icon icon-left" height="20" width="20" viewbox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M4.26 10.147a60 60 0 0 0-.491 6.347A48.6 48.6 0 0 1 12 20.904a48.6 48.6 0 0 1 8.232-4.41a61 61 0 0 0-.491-6.347m-15.482 0a51 51 0 0 0-2.658-.813A60 60 0 0 1 12 3.493a60 60 0 0 1 10.399 5.84q-1.345.372-2.658.814m-15.482 0A51 51 0 0 1 12 13.489a50.7 50.7 0 0 1 7.74-3.342M6.75 15a.75.75 0 1 0 0-1.5a.75.75 0 0 0 0 1.5m0 0v-3.675A55 55 0 0 1 12 8.443m-7.007 11.55A5.98 5.98 0 0 0 6.75 15.75v-1.5"/></svg> <input name="input-test" id="input-test" type="text" class="sui-input icon-left"> </div> </label>"`;
1212
13-
exports[`Input Component > renders Input with "{ icon: { name: 'heroicons:academic-cap', position: 'left' } }" 1`] = `"<label for="input-test" class="sui-input-label"> <svg style="min-width: 20px" xmlns="http://www.w3.org/2000/svg" prefix="heroicons" class="input-icon icon-left" height="20" width="20" viewbox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M4.26 10.147a60 60 0 0 0-.491 6.347A48.6 48.6 0 0 1 12 20.904a48.6 48.6 0 0 1 8.232-4.41a61 61 0 0 0-.491-6.347m-15.482 0a51 51 0 0 0-2.658-.813A60 60 0 0 1 12 3.493a60 60 0 0 1 10.399 5.84q-1.345.372-2.658.814m-15.482 0A51 51 0 0 1 12 13.489a50.7 50.7 0 0 1 7.74-3.342M6.75 15a.75.75 0 1 0 0-1.5a.75.75 0 0 0 0 1.5m0 0v-3.675A55 55 0 0 1 12 8.443m-7.007 11.55A5.98 5.98 0 0 0 6.75 15.75v-1.5"/></svg> <input name="input-test" id="input-test" type="text" class="sui-input has-icon icon-left"> </label>"`;
13+
exports[`Input Component > renders Input with "{ icon: { name: 'heroicons:academic-cap', position: 'left' } }" 1`] = `"<label for="input-test" class="sui-input-label"> <div class="sui-input-wrapper"> <svg style="min-width: 20px" xmlns="http://www.w3.org/2000/svg" prefix="heroicons" class="input-icon icon-left" height="20" width="20" viewbox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M4.26 10.147a60 60 0 0 0-.491 6.347A48.6 48.6 0 0 1 12 20.904a48.6 48.6 0 0 1 8.232-4.41a61 61 0 0 0-.491-6.347m-15.482 0a51 51 0 0 0-2.658-.813A60 60 0 0 1 12 3.493a60 60 0 0 1 10.399 5.84q-1.345.372-2.658.814m-15.482 0A51 51 0 0 1 12 13.489a50.7 50.7 0 0 1 7.74-3.342M6.75 15a.75.75 0 1 0 0-1.5a.75.75 0 0 0 0 1.5m0 0v-3.675A55 55 0 0 1 12 8.443m-7.007 11.55A5.98 5.98 0 0 0 6.75 15.75v-1.5"/></svg> <input name="input-test" id="input-test" type="text" class="sui-input icon-left"> </div> </label>"`;
1414
15-
exports[`Input Component > renders Input with "{ icon: { name: 'heroicons:academic-cap', position: 'right' } }" 1`] = `"<label for="input-test" class="sui-input-label"> <svg style="min-width: 20px" xmlns="http://www.w3.org/2000/svg" prefix="heroicons" class="input-icon icon-right" height="20" width="20" viewbox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M4.26 10.147a60 60 0 0 0-.491 6.347A48.6 48.6 0 0 1 12 20.904a48.6 48.6 0 0 1 8.232-4.41a61 61 0 0 0-.491-6.347m-15.482 0a51 51 0 0 0-2.658-.813A60 60 0 0 1 12 3.493a60 60 0 0 1 10.399 5.84q-1.345.372-2.658.814m-15.482 0A51 51 0 0 1 12 13.489a50.7 50.7 0 0 1 7.74-3.342M6.75 15a.75.75 0 1 0 0-1.5a.75.75 0 0 0 0 1.5m0 0v-3.675A55 55 0 0 1 12 8.443m-7.007 11.55A5.98 5.98 0 0 0 6.75 15.75v-1.5"/></svg> <input name="input-test" id="input-test" type="text" class="sui-input has-icon icon-right"> </label>"`;
15+
exports[`Input Component > renders Input with "{ icon: { name: 'heroicons:academic-cap', position: 'right' } }" 1`] = `"<label for="input-test" class="sui-input-label"> <div class="sui-input-wrapper"> <svg style="min-width: 20px" xmlns="http://www.w3.org/2000/svg" prefix="heroicons" class="input-icon icon-right" height="20" width="20" viewbox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M4.26 10.147a60 60 0 0 0-.491 6.347A48.6 48.6 0 0 1 12 20.904a48.6 48.6 0 0 1 8.232-4.41a61 61 0 0 0-.491-6.347m-15.482 0a51 51 0 0 0-2.658-.813A60 60 0 0 1 12 3.493a60 60 0 0 1 10.399 5.84q-1.345.372-2.658.814m-15.482 0A51 51 0 0 1 12 13.489a50.7 50.7 0 0 1 7.74-3.342M6.75 15a.75.75 0 1 0 0-1.5a.75.75 0 0 0 0 1.5m0 0v-3.675A55 55 0 0 1 12 8.443m-7.007 11.55A5.98 5.98 0 0 0 6.75 15.75v-1.5"/></svg> <input name="input-test" id="input-test" type="text" class="sui-input icon-right"> </div> </label>"`;
1616
17-
exports[`Input Component > renders Input with "{ isRequired: true }" 1`] = `"<label for="input-test" class="sui-input-label"> <input name="input-test" id="input-test" type="text" class="sui-input" required> </label>"`;
17+
exports[`Input Component > renders Input with "{ isRequired: true }" 1`] = `"<label for="input-test" class="sui-input-label"> <div class="sui-input-wrapper"> <input name="input-test" id="input-test" type="text" class="sui-input" required> </div> </label>"`;
1818
19-
exports[`Input Component > renders Input with "{ label: 'Input Test' }" 1`] = `"<label for="input-test" class="sui-input-label"> <span class="label"> Input Test <span class="req-star"></span> </span> <input name="input-test" id="input-test" type="text" class="sui-input"> </label>"`;
19+
exports[`Input Component > renders Input with "{ label: 'Input Test' }" 1`] = `"<label for="input-test" class="sui-input-label"> <span class="label"> Input Test <span class="req-star"></span> </span> <div class="sui-input-wrapper"> <input name="input-test" id="input-test" type="text" class="sui-input"> </div> </label>"`;
2020
21-
exports[`Input Component > renders Input with "{ placeholder: 'Enter text...' }" 1`] = `"<label for="input-test" class="sui-input-label"> <input placeholder="Enter text..." name="input-test" id="input-test" type="text" class="sui-input"> </label>"`;
21+
exports[`Input Component > renders Input with "{ placeholder: 'Enter text...' }" 1`] = `"<label for="input-test" class="sui-input-label"> <div class="sui-input-wrapper"> <input placeholder="Enter text..." name="input-test" id="input-test" type="text" class="sui-input"> </div> </label>"`;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2+
3+
exports[`SearchSelect Component > renders SearchSelect component correctly 1`] = `"<div id="search-selector-container" class="sui-search-select-label" data-options="[{&#34;label&#34;:&#34;Option 1&#34;,&#34;value&#34;:&#34;opt-1&#34;},{&#34;label&#34;:&#34;Option 2&#34;,&#34;value&#34;:&#34;opt-2&#34;},{&#34;label&#34;:&#34;Option 3&#34;,&#34;value&#34;:&#34;opt-3&#34;}]" data-id="search-selector"> <div class="sui-search-select-dropdown-container"> <div class="sui-search-input-wrapper" id="search-selector-search-input-wrapper"> <label for="search-selector-input" class="sui-input-label"> <span class="label"> Select Element <span class="req-star"></span> </span> <div class="sui-input-wrapper"> <svg style="min-width: 20px" xmlns="http://www.w3.org/2000/svg" prefix="heroicons" class="input-icon icon-right" height="20" width="20" viewbox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M8.25 15L12 18.75L15.75 15m-7.5-6L12 5.25L15.75 9"/></svg> <input placeholder="Select" name="search-selector-input" id="search-selector-input" type="text" class="sui-input icon-right" role="combobox" aria-controls="search-selector-dropdown" aria-expanded="false" tabindex="0"> </div> </label> </div> <div class="sui-search-select-dropdown"> <ul class="sui-search-select-dropdown-list" role="listbox" id="search-selector-dropdown"> <li role="option" value="opt-1" class="sui-search-select-option" id data-option-index="0" data-value="opt-1"> Option 1 </li><li role="option" value="opt-2" class="sui-search-select-option" id data-option-index="1" data-value="opt-2"> Option 2 </li><li role="option" value="opt-3" class="sui-search-select-option" id data-option-index="2" data-value="opt-3"> Option 3 </li> </ul> </div> </div> <select class="sui-hidden-search-select" id="search-selector" name="search-selector" hidden tabindex="-1"> <option value> Select </option> <option value="opt-1"> Option 1 </option><option value="opt-2"> Option 2 </option><option value="opt-3"> Option 3 </option> </select> </div> <script type="module" src="/mock/path/SearchSelect.astro?astro&type=script&index=0&lang.ts"></script>"`;

0 commit comments

Comments
 (0)