Skip to content

Commit e6c7bb6

Browse files
thetaPCliamdebeasiIonitron
authored
feat(checkbox, radio, toggle, range): stacked labels for form controls (#28075)
Co-authored-by: Liam DeBeasi <[email protected]> Co-authored-by: ionitron <[email protected]>
1 parent cbafa6b commit e6c7bb6

File tree

148 files changed

+604
-36
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

148 files changed

+604
-36
lines changed

core/api.txt

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -289,12 +289,13 @@ ion-card-title,prop,mode,"ios" | "md",undefined,false,false
289289
ion-card-title,css-prop,--color
290290

291291
ion-checkbox,shadow
292+
ion-checkbox,prop,alignment,"center" | "start",'center',false,false
292293
ion-checkbox,prop,checked,boolean,false,false,false
293294
ion-checkbox,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record<never, never> | undefined,undefined,false,true
294295
ion-checkbox,prop,disabled,boolean,false,false,false
295296
ion-checkbox,prop,indeterminate,boolean,false,false,false
296297
ion-checkbox,prop,justify,"end" | "space-between" | "start",'space-between',false,false
297-
ion-checkbox,prop,labelPlacement,"end" | "fixed" | "start",'start',false,false
298+
ion-checkbox,prop,labelPlacement,"end" | "fixed" | "stacked" | "start",'start',false,false
298299
ion-checkbox,prop,legacy,boolean | undefined,undefined,false,false
299300
ion-checkbox,prop,mode,"ios" | "md",undefined,false,false
300301
ion-checkbox,prop,name,string,this.inputId,false,false
@@ -1008,10 +1009,11 @@ ion-progress-bar,part,stream
10081009
ion-progress-bar,part,track
10091010

10101011
ion-radio,shadow
1012+
ion-radio,prop,alignment,"center" | "start",'center',false,false
10111013
ion-radio,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record<never, never> | undefined,undefined,false,true
10121014
ion-radio,prop,disabled,boolean,false,false,false
10131015
ion-radio,prop,justify,"end" | "space-between" | "start",'space-between',false,false
1014-
ion-radio,prop,labelPlacement,"end" | "fixed" | "start",'start',false,false
1016+
ion-radio,prop,labelPlacement,"end" | "fixed" | "stacked" | "start",'start',false,false
10151017
ion-radio,prop,legacy,boolean | undefined,undefined,false,false
10161018
ion-radio,prop,mode,"ios" | "md",undefined,false,false
10171019
ion-radio,prop,name,string,this.inputId,false,false
@@ -1038,7 +1040,7 @@ ion-range,prop,debounce,number | undefined,undefined,false,false
10381040
ion-range,prop,disabled,boolean,false,false,false
10391041
ion-range,prop,dualKnobs,boolean,false,false,false
10401042
ion-range,prop,label,string | undefined,undefined,false,false
1041-
ion-range,prop,labelPlacement,"end" | "fixed" | "start",'start',false,false
1043+
ion-range,prop,labelPlacement,"end" | "fixed" | "stacked" | "start",'start',false,false
10421044
ion-range,prop,legacy,boolean | undefined,undefined,false,false
10431045
ion-range,prop,max,number,100,false,false
10441046
ion-range,prop,min,number,0,false,false
@@ -1478,12 +1480,13 @@ ion-toast,part,icon
14781480
ion-toast,part,message
14791481

14801482
ion-toggle,shadow
1483+
ion-toggle,prop,alignment,"center" | "start",'center',false,false
14811484
ion-toggle,prop,checked,boolean,false,false,false
14821485
ion-toggle,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record<never, never> | undefined,undefined,false,true
14831486
ion-toggle,prop,disabled,boolean,false,false,false
14841487
ion-toggle,prop,enableOnOffLabels,boolean | undefined,config.get('toggleOnOffLabels'),false,false
14851488
ion-toggle,prop,justify,"end" | "space-between" | "start",'space-between',false,false
1486-
ion-toggle,prop,labelPlacement,"end" | "fixed" | "start",'start',false,false
1489+
ion-toggle,prop,labelPlacement,"end" | "fixed" | "stacked" | "start",'start',false,false
14871490
ion-toggle,prop,legacy,boolean | undefined,undefined,false,false
14881491
ion-toggle,prop,mode,"ios" | "md",undefined,false,false
14891492
ion-toggle,prop,name,string,this.inputId,false,false

core/src/components.d.ts

Lines changed: 40 additions & 16 deletions
Large diffs are not rendered by default.

core/src/components/checkbox/checkbox.scss

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,14 @@
108108
@include margin($checkbox-item-label-margin-top, null, $checkbox-item-label-margin-bottom, null);
109109
}
110110

111+
:host(.in-item.checkbox-label-placement-stacked) .label-text-wrapper {
112+
@include margin($checkbox-item-label-margin-top, null, $form-control-label-margin, null);
113+
}
114+
115+
:host(.in-item.checkbox-label-placement-stacked) .native-wrapper {
116+
@include margin(null, null, $checkbox-item-label-margin-bottom, null);
117+
}
118+
111119
/**
112120
* If no label text is placed into the slot
113121
* then the element should be hidden otherwise
@@ -181,6 +189,17 @@ input {
181189
justify-content: end;
182190
}
183191

192+
// Align Items
193+
// ---------------------------------------------
194+
195+
:host(.checkbox-alignment-start) .checkbox-wrapper {
196+
align-items: start;
197+
}
198+
199+
:host(.checkbox-alignment-center) .checkbox-wrapper {
200+
align-items: center;
201+
}
202+
184203

185204
// Label Placement - Start
186205
// ----------------------------------------------------------------
@@ -248,6 +267,24 @@ input {
248267
max-width: 200px;
249268
}
250269

270+
// Label Placement - Stacked
271+
// ----------------------------------------------------------------
272+
273+
/**
274+
* Label is on top of the checkbox.
275+
*/
276+
:host(.checkbox-label-placement-stacked) .checkbox-wrapper {
277+
flex-direction: column;
278+
}
279+
280+
:host(.checkbox-label-placement-stacked) .label-text-wrapper {
281+
/**
282+
* The margin between the label and
283+
* the checkbox should be on the bottom
284+
* when the label sits at the top.
285+
*/
286+
@include margin(null, 0, $form-control-label-margin, 0);
287+
}
251288

252289
// Checked / Indeterminate Checkbox
253290
// ---------------------------------------------

core/src/components/checkbox/checkbox.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,9 @@ export class Checkbox implements ComponentInterface {
8181
* `"start"`: The label will appear to the left of the checkbox in LTR and to the right in RTL.
8282
* `"end"`: The label will appear to the right of the checkbox in LTR and to the left in RTL.
8383
* `"fixed"`: The label has the same behavior as `"start"` except it also has a fixed width. Long text will be truncated with ellipses ("...").
84+
* `"stacked"`: The label will appear above the checkbox regardless of the direction. The alignment of the label can be controlled with the `alignment` property.
8485
*/
85-
@Prop() labelPlacement: 'start' | 'end' | 'fixed' = 'start';
86+
@Prop() labelPlacement: 'start' | 'end' | 'fixed' | 'stacked' = 'start';
8687

8788
/**
8889
* How to pack the label and checkbox within a line.
@@ -95,6 +96,13 @@ export class Checkbox implements ComponentInterface {
9596
*/
9697
@Prop() justify: 'start' | 'end' | 'space-between' = 'space-between';
9798

99+
/**
100+
* How to control the alignment of the checkbox and label on the cross axis.
101+
* `"start"`: The label and control will appear on the left of the cross axis in LTR, and on the right side in RTL.
102+
* `"center"`: The label and control will appear at the center of the cross axis in both LTR and RTL.
103+
*/
104+
@Prop() alignment: 'start' | 'center' = 'center';
105+
98106
// TODO(FW-3100): remove this
99107
/**
100108
* Set the `legacy` property to `true` to forcibly use the legacy form control markup.
@@ -224,6 +232,7 @@ export class Checkbox implements ComponentInterface {
224232
labelPlacement,
225233
name,
226234
value,
235+
alignment,
227236
} = this;
228237
const mode = getIonMode(this);
229238
const path = getSVGPath(mode, indeterminate);
@@ -240,6 +249,7 @@ export class Checkbox implements ComponentInterface {
240249
'checkbox-indeterminate': indeterminate,
241250
interactive: true,
242251
[`checkbox-justify-${justify}`]: true,
252+
[`checkbox-alignment-${alignment}`]: true,
243253
[`checkbox-label-placement-${labelPlacement}`]: true,
244254
})}
245255
>

core/src/components/checkbox/test/item/checkbox.e2e.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,4 +70,21 @@ configs({ directions: ['ltr'], modes: ['md'] }).forEach(({ title, screenshot, co
7070
await expect(list).toHaveScreenshot(screenshot(`checkbox-long-label-in-item`));
7171
});
7272
});
73+
74+
test.describe(title('checkbox: stacked label in item'), () => {
75+
test('should render margins correctly when using stacked label in item', async ({ page }) => {
76+
await page.setContent(
77+
`
78+
<ion-list>
79+
<ion-item>
80+
<ion-checkbox label-placement="stacked">Enable Notifications</ion-checkbox>
81+
</ion-item>
82+
</ion-list>
83+
`,
84+
config
85+
);
86+
const list = page.locator('ion-list');
87+
await expect(list).toHaveScreenshot(screenshot(`checkbox-stacked-label-in-item`));
88+
});
89+
});
7390
});
Loading
Loading
Loading

core/src/components/checkbox/test/item/index.html

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,27 @@ <h2>Justify Space Between</h2>
137137
</div>
138138
</div>
139139

140+
<h1>Placement Stacked</h1>
141+
<div class="grid">
142+
<div class="grid-item">
143+
<h2>Align Start</h2>
144+
<ion-list>
145+
<ion-item>
146+
<ion-checkbox label-placement="stacked" alignment="start">Enable Notifications</ion-checkbox>
147+
</ion-item>
148+
</ion-list>
149+
</div>
150+
151+
<div class="grid-item">
152+
<h2>Align Center</h2>
153+
<ion-list>
154+
<ion-item>
155+
<ion-checkbox label-placement="stacked" alignment="center">Enable Notifications</ion-checkbox>
156+
</ion-item>
157+
</ion-list>
158+
</div>
159+
</div>
160+
140161
<h1>States</h1>
141162
<div class="grid">
142163
<div class="grid-item">

core/src/components/checkbox/test/label/checkbox.e2e.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,5 +138,31 @@ configs().forEach(({ title, screenshot, config }) => {
138138
await expect(checkbox).toHaveScreenshot(screenshot(`checkbox-label-fixed-justify-space-between`));
139139
});
140140
});
141+
142+
test.describe('checkbox: stacked placement', () => {
143+
test('should align the label to the start of the container in the stacked position', async ({ page }) => {
144+
await page.setContent(
145+
`
146+
<ion-checkbox label-placement="stacked" alignment="start" style="width: 200px">This is a long label</ion-checkbox>
147+
`,
148+
config
149+
);
150+
151+
const checkbox = page.locator('ion-checkbox');
152+
await expect(checkbox).toHaveScreenshot(screenshot(`checkbox-label-stacked-align-start`));
153+
});
154+
155+
test('should align the label to the center of the container in the stacked position', async ({ page }) => {
156+
await page.setContent(
157+
`
158+
<ion-checkbox label-placement="stacked" alignment="center" style="width: 200px">This is a long label</ion-checkbox>
159+
`,
160+
config
161+
);
162+
163+
const checkbox = page.locator('ion-checkbox');
164+
await expect(checkbox).toHaveScreenshot(screenshot(`checkbox-label-stacked-align-center`));
165+
});
166+
});
141167
});
142168
});

0 commit comments

Comments
 (0)