Skip to content

Commit e56f0ac

Browse files
feat(divider): add new ion-divider component
1 parent 40932d7 commit e56f0ac

File tree

10 files changed

+356
-0
lines changed

10 files changed

+356
-0
lines changed

core/src/components.d.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1160,6 +1160,10 @@ export namespace Components {
11601160
*/
11611161
"theme"?: "ios" | "md" | "ionic";
11621162
}
1163+
interface IonDivider {
1164+
"inset"?: boolean;
1165+
"spacing"?: 'xxsmall' | 'xsmall' | 'small' | 'large' | 'xlarge' | 'xxlarge';
1166+
}
11631167
interface IonFab {
11641168
/**
11651169
* If `true`, both the `ion-fab-button` and all `ion-fab-list` inside `ion-fab` will become active. That means `ion-fab-button` will become a `close` icon and `ion-fab-list` will become visible.
@@ -4436,6 +4440,12 @@ declare global {
44364440
prototype: HTMLIonDatetimeButtonElement;
44374441
new (): HTMLIonDatetimeButtonElement;
44384442
};
4443+
interface HTMLIonDividerElement extends Components.IonDivider, HTMLStencilElement {
4444+
}
4445+
var HTMLIonDividerElement: {
4446+
prototype: HTMLIonDividerElement;
4447+
new (): HTMLIonDividerElement;
4448+
};
44394449
interface HTMLIonFabElement extends Components.IonFab, HTMLStencilElement {
44404450
}
44414451
var HTMLIonFabElement: {
@@ -5395,6 +5405,7 @@ declare global {
53955405
"ion-content": HTMLIonContentElement;
53965406
"ion-datetime": HTMLIonDatetimeElement;
53975407
"ion-datetime-button": HTMLIonDatetimeButtonElement;
5408+
"ion-divider": HTMLIonDividerElement;
53985409
"ion-fab": HTMLIonFabElement;
53995410
"ion-fab-button": HTMLIonFabButtonElement;
54005411
"ion-fab-list": HTMLIonFabListElement;
@@ -6611,6 +6622,10 @@ declare namespace LocalJSX {
66116622
*/
66126623
"theme"?: "ios" | "md" | "ionic";
66136624
}
6625+
interface IonDivider {
6626+
"inset"?: boolean;
6627+
"spacing"?: 'xxsmall' | 'xsmall' | 'small' | 'large' | 'xlarge' | 'xxlarge';
6628+
}
66146629
interface IonFab {
66156630
/**
66166631
* If `true`, both the `ion-fab-button` and all `ion-fab-list` inside `ion-fab` will become active. That means `ion-fab-button` will become a `close` icon and `ion-fab-list` will become visible.
@@ -9524,6 +9539,7 @@ declare namespace LocalJSX {
95249539
"ion-content": IonContent;
95259540
"ion-datetime": IonDatetime;
95269541
"ion-datetime-button": IonDatetimeButton;
9542+
"ion-divider": IonDivider;
95279543
"ion-fab": IonFab;
95289544
"ion-fab-button": IonFabButton;
95299545
"ion-fab-list": IonFabList;
@@ -9626,6 +9642,7 @@ declare module "@stencil/core" {
96269642
"ion-content": LocalJSX.IonContent & JSXBase.HTMLAttributes<HTMLIonContentElement>;
96279643
"ion-datetime": LocalJSX.IonDatetime & JSXBase.HTMLAttributes<HTMLIonDatetimeElement>;
96289644
"ion-datetime-button": LocalJSX.IonDatetimeButton & JSXBase.HTMLAttributes<HTMLIonDatetimeButtonElement>;
9645+
"ion-divider": LocalJSX.IonDivider & JSXBase.HTMLAttributes<HTMLIonDividerElement>;
96299646
"ion-fab": LocalJSX.IonFab & JSXBase.HTMLAttributes<HTMLIonFabElement>;
96309647
"ion-fab-button": LocalJSX.IonFabButton & JSXBase.HTMLAttributes<HTMLIonFabButtonElement>;
96319648
"ion-fab-list": LocalJSX.IonFabList & JSXBase.HTMLAttributes<HTMLIonFabListElement>;
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
@use "../../themes/ionic/ionic.globals.scss" as globals;
2+
@import "../../themes/native/native.globals";
3+
4+
:host {
5+
/**
6+
* @prop --margin-top: Top margin of the divider
7+
* @prop --margin-bottom: Bottom margin of the divider
8+
* @prop --padding-end: Right margin of the divider
9+
* @prop --padding-start: Left padding of the divider
10+
*/
11+
--margin-top: 0px;
12+
--margin-bottom: 0px;
13+
--padding-start: 0px;
14+
--padding-end: 0px;
15+
16+
padding-right: var(--padding-end);
17+
padding-left: calc(var(--padding-start) + var(--ion-safe-area-left, 0px));
18+
19+
display: block;
20+
21+
width: 100%;
22+
}
23+
24+
:host hr {
25+
margin-top: var(--margin-top);
26+
margin-bottom: var(--margin-bottom);
27+
28+
display: block;
29+
30+
width: 100%;
31+
32+
border: none;
33+
border-top: #{globals.$ion-border-size-025} solid #{globals.$ion-border-default};
34+
}
35+
36+
// Divider Spacing
37+
// --------------------------------------------------
38+
39+
:host(.divider-space-xsmall) {
40+
--margin-top: #{globals.$ion-space-200};
41+
--margin-bottom: #{globals.$ion-space-200};
42+
}
43+
44+
:host(.divider-space-small) {
45+
--margin-top: #{globals.$ion-space-300};
46+
--margin-bottom: #{globals.$ion-space-300};
47+
}
48+
49+
:host(.divider-space-medium) {
50+
--margin-top: #{globals.$ion-space-400};
51+
--margin-bottom: #{globals.$ion-space-400};
52+
}
53+
54+
:host(.divider-space-large) {
55+
--margin-top: #{globals.$ion-space-600};
56+
--margin-bottom: #{globals.$ion-space-600};
57+
}
58+
59+
:host(.divider-space-xlarge) {
60+
--margin-top: #{globals.$ion-space-800};
61+
--margin-bottom: #{globals.$ion-space-800};
62+
}
63+
64+
:host(.divider-space-xxlarge) {
65+
--margin-top: #{globals.$ion-space-1000};
66+
--margin-bottom: #{globals.$ion-space-1000};
67+
}
68+
69+
// Divider Inset
70+
// --------------------------------------------------
71+
72+
:host(.divider-inset) {
73+
--margin-top: #{globals.$ion-space-600};
74+
--margin-bottom: #{globals.$ion-space-600};
75+
--padding-start: #{globals.$ion-space-400};
76+
--padding-end: #{globals.$ion-space-400};
77+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { getIonTheme } from '@global/ionic-global';
2+
import type { ComponentInterface } from '@stencil/core';
3+
import { Component, Prop, Host, h } from '@stencil/core';
4+
5+
@Component({
6+
tag: 'ion-divider',
7+
styleUrls: {
8+
ios: 'divider.scss',
9+
md: 'divider.scss',
10+
ionic: 'divider.scss',
11+
},
12+
shadow: true,
13+
})
14+
export class Divider implements ComponentInterface {
15+
16+
/**
17+
* Set to `"xxsmall"` for the smallest spacing.
18+
* Set to "xsmall" for a very small spacing.
19+
* Set to `"small"` for a small spacing.
20+
* Set to "medium" for a medium spacing.
21+
* Set to "large" for a large spacing.
22+
* Set to `"xlarge"` for the largest spacing.
23+
*
24+
* Defaults to `"small"` for the `ionic` theme, undefined for all other themes.
25+
*/
26+
@Prop({ reflect: true }) spacing?: 'xxsmall' | 'xsmall' | 'small' | 'medium' | 'large' | 'xlarge' | 'xxlarge';
27+
28+
/**
29+
* If `true`, the divider will have horizontal margins
30+
* By default, it's `false`
31+
*/
32+
@Prop() inset?: boolean = false;
33+
34+
private getSpacing(): string | undefined {
35+
const { spacing } = this;
36+
37+
if (spacing === undefined) {
38+
return 'xxsmall';
39+
}
40+
41+
return spacing;
42+
}
43+
44+
render() {
45+
const theme = getIonTheme(this);
46+
const spacing = this.getSpacing();
47+
return (
48+
<Host
49+
class={{
50+
[theme]: true,
51+
[`divider-space-${spacing}`]: spacing !== undefined,
52+
[`divider-inset`]: this.inset || false,
53+
}}>
54+
<hr />
55+
</Host>
56+
);
57+
}
58+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { expect } from '@playwright/test';
2+
import { configs, test } from '@utils/test/playwright';
3+
4+
configs().forEach(({ title, screenshot, config }) => {
5+
test.describe(title('item-divider: basic'), () => {
6+
test('should display an item divider with text', async ({ page }) => {
7+
await page.setContent(
8+
`
9+
<ion-item-divider>
10+
<ion-label>Item Divider</ion-label>
11+
</ion-item-divider>
12+
`,
13+
config
14+
);
15+
16+
const divider = page.locator('ion-item-divider');
17+
await expect(divider).toHaveScreenshot(screenshot(`item-divider-text`));
18+
});
19+
20+
test('should display an item divider with a button in the end slot', async ({ page }) => {
21+
await page.setContent(
22+
`
23+
<ion-item-divider>
24+
<ion-label>Item Divider</ion-label>
25+
<ion-button slot="end">Button</ion-button>
26+
</ion-item-divider>
27+
`,
28+
config
29+
);
30+
31+
const divider = page.locator('ion-item-divider');
32+
await expect(divider).toHaveScreenshot(screenshot(`item-divider-button-end`));
33+
});
34+
35+
test('should display an item divider with an icon in the start slot', async ({ page }) => {
36+
await page.setContent(
37+
`
38+
<ion-item-divider>
39+
<ion-icon slot="start" name="star"></ion-icon>
40+
<ion-label>Item Divider</ion-label>
41+
</ion-item-divider>
42+
`,
43+
config
44+
);
45+
46+
const divider = page.locator('ion-item-divider');
47+
await expect(divider).toHaveScreenshot(screenshot(`item-divider-icon-start`));
48+
});
49+
50+
/**
51+
* This behavior needs to be tested for all modes & directions
52+
* Safe padding should stay on the same side when the direction changes
53+
*/
54+
test('should have safe area padding', async ({ page }) => {
55+
await page.setContent(
56+
`
57+
<style>
58+
:root {
59+
--ion-safe-area-left: 40px;
60+
--ion-safe-area-right: 20px;
61+
}
62+
</style>
63+
<ion-list>
64+
<ion-item-divider>
65+
<ion-label>Item Divider</ion-label>
66+
<ion-button slot="end">Button</ion-button>
67+
</ion-item-divider>
68+
</ion-list>
69+
`,
70+
config
71+
);
72+
73+
const list = page.locator('ion-list');
74+
75+
await expect(list).toHaveScreenshot(screenshot('item-divider-safe-area'));
76+
});
77+
});
78+
});
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<!DOCTYPE html>
2+
<html lang="en" dir="ltr">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<title>Divider - Basic</title>
6+
<meta
7+
name="viewport"
8+
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"
9+
/>
10+
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
11+
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
12+
<script src="../../../../../scripts/testing/scripts.js"></script>
13+
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
14+
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
15+
</head>
16+
17+
<body>
18+
<ion-app>
19+
<ion-header>
20+
<ion-toolbar>
21+
<ion-title>Divider - Basic</ion-title>
22+
</ion-toolbar>
23+
</ion-header>
24+
25+
<ion-content>
26+
<ion-list>
27+
<ion-item>
28+
<ion-select id="select-spacing" justify="space-between" interface="alert" label="Spacing" placeholder="">
29+
<ion-select-option selected="true" value="xxsmall">XXSmall(default)</ion-select-option>
30+
<ion-select-option value="xsmall">XSmall</ion-select-option>
31+
<ion-select-option value="small">Small</ion-select-option>
32+
<ion-select-option value="md">Medium</ion-select-option>
33+
<ion-select-option value="large">Large</ion-select-option>
34+
<ion-select-option value="xlarge">Xlarge</ion-select-option>
35+
<ion-select-option value="xlarge">XXlarge</ion-select-option>
36+
</ion-select>
37+
</ion-item>
38+
<ion-item>
39+
<ion-toggle id="inset-toggle">Inset</ion-toggle>
40+
</ion-item>
41+
</ion-list>
42+
43+
<h5>Top</h5>
44+
<ion-divider></ion-divider>
45+
<h5>Bottom</h5>
46+
</ion-content>
47+
</ion-app>
48+
<script>
49+
const listOfDividers = Array.from(document.querySelectorAll('ion-divider'));
50+
const toggle = document.getElementById('inset-toggle');
51+
52+
function updateAttr(el, name, value) {
53+
if (value === '' || value === undefined) {
54+
el.removeAttribute(name);
55+
} else {
56+
el.setAttribute(name, value);
57+
}
58+
}
59+
60+
toggle.addEventListener('ionChange', (event) => {
61+
const isChecked = event.detail.checked;
62+
for (const el of listOfDividers) {
63+
updateAttr(el, 'inset', isChecked);
64+
}
65+
});
66+
67+
const selectSize = document.getElementById('select-spacing');
68+
selectSize.addEventListener('ionChange', (e) => {
69+
for (const el of listOfDividers) {
70+
updateAttr(el, 'spacing', e.detail.value);
71+
}
72+
});
73+
</script>
74+
</body>
75+
</html>

packages/angular/src/directives/proxies-list.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export const DIRECTIVES = [
2525
d.IonContent,
2626
d.IonDatetime,
2727
d.IonDatetimeButton,
28+
d.IonDivider,
2829
d.IonFab,
2930
d.IonFabButton,
3031
d.IonFabList,

packages/angular/src/directives/proxies.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -703,6 +703,28 @@ export class IonDatetimeButton {
703703
export declare interface IonDatetimeButton extends Components.IonDatetimeButton {}
704704

705705

706+
@ProxyCmp({
707+
inputs: ['inset', 'spacing']
708+
})
709+
@Component({
710+
selector: 'ion-divider',
711+
changeDetection: ChangeDetectionStrategy.OnPush,
712+
template: '<ng-content></ng-content>',
713+
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
714+
inputs: ['inset', 'spacing'],
715+
})
716+
export class IonDivider {
717+
protected el: HTMLElement;
718+
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
719+
c.detach();
720+
this.el = r.nativeElement;
721+
}
722+
}
723+
724+
725+
export declare interface IonDivider extends Components.IonDivider {}
726+
727+
706728
@ProxyCmp({
707729
inputs: ['activated', 'edge', 'horizontal', 'mode', 'theme', 'vertical'],
708730
methods: ['close']

0 commit comments

Comments
 (0)