feat(item-divider): add recipe and tokens

This commit is contained in:
Maria Hutt
2026-03-11 16:15:19 -07:00
parent 55cac74c95
commit 20a4a3370c
13 changed files with 1505 additions and 51 deletions

View File

@@ -12,7 +12,7 @@
@include border-radius(0);
position: relative;
position: relative; // doesn't seem to be needed?, no issues with it being missing for sticky
min-height: $item-divider-ios-min-height;
@@ -33,6 +33,7 @@
);
}
// all of these are null
:host([slot="end"]) {
@include margin(
$item-ios-slot-end-margin-top,
@@ -82,7 +83,7 @@
font-size: $item-ios-paragraph-font-size;
line-height: normal;
line-height: normal; // not needed since it's the default
text-overflow: inherit;

View File

@@ -11,22 +11,26 @@
--padding-start: #{$item-divider-md-padding-start};
--inner-padding-end: #{$item-divider-md-padding-end};
min-height: $item-divider-md-min-height;
min-height: $item-divider-md-min-height; // for ionic this should be the default
border-bottom: $item-divider-md-border-bottom;
border-bottom: $item-divider-md-border-bottom; // for ionic this should be null
font-size: dynamic-font($item-divider-md-font-size);
font-size: dynamic-font($item-divider-md-font-size); // for ionic this should be the default
}
// Material Design Item Divider Slots
// --------------------------------------------------
::slotted([slot="start"]) {
@include margin-horizontal($item-md-start-slot-margin-start, $item-md-start-slot-margin-end);
@include margin-horizontal(
$item-md-start-slot-margin-start,
//null
$item-md-start-slot-margin-end
); // for ionic this should be 0
}
::slotted([slot="end"]) {
@include margin-horizontal($item-md-end-slot-margin-start, $item-md-end-slot-margin-end);
@include margin-horizontal($item-md-end-slot-margin-start, $item-md-end-slot-margin-end); //null
}
// Material Design Slotted Label
@@ -52,7 +56,7 @@
}
:host(.ion-color) ::slotted(ion-icon) {
color: current-color(contrast);
color: current-color(contrast); // for ionic this should be currentColor, inherit
}
::slotted(ion-icon[slot]) {

View File

@@ -1,6 +1,7 @@
@import "../../themes/native/native.globals";
@use "../../themes/mixins" as mixins;
@use "../../themes/functions.color" as colors;
// Item Divider
// Item Divider: Common Styles
// --------------------------------------------------
:host {
@@ -19,26 +20,29 @@
* @prop --inner-padding-bottom: Bottom inner padding of the item divider
* @prop --inner-padding-start: Start inner padding of the item divider
*/
--padding-top: 0px;
--padding-end: 0px;
--padding-bottom: 0px;
--padding-start: 0px;
--inner-padding-top: 0px;
--inner-padding-end: 0px;
--inner-padding-bottom: 0px;
--inner-padding-start: 0px;
--background: var(--ion-item-divider-background);
--color: var(--ion-item-divider-color);
--padding-top: var(--ion-item-divider-padding-top);
--padding-end: var(--ion-item-divider-padding-end);
--padding-bottom: var(--ion-item-divider-padding-bottom);
--padding-start: var(--ion-item-divider-padding-start);
--inner-padding-top: var(--ion-item-divider-inner-padding-top);
--inner-padding-end: var(--ion-item-divider-inner-padding-end);
--inner-padding-bottom: var(--ion-item-divider-inner-padding-bottom);
--inner-padding-start: var(--ion-item-divider-inner-padding-start);
@include font-smoothing();
@include margin(0);
@include padding(var(--padding-top), null, var(--padding-bottom), null);
@include mixins.font-smoothing();
@include mixins.margin(0);
@include mixins.padding(var(--padding-top), null, var(--padding-bottom), null);
@include mixins.border-radius(0);
/* stylelint-disable */
@include ltr() {
@include mixins.ltr() {
padding-right: var(--padding-end);
padding-left: calc(var(--padding-start) + var(--ion-safe-area-left, 0px));
}
@include rtl() {
@include mixins.rtl() {
padding-right: calc(var(--padding-start) + var(--ion-safe-area-right, 0px));
padding-left: var(--padding-end);
}
@@ -51,19 +55,26 @@
width: 100%;
min-height: var(--ion-item-divider-min-height);
border-bottom: var(--ion-item-divider-border-bottom);
background: var(--background);
color: var(--color);
font-family: $font-family-base;
font-family: var(--ion-font-family, inherit);
font-size: var(--ion-item-divider-font-size);
font-weight: var(--ion-item-divider-font-weight);
overflow: hidden;
z-index: $z-index-item-divider;
z-index: var(--ion-item-divider-z-index, auto);
box-sizing: border-box;
}
:host(.ion-color) {
background: current-color(base);
color: current-color(contrast);
background: colors.current-color("base");
color: colors.current-color("contrast");
}
:host(.item-divider-sticky) {
@@ -72,16 +83,16 @@
}
.item-divider-inner {
@include margin(0);
@include padding(var(--inner-padding-top), null, var(--inner-padding-bottom), null);
@include mixins.margin(0);
@include mixins.padding(var(--inner-padding-top), null, var(--inner-padding-bottom), null);
/* stylelint-disable */
@include ltr() {
@include mixins.ltr() {
padding-right: calc(var(--ion-safe-area-right, 0px) + var(--inner-padding-end));
padding-left: var(--inner-padding-start);
}
@include rtl() {
@include mixins.rtl() {
padding-right: var(--inner-padding-start);
padding-left: calc(var(--ion-safe-area-left, 0px) + var(--inner-padding-end));
}
@@ -113,3 +124,283 @@
overflow: hidden;
}
// Item Divider Slots
// --------------------------------------------------
:host([slot="start"]) {
@include mixins.margin(
var(--ion-item-divider-leading-anchor-margin-top, revert-layer),
var(--ion-item-divider-leading-anchor-margin-end, revert-layer),
var(--ion-item-divider-leading-anchor-margin-bottom, revert-layer),
var(--ion-item-divider-leading-anchor-margin-start, revert-layer)
);
}
::slotted([slot="start"]) {
@include mixins.margin-horizontal(
var(--ion-item-divider-leading-edge-margin-start, revert-layer),
var(--ion-item-divider-leading-edge-margin-end, revert-layer)
); // for ionic this should be 0
}
::slotted([slot="end"]) {
@include mixins.margin-horizontal(
var(--ion-item-divider-trailing-edge-margin-start, revert-layer),
var(--ion-item-divider-trailing-edge-margin-end, revert-layer)
);
}
// Slotted Label
// --------------------------------------------------
::slotted(ion-label) {
@include mixins.margin(
var(--ion-item-divider-label-margin-top),
var(--ion-item-divider-label-margin-end),
var(--ion-item-divider-label-margin-bottom),
var(--ion-item-divider-label-margin-start)
);
}
// Slotted Icon
// --------------------------------------------------
::slotted(ion-icon) {
color: var(--ion-item-divider-icon-default-color);
font-size: var(--ion-item-divider-icon-font-size);
}
:host(.ion-color) ::slotted(ion-icon) {
color: var(--ion-item-divider-icon-semantic-default-color);
}
::slotted(ion-icon[slot="start"]) {
@include mixins.margin(
var(--ion-item-divider-icon-leading-edge-margin-top, revert-layer),
var(--ion-item-divider-icon-leading-edge-margin-end, revert-layer),
var(--ion-item-divider-icon-leading-edge-margin-bottom, revert-layer),
var(--ion-item-divider-icon-leading-edge-margin-start, revert-layer)
);
}
::slotted(ion-icon[slot="end"]) {
@include mixins.margin(
var(--ion-item-divider-icon-trailing-edge-margin-top, revert-layer),
var(--ion-item-divider-icon-trailing-edge-margin-end, revert-layer),
var(--ion-item-divider-icon-trailing-edge-margin-bottom, revert-layer),
var(--ion-item-divider-icon-trailing-edge-margin-start, revert-layer)
);
}
// Slotted Note
// --------------------------------------------------
::slotted(ion-note) {
@include mixins.margin(
var(--ion-item-divider-note-margin-top),
var(--ion-item-divider-note-margin-end),
var(--ion-item-divider-note-margin-bottom),
var(--ion-item-divider-note-margin-start)
);
align-self: flex-start;
font-size: var(--ion-item-divider-note-font-size);
}
::slotted(ion-note[slot]) {
@include mixins.padding(
var(--ion-item-divider-note-padding-top),
var(--ion-item-divider-note-padding-end),
var(--ion-item-divider-note-padding-bottom),
var(--ion-item-divider-note-padding-start)
);
}
// Slotted Avatar
// --------------------------------------------------
::slotted(ion-avatar) {
@include mixins.margin(
var(--ion-item-divider-avatar-margin-top),
var(--ion-item-divider-avatar-margin-end),
var(--ion-item-divider-avatar-margin-bottom),
var(--ion-item-divider-avatar-margin-start)
);
width: var(--ion-item-divider-avatar-width, revert-layer);
height: var(--ion-item-divider-avatar-height, revert-layer);
}
::slotted(ion-avatar[slot="start"]) {
@include mixins.margin-horizontal(
var(--ion-item-divider-avatar-leading-edge-margin-start),
var(--ion-item-divider-avatar-leading-edge-margin-end)
);
}
::slotted(ion-avatar[slot="end"]) {
@include mixins.margin-horizontal(
var(--ion-item-divider-avatar-trailing-edge-margin-start),
var(--ion-item-divider-avatar-trailing-edge-margin-end)
);
}
// Slotted Thumbnail
// --------------------------------------------------
::slotted(ion-thumbnail) {
// TODO(): separate width and height tokens for thumbnails
--size: var(--ion-item-divider-thumbnail-width);
@include mixins.margin(
var(--ion-item-divider-thumbnail-margin-top),
var(--ion-item-divider-thumbnail-margin-end),
var(--ion-item-divider-thumbnail-margin-bottom),
var(--ion-item-divider-thumbnail-margin-start)
);
}
::slotted(ion-thumbnail[slot="start"]) {
@include mixins.margin-horizontal(
var(--ion-item-divider-thumbnail-leading-edge-margin-start),
var(--ion-item-divider-thumbnail-leading-edge-margin-end)
);
}
::slotted(ion-thumbnail[slot="end"]) {
@include mixins.margin-horizontal(
var(--ion-item-divider-thumbnail-trailing-edge-margin-start),
var(--ion-item-divider-thumbnail-trailing-edge-margin-end)
);
}
// Slotted Content
// --------------------------------------------------
::slotted(h1) {
@include mixins.margin(
var(--ion-item-divider-header1-margin-top),
var(--ion-item-divider-header1-margin-end),
var(--ion-item-divider-header1-margin-bottom),
var(--ion-item-divider-header1-margin-start)
);
}
::slotted(h2) {
@include mixins.margin(
var(--ion-item-divider-header2-margin-top),
var(--ion-item-divider-header2-margin-end),
var(--ion-item-divider-header2-margin-bottom),
var(--ion-item-divider-header2-margin-start)
);
}
::slotted(h2:last-child) {
@include mixins.margin(
var(--ion-item-divider-header2-trailing-margin-top),
var(--ion-item-divider-header2-trailing-margin-end),
var(--ion-item-divider-header2-trailing-margin-bottom, 0),
var(--ion-item-divider-header2-trailing-margin-start)
);
}
::slotted(h3) {
@include mixins.margin(
var(--ion-item-divider-header3-margin-top),
var(--ion-item-divider-header3-margin-end),
var(--ion-item-divider-header3-margin-bottom),
var(--ion-item-divider-header3-margin-start)
);
}
::slotted(h3:last-child) {
@include mixins.margin(
var(--ion-item-divider-header3-trailing-margin-top),
var(--ion-item-divider-header3-trailing-margin-end),
var(--ion-item-divider-header3-trailing-margin-bottom, 0),
var(--ion-item-divider-header3-trailing-margin-start)
);
}
::slotted(h4) {
@include mixins.margin(
var(--ion-item-divider-header4-margin-top),
var(--ion-item-divider-header4-margin-end),
var(--ion-item-divider-header4-margin-bottom),
var(--ion-item-divider-header4-margin-start)
);
}
::slotted(h4:last-child) {
@include mixins.margin(
var(--ion-item-divider-header4-trailing-margin-top),
var(--ion-item-divider-header4-trailing-margin-end),
var(--ion-item-divider-header4-trailing-margin-bottom, 0),
var(--ion-item-divider-header4-trailing-margin-start)
);
}
::slotted(h5) {
@include mixins.margin(
var(--ion-item-divider-header5-margin-top),
var(--ion-item-divider-header5-margin-end),
var(--ion-item-divider-header5-margin-bottom),
var(--ion-item-divider-header5-margin-start)
);
}
::slotted(h5:last-child) {
@include mixins.margin(
var(--ion-item-divider-header5-trailing-margin-top),
var(--ion-item-divider-header5-trailing-margin-end),
var(--ion-item-divider-header5-trailing-margin-bottom, 0),
var(--ion-item-divider-header5-trailing-margin-start)
);
}
::slotted(h6) {
@include mixins.margin(
var(--ion-item-divider-header6-margin-top),
var(--ion-item-divider-header6-margin-end),
var(--ion-item-divider-header6-margin-bottom),
var(--ion-item-divider-header6-margin-start)
);
}
::slotted(h6:last-child) {
@include mixins.margin(
var(--ion-item-divider-header6-trailing-margin-top),
var(--ion-item-divider-header6-trailing-margin-end),
var(--ion-item-divider-header6-trailing-margin-bottom, 0),
var(--ion-item-divider-header6-trailing-margin-start)
);
}
::slotted(p) {
@include mixins.margin(
var(--ion-item-divider-paragraph-margin-top),
var(--ion-item-divider-paragraph-margin-end),
var(--ion-item-divider-paragraph-margin-bottom),
var(--ion-item-divider-paragraph-margin-start)
);
color: var(--ion-item-divider-paragraph-color);
font-size: var(--ion-item-divider-paragraph-font-size);
text-overflow: var(--ion-item-divider-paragraph-text-overflow);
overflow: var(--ion-item-divider-paragraph-overflow); // native are the same, can ionic be the same?
}
::slotted(p:last-child) {
@include mixins.margin(
var(--ion-item-divider-paragraph-trailing-margin-top),
var(--ion-item-divider-paragraph-trailing-margin-end),
var(--ion-item-divider-paragraph-trailing-margin-bottom, 0),
var(--ion-item-divider-paragraph-trailing-margin-start)
);
}

View File

@@ -15,11 +15,7 @@ import type { Color } from '../../interface';
*/
@Component({
tag: 'ion-item-divider',
styleUrls: {
ios: 'item-divider.ios.scss',
md: 'item-divider.md.scss',
ionic: 'item-divider.md.scss',
},
styleUrl: 'item-divider.scss',
shadow: true,
})
export class ItemDivider implements ComponentInterface {
@@ -43,6 +39,7 @@ export class ItemDivider implements ComponentInterface {
render() {
const theme = getIonTheme(this);
console.log('theme', theme);
return (
<Host
class={createColorClasses(this.color, {

View File

@@ -8,6 +8,7 @@
* @prop --border-radius: Border radius of the thumbnail
* @prop --size: Size of the thumbnail
*/
// TODO(): separate width and height tokens for thumbnails
--size: 48px;
--border-radius: 0;
@@ -15,8 +16,8 @@
display: block;
width: var(--size);
height: var(--size);
width: var(--size, 48px);
height: var(--size, 48px);
}
::slotted(ion-img),

View File

@@ -1,9 +1,10 @@
import { currentColor } from '../../utils/theme';
import { currentColor, mix, dynamicFont } from '../../utils/theme';
import { defaultTheme as baseDefaultTheme } from '../base/default.tokens';
import type { DefaultTheme } from '../themes.interfaces';
import { darkTheme } from './dark.tokens';
import { lightTheme } from './light.tokens';
import { fontSizes, colors, components } from './shared.tokens';
export const defaultTheme: DefaultTheme = {
...baseDefaultTheme,
@@ -261,5 +262,286 @@ export const defaultTheme: DefaultTheme = {
},
},
},
IonItemDivider: {
background: `var(--ion-background-color, ${colors.white})`,
color: `var(--ion-text-color-step-600, ${mix(colors.white, colors.black, '40%')})`,
minHeight: 'var(--ion-scaling-750)',
zIndex: 100,
padding: {
top: 'var(--ion-spacing-0)',
end: 'var(--ion-spacing-0)',
bottom: 'var(--ion-spacing-0)',
start: components.item.padding.start,
},
inner: {
padding: {
top: 'var(--ion-spacing-0)',
end: components.item.inner.padding.end,
bottom: 'var(--ion-spacing-0)',
start: 'var(--ion-spacing-0)',
},
},
border: {
bottom: `1px solid ${components.item.border.color}`,
},
font: {
size: dynamicFont(fontSizes.itemDividerBase),
},
leading: {
// Targets `::slotted([slot="start"])`
edge: {
margin: {
end: components.item.start.slot.margin.end,
},
},
},
trailing: {
// Targets `::slotted([slot="end"])`
edge: {
margin: {
start: components.item.end.slot.margin.start,
},
},
},
// Targets `ion-label`
label: {
margin: {
top: '13px',
end: 'var(--ion-spacing-0)',
bottom: 'var(--ion-spacing-250)',
start: 'var(--ion-spacing-0)',
},
},
// Targets `ion-icon`
icon: {
font: {
/**
* The icon's font size should use em units to support
* font scaling but evaluate to 24px at 100% font size.
* The value in em units is calculated by dividing
* the icon's font size in pixels by the item divider's
* font size in pixels.
* e.g. 24px / 14px = 1.7142857143em
*/
size: `${components.item.icon.slot.font.size / fontSizes.itemDividerBase}em`,
},
leading: {
// Targets `ion-icon[slot="start"]`
edge: {
margin: {
top: components.item.icon.slot.margin.top,
end: components.item.icon.start.slot.margin.end,
bottom: components.item.icon.slot.margin.bottom,
},
},
},
trailing: {
// Targets `ion-icon[slot="end"]`
edge: {
margin: {
top: components.item.icon.slot.margin.top,
start: components.item.icon.end.slot.margin.start,
bottom: components.item.icon.slot.margin.bottom,
},
},
},
// Default non-semantic states
default: {
color: components.item.icon.slot.color,
},
// Any of the semantic colors like primary, secondary, etc.
semantic: {
default: {
color: currentColor('contrast'),
},
},
},
// Targets `ion-note`
note: {
align: {
self: 'flex-start',
},
font: {
size: dynamicFont(components.item.note.slot.font.size),
},
margin: {
top: 'var(--ion-spacing-0)',
end: 'var(--ion-spacing-0)',
bottom: 'var(--ion-spacing-0)',
start: 'var(--ion-spacing-0)',
},
padding: {
top: components.item.note.slot.padding.top,
end: components.item.note.slot.padding.end,
bottom: components.item.note.slot.padding.bottom,
start: components.item.note.slot.padding.start,
},
},
// Targets `ion-avatar`
avatar: {
height: components.item.avatar.height,
width: components.item.avatar.width,
margin: {
top: components.item.media.slot.margin.top,
end: components.item.media.slot.margin.end,
bottom: components.item.media.slot.margin.bottom,
start: components.item.media.slot.margin.start,
},
leading: {
// Targets `ion-avatar[slot="start"]`
edge: {
margin: {
start: components.item.media.start.slot.margin.start,
end: components.item.media.start.slot.margin.end,
},
},
},
trailing: {
// Targets `ion-avatar[slot="end"]`
edge: {
margin: {
start: components.item.media.end.slot.margin.start,
end: components.item.media.end.slot.margin.end,
},
},
},
},
// Targets `ion-thumbnail`
thumbnail: {
// TODO: switch from size to height and width
height: components.item.thumbnail.height,
width: components.item.thumbnail.width,
margin: {
top: components.item.media.slot.margin.top,
end: components.item.media.slot.margin.end,
bottom: components.item.media.slot.margin.bottom,
start: components.item.media.slot.margin.start,
},
leading: {
// Targets `ion-thumbnail[slot="start"]`
edge: {
margin: {
start: components.item.media.start.slot.margin.start,
end: components.item.media.start.slot.margin.end,
},
},
},
trailing: {
// Targets `ion-thumbnail[slot="end"]`
edge: {
margin: {
start: components.item.media.end.slot.margin.start,
end: components.item.media.end.slot.margin.end,
},
},
},
},
// Targets `h1`
header1: {
margin: {
top: 'var(--ion-spacing-0)',
end: 'var(--ion-spacing-0)',
bottom: 'var(--ion-spacing-xxxs)',
start: 'var(--ion-spacing-0)',
},
},
// Targets `h2`
header2: {
margin: {
top: 'var(--ion-spacing-xxxs)',
end: 'var(--ion-spacing-0)',
bottom: 'var(--ion-spacing-xxxs)',
start: 'var(--ion-spacing-0)',
},
},
// Targets `h3`
header3: {
margin: {
top: 'var(--ion-spacing-xxxs)',
end: 'var(--ion-spacing-0)',
bottom: 'var(--ion-spacing-xxxs)',
start: 'var(--ion-spacing-0)',
},
},
// Targets `h4`
header4: {
margin: {
top: 'var(--ion-spacing-xxxs)',
end: 'var(--ion-spacing-0)',
bottom: 'var(--ion-spacing-xxxs)',
start: 'var(--ion-spacing-0)',
},
},
// Targets `h5`
header5: {
margin: {
top: 'var(--ion-spacing-xxxs)',
end: 'var(--ion-spacing-0)',
bottom: 'var(--ion-spacing-xxxs)',
start: 'var(--ion-spacing-0)',
},
},
// Targets `h6`
header6: {
margin: {
top: 'var(--ion-spacing-xxxs)',
end: 'var(--ion-spacing-0)',
bottom: 'var(--ion-spacing-xxxs)',
start: 'var(--ion-spacing-0)',
},
},
// Targets `p`
paragraph: {
color: components.item.paragraph.color,
overflow: 'inherit',
margin: {
top: 'var(--ion-spacing-0)',
end: 'var(--ion-spacing-0)',
bottom: 'var(--ion-spacing-xxxs)',
start: 'var(--ion-spacing-0)',
},
font: {
size: dynamicFont(14),
},
text: {
overflow: 'initial',
},
},
},
},
};

View File

@@ -0,0 +1,159 @@
/**
* Tokens shared between the MD theme files or are being used in the files
* multiple times.
*/
import { mix, rgba } from '../../utils/theme';
const hexColors = {
white: '#ffffff',
black: '#000000',
};
const rgbColors = {
white: '255, 255, 255',
black: '0, 0, 0',
};
export const colors = {
...hexColors,
backgroundColor: `var(--ion-background-color, ${hexColors.white})`,
backgroundColorRgb: `var(--ion-background-color-rgb, ${rgbColors.white})`,
textColor: `var(--ion-text-color, ${hexColors.black})`,
textColorRgb: `var(--ion-text-color-rgb, ${rgbColors.black})`,
};
export const fontSizes = {
itemDividerBase: 14,
};
export const components = {
item: {
padding: {
start: 'var(--ion-spacing-lg)',
},
inner: {
padding: {
end: 'var(--ion-spacing-lg)',
},
},
border: {
color: `var(--ion-item-border-color, var(--ion-border-color, var(--ion-background-color-step-150, ${rgba(
'0, 0, 0',
0.13
)})))`,
},
// ::slotted([slot="start"])
start: {
slot: {
margin: {
end: 'var(--ion-spacing-lg)',
},
},
},
// ::slotted([slot="end"])
end: {
slot: {
margin: {
start: 'var(--ion-spacing-lg)',
},
},
},
icon: {
slot: {
color: rgba(colors.textColorRgb, 0.54),
font: {
size: 24,
},
margin: {
top: 'var(--ion-spacing-md)',
bottom: 'var(--ion-spacing-md)',
},
},
start: {
slot: {
margin: {
end: 'var(--ion-spacing-xxxxl)',
},
},
},
end: {
slot: {
margin: {
start: 'var(--ion-spacing-lg)',
},
},
},
},
note: {
slot: {
font: {
size: 11,
},
padding: {
top: 'var(--ion-spacing-450)',
end: 'var(--ion-spacing-0)',
bottom: 'var(--ion-spacing-250)',
start: 'var(--ion-spacing-0)',
},
},
},
avatar: {
width: 'var(--ion-scaling-xl)',
height: 'var(--ion-scaling-xl)',
},
thumbnail: {
width: 'var(--ion-scaling-1400)',
height: 'var(--ion-scaling-1400)',
},
// media: avatar and thumbnail
media: {
slot: {
margin: {
top: 'var(--ion-spacing-sm)',
end: null,
bottom: 'var(--ion-spacing-sm)',
start: null,
},
},
start: {
slot: {
margin: {
end: 'var(--ion-spacing-lg)',
start: null,
},
},
},
end: {
slot: {
margin: {
end: null,
start: 'var(--ion-spacing-lg)',
},
},
},
},
paragraph: {
color: `var(--ion-text-color-400, ${mix(colors.white, colors.black, '40%')})`,
},
},
};

View File

@@ -1,4 +1,4 @@
import { rgba, currentColor, clamp } from '../../utils/theme';
import { rgba, currentColor, clamp, mix, dynamicFont } from '../../utils/theme';
import { defaultTheme as baseDefaultTheme } from '../base/default.tokens';
import type { DefaultTheme } from '../themes.interfaces';
@@ -6,11 +6,7 @@ import { darkTheme } from './dark.tokens';
import { highContrastDarkTheme } from './high-contrast-dark.tokens';
import { highContrastTheme } from './high-contrast.tokens';
import { lightTheme } from './light.tokens';
const fontSizes = {
chipBase: 14,
root: parseFloat(baseDefaultTheme.fontSize!.root as string),
};
import { fontSizes, colors, components } from './shared.tokens';
export const defaultTheme: DefaultTheme = {
...baseDefaultTheme,
@@ -358,6 +354,7 @@ export const defaultTheme: DefaultTheme = {
size: `${(20 / fontSizes.chipBase).toFixed(2)}em`,
},
// Targets `:first-child`
leading: {
margin: {
top: '-4px',
@@ -367,6 +364,7 @@ export const defaultTheme: DefaultTheme = {
},
},
// Targets `:last-child`
trailing: {
margin: {
top: '-4px',
@@ -381,6 +379,7 @@ export const defaultTheme: DefaultTheme = {
height: `${(24 / fontSizes.chipBase).toFixed(2)}em`,
width: `${(24 / fontSizes.chipBase).toFixed(2)}em`,
// Targets `:first-child`
leading: {
margin: {
top: '-4px',
@@ -390,6 +389,7 @@ export const defaultTheme: DefaultTheme = {
},
},
// Targets `:last-child`
trailing: {
margin: {
top: '-4px',
@@ -400,5 +400,179 @@ export const defaultTheme: DefaultTheme = {
},
},
},
IonItemDivider: {
background: `var(--ion-background-color-step-100, ${mix(colors.black, colors.white, '90%')})`,
color: `var(--ion-text-color-step-150, ${mix(colors.white, colors.black, '85%')})`,
minHeight: 'var(--ion-scaling-sm)',
padding: {
start: components.item.padding.start,
},
inner: {
padding: {
end: `calc(${components.item.padding.end} * 0.5)`,
},
},
font: {
size: dynamicFont(17),
weight: 'var(--ion-font-weight-semi-bold)',
},
leading: {
// Targets `:host([slot="start"])`
anchor: {
margin: {
top: components.item.slot.start.margin.top,
end: components.item.slot.start.margin.end,
bottom: components.item.slot.start.margin.bottom,
start: components.item.slot.start.margin.start,
},
},
},
// Targets `ion-icon`
icon: {
leading: {
// Targets `ion-icon[slot="start"]`
edge: {
margin: {
top: components.item.icon.slot.margin.top,
bottom: components.item.icon.slot.margin.bottom,
},
},
},
trailing: {
// Targets `ion-icon[slot="end"]`
edge: {
margin: {
top: components.item.icon.slot.margin.top,
bottom: components.item.icon.slot.margin.bottom,
},
},
},
},
// Targets `h1`
header1: {
margin: {
top: 'var(--ion-spacing-0)',
end: 'var(--ion-spacing-0)',
bottom: 'var(--ion-spacing-xxxs)',
start: 'var(--ion-spacing-0)',
},
},
// Targets `h2`
header2: {
margin: {
top: 'var(--ion-spacing-0)',
end: 'var(--ion-spacing-0)',
bottom: 'var(--ion-spacing-xxxs)',
start: 'var(--ion-spacing-0)',
},
// Targets `:last-child`
trailing: {
margin: {
bottom: 'var(--ion-spacing-0)',
},
},
},
// Targets `h3`
header3: {
margin: {
top: 'var(--ion-spacing-0)',
end: 'var(--ion-spacing-0)',
bottom: 'var(--ion-spacing-75)',
start: 'var(--ion-spacing-0)',
},
trailing: {
margin: {
bottom: 'var(--ion-spacing-0)',
},
},
},
// Targets `h4`
header4: {
margin: {
top: 'var(--ion-spacing-0)',
end: 'var(--ion-spacing-0)',
bottom: 'var(--ion-spacing-75)',
start: 'var(--ion-spacing-0)',
},
trailing: {
margin: {
bottom: 'var(--ion-spacing-0)',
},
},
},
// Targets `h5`
header5: {
margin: {
top: 'var(--ion-spacing-0)',
end: 'var(--ion-spacing-0)',
bottom: 'var(--ion-spacing-75)',
start: 'var(--ion-spacing-0)',
},
trailing: {
margin: {
bottom: 'var(--ion-spacing-0)',
},
},
},
// Targets `h6`
header6: {
margin: {
top: 'var(--ion-spacing-0)',
end: 'var(--ion-spacing-0)',
bottom: 'var(--ion-spacing-75)',
start: 'var(--ion-spacing-0)',
},
trailing: {
margin: {
bottom: 'var(--ion-spacing-0)',
},
},
},
// Targets `p`
paragraph: {
color: components.item.paragraph.color,
overflow: 'inherit',
margin: {
top: components.item.paragraph.margin.top,
end: components.item.paragraph.margin.end,
bottom: components.item.paragraph.margin.bottom,
start: components.item.paragraph.margin.start,
},
font: {
size: components.item.paragraph.font.size,
},
text: {
overflow: 'inherit',
},
trailing: {
margin: {
bottom: 'var(--ion-spacing-0)',
},
},
},
},
},
};

View File

@@ -0,0 +1,84 @@
/**
* Tokens shared between the iOS theme files or are being used in the files
* multiple times.
*/
import { dynamicFont } from '../../utils/theme';
import { defaultTheme as baseDefaultTheme } from '../base/default.tokens';
const hexColors = {
white: '#ffffff',
black: '#000000',
};
const rgbColors = {
white: '255, 255, 255',
black: '0, 0, 0',
};
export const colors = {
...hexColors,
backgroundColor: `var(--ion-background-color, ${hexColors.white})`,
backgroundColorRgb: `var(--ion-background-color-rgb, ${rgbColors.white})`,
textColor: `var(--ion-text-color, ${hexColors.black})`,
textColorRgb: `var(--ion-text-color-rgb, ${rgbColors.black})`,
};
export const fontSizes = {
chipBase: 14,
root: parseFloat(baseDefaultTheme.fontSize!.root as string),
};
export const components = {
item: {
padding: {
start: 'var(--ion-spacing-lg)',
end: 'var(--ion-spacing-lg)',
},
inner: {
padding: {
end: 'var(--ion-spacing-lg)',
},
},
// Targets `:host([slot="start"])`
slot: {
start: {
margin: {
top: 'var(--ion-spacing-xxxs)',
// Same as `padding-end`
end: 'var(--ion-spacing-lg)',
bottom: 'var(--ion-spacing-xxxs)',
start: 'var(--ion-spacing-0)',
},
},
},
icon: {
slot: {
margin: {
top: '7px',
bottom: '7px',
},
},
},
paragraph: {
color: 'var(--ion-text-color-step-550, #a3a3a3)',
margin: {
top: 'var(--ion-spacing-0)',
end: 'var(--ion-spacing-0)',
bottom: 'var(--ion-spacing-xxxs)',
start: 'var(--ion-spacing-0)',
},
font: {
size: dynamicFont(14),
},
},
},
};

View File

@@ -1,4 +1,4 @@
import { rgba, currentColor } from '../../utils/theme';
import { rgba, currentColor, mix, dynamicFont } from '../../utils/theme';
import { defaultTheme as baseDefaultTheme } from '../base/default.tokens';
import type { DefaultTheme } from '../themes.interfaces';
@@ -6,11 +6,7 @@ import { darkTheme } from './dark.tokens';
import { highContrastDarkTheme } from './high-contrast-dark.tokens';
import { highContrastTheme } from './high-contrast.tokens';
import { lightTheme } from './light.tokens';
const fontSizes = {
chipBase: 14,
root: parseFloat(baseDefaultTheme.fontSize!.root as string),
};
import { fontSizes, colors, components } from './shared.tokens';
export const defaultTheme: DefaultTheme = {
...baseDefaultTheme,
@@ -348,6 +344,7 @@ export const defaultTheme: DefaultTheme = {
},
},
// Targets `ion-icon`
icon: {
color: rgba('var(--ion-text-color-rgb, 0, 0, 0)', 0.54),
@@ -374,6 +371,7 @@ export const defaultTheme: DefaultTheme = {
},
},
// Targets `ion-avatar`
avatar: {
height: `${(24 / fontSizes.chipBase).toFixed(2)}em`,
width: `${(24 / fontSizes.chipBase).toFixed(2)}em`,
@@ -397,5 +395,286 @@ export const defaultTheme: DefaultTheme = {
},
},
},
IonItemDivider: {
background: `var(--ion-background-color, ${colors.white})`,
color: `var(--ion-text-color-step-600, ${mix(colors.white, colors.black, '40%')})`,
minHeight: 'var(--ion-scaling-750)',
zIndex: 100,
padding: {
top: 'var(--ion-spacing-0)',
end: 'var(--ion-spacing-0)',
bottom: 'var(--ion-spacing-0)',
start: components.item.padding.start,
},
inner: {
padding: {
top: 'var(--ion-spacing-0)',
end: components.item.inner.padding.end,
bottom: 'var(--ion-spacing-0)',
start: 'var(--ion-spacing-0)',
},
},
border: {
bottom: `1px solid ${components.item.border.color}`,
},
font: {
size: dynamicFont(fontSizes.itemDividerBase),
},
leading: {
// Targets `::slotted([slot="start"])`
edge: {
margin: {
end: components.item.start.slot.margin.end,
},
},
},
trailing: {
// Targets `::slotted([slot="end"])`
edge: {
margin: {
start: components.item.end.slot.margin.start,
},
},
},
// Targets `ion-label`
label: {
margin: {
top: '13px',
end: 'var(--ion-spacing-0)',
bottom: 'var(--ion-spacing-250)',
start: 'var(--ion-spacing-0)',
},
},
// Targets `ion-icon`
icon: {
font: {
/**
* The icon's font size should use em units to support
* font scaling but evaluate to 24px at 100% font size.
* The value in em units is calculated by dividing
* the icon's font size in pixels by the item divider's
* font size in pixels.
* e.g. 24px / 14px = 1.7142857143em
*/
size: `${components.item.icon.slot.font.size / fontSizes.itemDividerBase}em`,
},
leading: {
// Targets `ion-icon[slot="start"]`
edge: {
margin: {
top: components.item.icon.slot.margin.top,
end: components.item.icon.start.slot.margin.end,
bottom: components.item.icon.slot.margin.bottom,
},
},
},
trailing: {
// Targets `ion-icon[slot="end"]`
edge: {
margin: {
top: components.item.icon.slot.margin.top,
start: components.item.icon.end.slot.margin.start,
bottom: components.item.icon.slot.margin.bottom,
},
},
},
// Default non-semantic states
default: {
color: components.item.icon.slot.color,
},
// Any of the semantic colors like primary, secondary, etc.
semantic: {
default: {
color: currentColor('contrast'),
},
},
},
// Targets `ion-note`
note: {
align: {
self: 'flex-start',
},
font: {
size: dynamicFont(components.item.note.slot.font.size),
},
margin: {
top: 'var(--ion-spacing-0)',
end: 'var(--ion-spacing-0)',
bottom: 'var(--ion-spacing-0)',
start: 'var(--ion-spacing-0)',
},
padding: {
top: components.item.note.slot.padding.top,
end: components.item.note.slot.padding.end,
bottom: components.item.note.slot.padding.bottom,
start: components.item.note.slot.padding.start,
},
},
// Targets `ion-avatar`
avatar: {
height: components.item.avatar.height,
width: components.item.avatar.width,
margin: {
top: components.item.media.slot.margin.top,
end: components.item.media.slot.margin.end,
bottom: components.item.media.slot.margin.bottom,
start: components.item.media.slot.margin.start,
},
leading: {
// Targets `ion-avatar[slot="start"]`
edge: {
margin: {
start: components.item.media.start.slot.margin.start,
end: components.item.media.start.slot.margin.end,
},
},
},
trailing: {
// Targets `ion-avatar[slot="end"]`
edge: {
margin: {
start: components.item.media.end.slot.margin.start,
end: components.item.media.end.slot.margin.end,
},
},
},
},
// Targets `ion-thumbnail`
thumbnail: {
// TODO: switch from size to height and width
height: components.item.thumbnail.height,
width: components.item.thumbnail.width,
margin: {
top: components.item.media.slot.margin.top,
end: components.item.media.slot.margin.end,
bottom: components.item.media.slot.margin.bottom,
start: components.item.media.slot.margin.start,
},
leading: {
// Targets `ion-thumbnail[slot="start"]`
edge: {
margin: {
start: components.item.media.start.slot.margin.start,
end: components.item.media.start.slot.margin.end,
},
},
},
trailing: {
// Targets `ion-thumbnail[slot="end"]`
edge: {
margin: {
start: components.item.media.end.slot.margin.start,
end: components.item.media.end.slot.margin.end,
},
},
},
},
// Targets `h1`
header1: {
margin: {
top: 'var(--ion-spacing-0)',
end: 'var(--ion-spacing-0)',
bottom: 'var(--ion-spacing-xxxs)',
start: 'var(--ion-spacing-0)',
},
},
// Targets `h2`
header2: {
margin: {
top: 'var(--ion-spacing-xxxs)',
end: 'var(--ion-spacing-0)',
bottom: 'var(--ion-spacing-xxxs)',
start: 'var(--ion-spacing-0)',
},
},
// Targets `h3`
header3: {
margin: {
top: 'var(--ion-spacing-xxxs)',
end: 'var(--ion-spacing-0)',
bottom: 'var(--ion-spacing-xxxs)',
start: 'var(--ion-spacing-0)',
},
},
// Targets `h4`
header4: {
margin: {
top: 'var(--ion-spacing-xxxs)',
end: 'var(--ion-spacing-0)',
bottom: 'var(--ion-spacing-xxxs)',
start: 'var(--ion-spacing-0)',
},
},
// Targets `h5`
header5: {
margin: {
top: 'var(--ion-spacing-xxxs)',
end: 'var(--ion-spacing-0)',
bottom: 'var(--ion-spacing-xxxs)',
start: 'var(--ion-spacing-0)',
},
},
// Targets `h6`
header6: {
margin: {
top: 'var(--ion-spacing-xxxs)',
end: 'var(--ion-spacing-0)',
bottom: 'var(--ion-spacing-xxxs)',
start: 'var(--ion-spacing-0)',
},
},
// Targets `p`
paragraph: {
color: components.item.paragraph.color,
overflow: 'inherit',
margin: {
top: 'var(--ion-spacing-0)',
end: 'var(--ion-spacing-0)',
bottom: 'var(--ion-spacing-xxxs)',
start: 'var(--ion-spacing-0)',
},
font: {
size: dynamicFont(14),
},
text: {
overflow: 'initial',
},
},
},
},
};

View File

@@ -0,0 +1,162 @@
/**
* Tokens shared between the MD theme files or are being used in the files
* multiple times.
*/
import { mix, rgba } from '../../utils/theme';
import { defaultTheme as baseDefaultTheme } from '../base/default.tokens';
const hexColors = {
white: '#ffffff',
black: '#000000',
};
const rgbColors = {
white: '255, 255, 255',
black: '0, 0, 0',
};
export const colors = {
...hexColors,
backgroundColor: `var(--ion-background-color, ${hexColors.white})`,
backgroundColorRgb: `var(--ion-background-color-rgb, ${rgbColors.white})`,
textColor: `var(--ion-text-color, ${hexColors.black})`,
textColorRgb: `var(--ion-text-color-rgb, ${rgbColors.black})`,
};
export const fontSizes = {
chipBase: 14,
root: parseFloat(baseDefaultTheme.fontSize!.root as string),
itemDividerBase: 14,
};
export const components = {
item: {
padding: {
start: 'var(--ion-spacing-lg)',
},
inner: {
padding: {
end: 'var(--ion-spacing-lg)',
},
},
border: {
color: `var(--ion-item-border-color, var(--ion-border-color, var(--ion-background-color-step-150, ${rgba(
'0, 0, 0',
0.13
)})))`,
},
// ::slotted([slot="start"])
start: {
slot: {
margin: {
end: 'var(--ion-spacing-lg)',
},
},
},
// ::slotted([slot="end"])
end: {
slot: {
margin: {
start: 'var(--ion-spacing-lg)',
},
},
},
icon: {
slot: {
color: rgba(colors.textColorRgb, 0.54),
font: {
size: 24,
},
margin: {
top: 'var(--ion-spacing-md)',
bottom: 'var(--ion-spacing-md)',
},
},
start: {
slot: {
margin: {
end: 'var(--ion-spacing-xxxxl)',
},
},
},
end: {
slot: {
margin: {
start: 'var(--ion-spacing-lg)',
},
},
},
},
note: {
slot: {
font: {
size: 11,
},
padding: {
top: 'var(--ion-spacing-450)',
end: 'var(--ion-spacing-0)',
bottom: 'var(--ion-spacing-250)',
start: 'var(--ion-spacing-0)',
},
},
},
avatar: {
width: 'var(--ion-scaling-xl)',
height: 'var(--ion-scaling-xl)',
},
thumbnail: {
width: 'var(--ion-scaling-1400)',
height: 'var(--ion-scaling-1400)',
},
// media: avatar and thumbnail
media: {
slot: {
margin: {
top: 'var(--ion-spacing-sm)',
end: null,
bottom: 'var(--ion-spacing-sm)',
start: null,
},
},
start: {
slot: {
margin: {
end: 'var(--ion-spacing-lg)',
start: null,
},
},
},
end: {
slot: {
margin: {
end: null,
start: 'var(--ion-spacing-lg)',
},
},
},
},
paragraph: {
color: `var(--ion-text-color-400, ${mix(colors.white, colors.black, '40%')})`,
},
},
};

View File

@@ -282,6 +282,7 @@ export type DefaultTheme = BaseTheme & {
type Components = {
IonChip?: IonChipRecipe;
IonItemDivider?: any;
IonCard?: any;
IonItem?: any;

View File

@@ -633,3 +633,22 @@ export function currentColor(variation: string, alpha: number | string | null =
export function clamp(min: number | string, val: number | string, max: number | string): string {
return `clamp(${min}, ${val}, ${max})`;
}
const baselinePixelSize = 16;
const baselineUnit = 'rem';
/**
* Converts a pixel value to a dynamic unit (defaulting to rem).
*
* Examples:
* - `dynamicFont(16)` returns `'1.000rem'`
* - `dynamicFont(20, 'em')` returns `'1.250em'`
*
* @internal
* @param size - The numeric pixel value.
* @param unit - The CSS unit string.
* @returns The calculated CSS value string.
*/
export const dynamicFont = (size: number, unit: string = baselineUnit): string => {
return `${size / baselinePixelSize}${unit}`;
};