Skip to content

Commit 3a57650

Browse files
feat(toggle): add checkedIcon and uncheckedIcon properties for custom icons
1 parent f04fa23 commit 3a57650

File tree

9 files changed

+119
-9
lines changed

9 files changed

+119
-9
lines changed

core/api.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2068,6 +2068,7 @@ ion-toast,part,wrapper
20682068
ion-toggle,shadow
20692069
ion-toggle,prop,alignment,"center" | "start" | undefined,undefined,false,false
20702070
ion-toggle,prop,checked,boolean,false,false,false
2071+
ion-toggle,prop,checkedIcon,null | string | undefined,undefined,false,false
20712072
ion-toggle,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record<never, never> | undefined,undefined,false,true
20722073
ion-toggle,prop,disabled,boolean,false,false,false
20732074
ion-toggle,prop,enableOnOffLabels,boolean | undefined,config.get('toggleOnOffLabels'),false,false
@@ -2078,6 +2079,7 @@ ion-toggle,prop,labelPlacement,"end" | "fixed" | "stacked" | "start",'start',fal
20782079
ion-toggle,prop,mode,"ios" | "md",undefined,false,false
20792080
ion-toggle,prop,name,string,this.inputId,false,false
20802081
ion-toggle,prop,required,boolean,false,false,false
2082+
ion-toggle,prop,uncheckedIcon,null | string | undefined,undefined,false,false
20812083
ion-toggle,prop,value,null | string | undefined,'on',false,false
20822084
ion-toggle,event,ionBlur,void,true
20832085
ion-toggle,event,ionChange,ToggleChangeEventDetail<any>,true

core/src/components.d.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3700,6 +3700,10 @@ export namespace Components {
37003700
* @default false
37013701
*/
37023702
"checked": boolean;
3703+
/**
3704+
* The built-in named SVG icon name or the exact `src` of an SVG file to use when the toggle is checked.
3705+
*/
3706+
"checkedIcon"?: string | null;
37033707
/**
37043708
* The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics).
37053709
*/
@@ -3745,6 +3749,10 @@ export namespace Components {
37453749
* @default false
37463750
*/
37473751
"required": boolean;
3752+
/**
3753+
* The built-in named SVG icon name or the exact `src` of an SVG file to use when the toggle is unchecked.
3754+
*/
3755+
"uncheckedIcon"?: string | null;
37483756
/**
37493757
* The value of the toggle does not mean if it's checked or not, use the `checked` property for that. The value of a toggle is analogous to the value of a `<input type="checkbox">`, it's only used when the toggle participates in a native `<form>`.
37503758
* @default 'on'
@@ -9094,6 +9102,10 @@ declare namespace LocalJSX {
90949102
* @default false
90959103
*/
90969104
"checked"?: boolean;
9105+
/**
9106+
* The built-in named SVG icon name or the exact `src` of an SVG file to use when the toggle is checked.
9107+
*/
9108+
"checkedIcon"?: string | null;
90979109
/**
90989110
* The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics).
90999111
*/
@@ -9151,6 +9163,10 @@ declare namespace LocalJSX {
91519163
* @default false
91529164
*/
91539165
"required"?: boolean;
9166+
/**
9167+
* The built-in named SVG icon name or the exact `src` of an SVG file to use when the toggle is unchecked.
9168+
*/
9169+
"uncheckedIcon"?: string | null;
91549170
/**
91559171
* The value of the toggle does not mean if it's checked or not, use the `checked` property for that. The value of a toggle is analogous to the value of a `<input type="checkbox">`, it's only used when the toggle participates in a native `<form>`.
91569172
* @default 'on'
@@ -9907,6 +9923,8 @@ declare namespace LocalJSX {
99079923
"helperText": string;
99089924
"value": string | null;
99099925
"enableOnOffLabels": boolean | undefined;
9926+
"checkedIcon": string | null;
9927+
"uncheckedIcon": string | null;
99109928
"labelPlacement": 'start' | 'end' | 'fixed' | 'stacked';
99119929
"justify": 'start' | 'end' | 'space-between';
99129930
"alignment": 'start' | 'center';

core/src/components/toggle/test/states/index.html

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,18 @@
5252
<div class="grid">
5353
<div class="grid-item">
5454
<h2>Unchecked</h2>
55-
<ion-toggle>Enable Notifications</ion-toggle>
55+
<ion-toggle >Enable Notifications</ion-toggle>
5656
</div>
5757

5858
<div class="grid-item">
5959
<h2>Checked</h2>
6060
<ion-toggle checked="true">Enable Notifications</ion-toggle>
6161
</div>
6262

63+
<ion-toggle enable-on-off-labels="true" checked-icon="heart" unchecked-icon="close" checked="true">Icons Toggle</ion-toggle>
64+
65+
66+
6367
<div class="grid-item">
6468
<h2>Disabled, Unchecked</h2>
6569
<ion-toggle disabled="true">Enable Notifications</ion-toggle>

core/src/components/toggle/test/toggle.spec.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { newSpecPage } from '@stencil/core/testing';
2+
import { checkmarkOutline, ellipseOutline, removeOutline } from 'ionicons/icons';
23

34
import { config } from '../../../global/config';
45
import { Toggle } from '../toggle';
@@ -41,6 +42,58 @@ describe('toggle', () => {
4142
});
4243
});
4344

45+
describe('checkedIcon and uncheckedIcon', () => {
46+
it('should set custom checked icon on the instance, overriding config', async () => {
47+
const t = await newToggle();
48+
t.checkedIcon = 'custom-checked-icon-instance';
49+
config.reset({
50+
toggleCheckedIcon: 'custom-checked-icon-config',
51+
});
52+
53+
expect((t as any).getSwitchLabelIcon('md', true)).toBe('custom-checked-icon-instance');
54+
});
55+
56+
it('should set custom unchecked icon on the instance, overriding config', async () => {
57+
const t = await newToggle();
58+
t.uncheckedIcon = 'custom-unchecked-icon-instance';
59+
config.reset({
60+
toggleUncheckedIcon: 'custom-unchecked-icon-config',
61+
});
62+
63+
expect((t as any).getSwitchLabelIcon('md', false)).toBe('custom-unchecked-icon-instance');
64+
});
65+
66+
it('should set custom checked icon in the config', async () => {
67+
const t = await newToggle();
68+
config.reset({
69+
toggleCheckedIcon: 'custom-checked-icon-config',
70+
});
71+
72+
expect((t as any).getSwitchLabelIcon('md', true)).toBe('custom-checked-icon-config');
73+
});
74+
75+
it('should set custom unchecked icon in the config', async () => {
76+
const t = await newToggle();
77+
config.reset({
78+
toggleUncheckedIcon: 'custom-unchecked-icon-config',
79+
});
80+
81+
expect((t as any).getSwitchLabelIcon('md', false)).toBe('custom-unchecked-icon-config');
82+
});
83+
84+
it('should use default icons in md mode', async () => {
85+
const t = await newToggle();
86+
expect((t as any).getSwitchLabelIcon('md', true)).toBe(checkmarkOutline);
87+
expect((t as any).getSwitchLabelIcon('md', false)).toBe(removeOutline);
88+
});
89+
90+
it('should use default icons in ios mode', async () => {
91+
const t = await newToggle();
92+
expect((t as any).getSwitchLabelIcon('ios', true)).toBe(removeOutline);
93+
expect((t as any).getSwitchLabelIcon('ios', false)).toBe(ellipseOutline);
94+
});
95+
});
96+
4497
describe('shadow parts', () => {
4598
it('should have shadow parts', async () => {
4699
const page = await newSpecPage({

core/src/components/toggle/toggle.md.scss

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
--handle-max-height: #{$toggle-md-handle-max-height};
1818
--handle-spacing: 0;
1919
--handle-transition: #{$toggle-md-transition};
20+
--on-off-label-icon-size: 13px;
21+
22+
2023
}
2124

2225
// Toggle Native Wrapper
@@ -61,10 +64,13 @@
6164
}
6265

6366
.toggle-inner .toggle-switch-icon {
64-
@include padding(1px);
65-
66-
width: 100%;
67-
height: 100%;
67+
width: var(--on-off-label-icon-size);
68+
height: var(--on-off-label-icon-size);
69+
min-width: var(--on-off-label-icon-size);
70+
min-height: var(--on-off-label-icon-size);
71+
display: block;
72+
opacity: 0.86;
73+
transition: transform $toggle-md-transition-duration, opacity $toggle-md-transition-duration, color $toggle-md-transition-duration;
6874
}
6975

7076
// Material Design Toggle: Disabled

core/src/components/toggle/toggle.tsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,18 @@ export class Toggle implements ComponentInterface {
104104
*/
105105
@Prop() enableOnOffLabels: boolean | undefined = config.get('toggleOnOffLabels');
106106

107+
/**
108+
* The built-in named SVG icon name or the exact `src` of an SVG file
109+
* to use when the toggle is checked.
110+
*/
111+
@Prop() checkedIcon?: string | null;
112+
113+
/**
114+
* The built-in named SVG icon name or the exact `src` of an SVG file
115+
* to use when the toggle is unchecked.
116+
*/
117+
@Prop() uncheckedIcon?: string | null;
118+
107119
/**
108120
* Where to place the label relative to the input.
109121
* `"start"`: The label will appear to the left of the toggle in LTR and to the right in RTL.
@@ -348,10 +360,13 @@ export class Toggle implements ComponentInterface {
348360
};
349361

350362
private getSwitchLabelIcon = (mode: Mode, checked: boolean) => {
363+
const checkedIcon = this.checkedIcon ?? config.get('toggleCheckedIcon');
364+
const uncheckedIcon = this.uncheckedIcon ?? config.get('toggleUncheckedIcon');
365+
351366
if (mode === 'md') {
352-
return checked ? checkmarkOutline : removeOutline;
367+
return checked ? checkedIcon ?? checkmarkOutline : uncheckedIcon ?? removeOutline;
353368
}
354-
return checked ? removeOutline : ellipseOutline;
369+
return checked ? checkedIcon ?? removeOutline : uncheckedIcon ?? ellipseOutline;
355370
};
356371

357372
private renderOnOffSwitchLabels(mode: Mode, checked: boolean) {

core/src/utils/config.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,16 @@ export interface IonicConfig {
7272
*/
7373
toggleOnOffLabels?: boolean;
7474

75+
/**
76+
* Overrides the default checked icon in all `<ion-toggle>` components.
77+
*/
78+
toggleCheckedIcon?: string;
79+
80+
/**
81+
* Overrides the default unchecked icon in all `<ion-toggle>` components.
82+
*/
83+
toggleUncheckedIcon?: string;
84+
7585
/**
7686
* Overrides the default spinner for all `ion-loading` overlays, ie. the ones
7787
* created with `ion-loading-controller`.

packages/angular/src/directives/proxies.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2567,14 +2567,14 @@ Shorthand for ionToastDidDismiss.
25672567

25682568

25692569
@ProxyCmp({
2570-
inputs: ['alignment', 'checked', 'color', 'disabled', 'enableOnOffLabels', 'errorText', 'helperText', 'justify', 'labelPlacement', 'mode', 'name', 'required', 'value']
2570+
inputs: ['alignment', 'checked', 'checkedIcon', 'color', 'disabled', 'enableOnOffLabels', 'errorText', 'helperText', 'justify', 'labelPlacement', 'mode', 'name', 'required', 'uncheckedIcon', 'value']
25712571
})
25722572
@Component({
25732573
selector: 'ion-toggle',
25742574
changeDetection: ChangeDetectionStrategy.OnPush,
25752575
template: '<ng-content></ng-content>',
25762576
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
2577-
inputs: ['alignment', 'checked', 'color', 'disabled', 'enableOnOffLabels', 'errorText', 'helperText', 'justify', 'labelPlacement', 'mode', 'name', 'required', 'value'],
2577+
inputs: ['alignment', 'checked', 'checkedIcon', 'color', 'disabled', 'enableOnOffLabels', 'errorText', 'helperText', 'justify', 'labelPlacement', 'mode', 'name', 'required', 'uncheckedIcon', 'value'],
25782578
})
25792579
export class IonToggle {
25802580
protected el: HTMLIonToggleElement;

packages/vue/src/proxies.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1066,6 +1066,8 @@ export const IonToggle: StencilVueComponent<JSX.IonToggle, JSX.IonToggle["checke
10661066
'helperText',
10671067
'value',
10681068
'enableOnOffLabels',
1069+
'checkedIcon',
1070+
'uncheckedIcon',
10691071
'labelPlacement',
10701072
'justify',
10711073
'alignment',

0 commit comments

Comments
 (0)