feat(item-divider): add recipe and tokens#31009
feat(item-divider): add recipe and tokens#31009thetaPC wants to merge 21 commits intoionic-modularfrom
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
| // Slotted Content | ||
| // -------------------------------------------------- | ||
|
|
||
| ::slotted(h1) { |
There was a problem hiding this comment.
My following comment applies to all the slotted headers:
I noticed that the margins are being overridden by a high level style for headers. I'm not sure when the margins declared in the item divider ever get applied. This is also being overridden in main. Can we just get rid of the slotted header styles?
There was a problem hiding this comment.
Noticed that color functions were never used.
| // TODO(FW-6862): separate width and height tokens for thumbnails | ||
| --size: var(--ion-item-divider-thumbnail-width); |
There was a problem hiding this comment.
To maximize customization, we should split the size into height and width.
| * @prop --size: Size of the thumbnail | ||
| */ | ||
| --size: 48px; | ||
| --size: 48px; // TODO(FW-6862): separate width and height tokens for thumbnails |
There was a problem hiding this comment.
To maximize customization, we should split the size into height and width.
| width: var(--size, 48px); | ||
| height: var(--size, 48px); |
There was a problem hiding this comment.
We're using --ion-item-divider-thumbnail-width to let dividers control thumbnail sizes. The problem is that if a theme doesn't provide this token, the CSS breaks instead of falling back. We can't use revert-layer because the 'original' size we want to return to is actually another variable, not a fixed value. The cleanest fix is to just bake the default 48px right into the variable's fallback.
|
|
||
| :host([slot="start"]) { | ||
| @include mixins.margin( | ||
| var(--ion-item-divider-leading-anchor-margin-top, revert-layer), |
There was a problem hiding this comment.
revert-layer is being used so that if we don't provide a modular CSS variable, we give the component permission to just keep doing whatever it was doing originally.
| leading?: { | ||
| // Targets `:host([slot="start"])` | ||
| anchor?: { | ||
| margin?: IonMargin; | ||
| }; | ||
|
|
||
| // Targets `::slotted([slot="start"])` | ||
| edge?: { | ||
| margin?: IonMargin; | ||
| }; | ||
| }; | ||
|
|
||
| trailing?: { | ||
| // Targets `::slotted([slot="end"])` | ||
| edge?: { | ||
| margin?: IonMargin; | ||
| }; | ||
| }; |
There was a problem hiding this comment.
💡 Naming Convention: Anchors & Edges
To maintain consistency with the Ionic Modular architecture established in ion-chip, this component adopts a shared spatial vocabulary. While chips use DOM order (:first-child), dividers use named slots (slot="start/end"). The following naming convention bridges that gap:
- Anchors (
:host): Refers to the component's own placement within a parent container.leading-anchor: Targets:host([slot="start"]).trailing-anchor: Targets:host([slot="end"]).
- Edges (
::slotted): Refers to content placed at the boundaries of the component itself.leading-edge: Targets::slotted([slot="start"]).trailing-edge: Targets::slotted([slot="end"]).
Why this matters:
This mirrors the leading (:first-child) and trailing (:last-child) pattern used in chips. By using leading/trailing instead of left/right or start/end, we create a less confusing structure that developers can use with ease.
| default?: { | ||
| color?: string; | ||
| }; | ||
|
|
||
| semantic?: { | ||
| default?: { | ||
| color?: string; | ||
| }; | ||
| }; |
There was a problem hiding this comment.
This follows the same structure established in chip when it came to default vs semantic colors.
There was a problem hiding this comment.
Added:
- Item divider with icons at the start and end slot
- Item divider with a thumbnail at the start
- Item divider with paragrahs
| import { colors as baseColors } from '../base/shared.tokens'; | ||
|
|
||
| export const components = { | ||
| item: { |
There was a problem hiding this comment.
Item divider depends on the values set on item. This file was created to keep track of those values and other components that need to be shared with each other.
ShaneK
left a comment
There was a problem hiding this comment.
This is looking really good so far! I just noticed a few inconsistencies and had question. I may be way off base on some of what I think I found, but just making sure I understand things right!
| var(--ion-item-divider-note-margin-start) | ||
| ); | ||
|
|
||
| align-self: flex-start; |
There was a problem hiding this comment.
Shouldn't this use --ion-item-divider-note-align-self? You're hard coding it here, but there's a CSS variable prepared for it that's not being consumed?
|
|
||
| border: { | ||
| color: `var(--ion-item-border-color, var(--ion-border-color, var(--ion-background-color-step-150, ${rgba( | ||
| '0, 0, 0', |
There was a problem hiding this comment.
Should this be using baseColors.textColorRgb instead of the hardcoded '0, 0, 0'? The md version uses baseColors.textColorRgb for this same border color which lets it respond to --ion-text-color-rgb customization, but this one won't. You're already using baseColors.textColorRgb correctly for the icon color on line 47 so I'm guessing this is just a copy-paste miss?
| */ | ||
| export const dynamicFont = (size: number, unit: string | undefined = baselineUnit): string => { | ||
| let baselinePixelSize = 16; | ||
|
|
There was a problem hiding this comment.
Does this path actually work? config is a plain object without a .get() method, and this runs at import time before Ionic.config is initialized anyway. So configRootFontSize will always be undefined and you'll always fall back to 16. Is this meant to hook into something that doesn't exist yet, or should we just drop the window check and use the constant?
| } | ||
|
|
||
| // Paragraph | ||
| ::slotted(p) { |
There was a problem hiding this comment.
Was dropping line-height: normal intentional here? Both the old iOS and MD files had it on ::slotted(p). Without it paragraphs will just inherit from the parent, which is probably fine most of the time but it's a behavior change.
| 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); |
There was a problem hiding this comment.
iOS previously set font-weight: 600 explicitly but the iOS recipe doesn't define a font.weight token. Without a fallback value here this becomes invalid at computed-value time and falls to inherited/initial (probably normal/400). Should iOS define font.weight in its recipe, or should this have a fallback like var(--ion-item-divider-font-weight, normal)?
| }; | ||
| }; | ||
|
|
||
| trailing?: { |
There was a problem hiding this comment.
Something I noticed: trailing means slot="end" here (and in icon.trailing, avatar.trailing, etc.), but down on header2.trailing (line 141) it means :last-child which is the same meaning chip uses for trailing. So the same word means two different things within the same recipe. The anchor/edge vocabulary helps but I think this could still trip people up when working across recipes. Worth thinking about whether there's a clearer way to distinguish these?
| }, | ||
|
|
||
| // Targets `:host([slot="start"])` | ||
| slot: { |
There was a problem hiding this comment.
The nesting order here is item.slot.start.margin but ionic and md use item.start.slot.margin for the semantically equivalent thing. I know they target different CSS selectors (:host([slot]) vs ::slotted([slot])) so different naming is defensible, but if someone's adding a new theme or reading across implementations this is going to be confusing. Could we normalize the path structure across all three?
Issue number: internal
What is the current behavior?
Component styles for
ion-item-dividerare fragmented across multiple files (ios, md). Theionictheme uses themdstyles. Developers were restricted to those themes and how those themes structured the logic and styles.What is the new behavior?
item-divider.scssfile that consumes CSS variables, ensuring a single source of truth for component logic.item-divider.interfaces.tsDoes this introduce a breaking change?
This PR introduces a breaking change to how
IonItemDivideris styled. Existing manual CSS overrides targeting internal item divider structures or old token names will no longer work due to the shift to thew new token hierarchy and a unified base SCSS file.Migration Path:
Token Updates: Update any custom theme configurations to match the new nested structure.
CSS Overrides: Ensure selectors align with the new slotted element logic and variable names (e.g.,
--ion-item-divider-background).--backgroundshould no longer be used. Setting the value,IonDivider.background, within the tokens file should be used to change the background.ion-divider.mdOther information
This PR significantly improves the developer experience for theming. By moving logic into
default.tokens.tsand away from hardcoded SCSS functions, designers and developers can now override styles (like the size on a slotted avatar) purely through token configuration.Preview