ionbase-ui 0.26.0 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/Avatar.d.ts +33 -0
- package/dist/components/Avatar.d.ts.map +1 -1
- package/dist/components/Avatar.js +43 -4
- package/dist/components/Avatar.js.map +1 -1
- package/dist/components/AvatarGradient.d.ts +44 -0
- package/dist/components/AvatarGradient.d.ts.map +1 -0
- package/dist/components/AvatarGradient.js +32 -0
- package/dist/components/AvatarGradient.js.map +1 -0
- package/dist/components/index.d.ts +3 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +1 -0
- package/dist/components/index.js.map +1 -1
- package/dist/figma-descriptions.json +34 -29
- package/dist/figma-map.json +94 -3
- package/dist/meta/Avatar.json +137 -7
- package/dist/meta/AvatarGradient.json +202 -0
- package/dist/meta/AvatarGroup.json +26 -3
- package/dist/meta/Button.json +4 -0
- package/dist/meta/components.json +370 -11
- package/dist/meta/contrast.json +591 -283
- package/dist/meta/index.json +40 -1
- package/dist/meta/patterns/index.json +1 -1
- package/dist/styles/avatar-gradient.css +218 -0
- package/dist/styles/avatar.css +185 -19
- package/dist/styles/button.css +47 -18
- package/dist/styles/index.css +1 -0
- package/dist/styles/tokens/base.css +26 -26
- package/dist/styles/tokens/theme-dark.css +6 -6
- package/dist/tokens/index.d.ts +10 -10
- package/dist/tokens/index.js +10 -10
- package/llms.txt +2 -2
- package/package.json +1 -1
package/dist/meta/index.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"package": "ionbase-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.0",
|
|
4
4
|
"usage": "Pick a component here, then read dist/meta/<Name>.json for its full contract.",
|
|
5
5
|
"hooks": [
|
|
6
6
|
"useToast"
|
|
@@ -97,11 +97,50 @@
|
|
|
97
97
|
"shape": [
|
|
98
98
|
"circle",
|
|
99
99
|
"square"
|
|
100
|
+
],
|
|
101
|
+
"topIndicator": [
|
|
102
|
+
"neutral",
|
|
103
|
+
"primary",
|
|
104
|
+
"success",
|
|
105
|
+
"warning",
|
|
106
|
+
"error",
|
|
107
|
+
"information"
|
|
108
|
+
],
|
|
109
|
+
"bottomIndicator": [
|
|
110
|
+
"neutral",
|
|
111
|
+
"primary",
|
|
112
|
+
"success",
|
|
113
|
+
"warning",
|
|
114
|
+
"error",
|
|
115
|
+
"information"
|
|
100
116
|
]
|
|
101
117
|
},
|
|
102
118
|
"detail": "dist/meta/Avatar.json",
|
|
103
119
|
"hasIntent": true
|
|
104
120
|
},
|
|
121
|
+
"AvatarGradient": {
|
|
122
|
+
"summary": "Avatar's initials-only sibling: a tinted, bevelled disc in one of seven colours, for people with no photo.",
|
|
123
|
+
"status": "stable",
|
|
124
|
+
"variants": {
|
|
125
|
+
"size": [
|
|
126
|
+
"sm",
|
|
127
|
+
"md",
|
|
128
|
+
"lg",
|
|
129
|
+
"mini"
|
|
130
|
+
],
|
|
131
|
+
"color": [
|
|
132
|
+
"slate",
|
|
133
|
+
"blue",
|
|
134
|
+
"violet",
|
|
135
|
+
"pink",
|
|
136
|
+
"orange",
|
|
137
|
+
"green",
|
|
138
|
+
"red"
|
|
139
|
+
]
|
|
140
|
+
},
|
|
141
|
+
"detail": "dist/meta/AvatarGradient.json",
|
|
142
|
+
"hasIntent": true
|
|
143
|
+
},
|
|
105
144
|
"AvatarGroup": {
|
|
106
145
|
"summary": "Overlapping Avatars with a `+N` overflow, for showing several people in one row's width.",
|
|
107
146
|
"status": "stable",
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Avatar Gradient
|
|
3
|
+
*
|
|
4
|
+
* Measured from Figma `Avatar Gradient` (1054:305), re-read after the
|
|
5
|
+
* 2026-08-28 redesign. Circle only — the set has no Shape axis, because a
|
|
6
|
+
* gradient this directional does not survive a corner.
|
|
7
|
+
*
|
|
8
|
+
* Sizes and the type ramp are Avatar's, including regular weight at Mini:
|
|
9
|
+
*
|
|
10
|
+
* mini 24 type 12/18 regular
|
|
11
|
+
* small 32 type 14/20 medium
|
|
12
|
+
* medium 40 type 16/24 medium
|
|
13
|
+
* large 48 type 20/32 medium
|
|
14
|
+
*
|
|
15
|
+
* IT USED TO BE THE OTHER WAY UP, AND THE CONTRAST GATE IS WHY IT IS NOT
|
|
16
|
+
*
|
|
17
|
+
* The first version was a saturated disc — `color/<hue>/500` to `/600` — with
|
|
18
|
+
* white initials. Four of the seven failed WCAG AA at 3.5 to 3.8:1, and the fix
|
|
19
|
+
* was made in Figma rather than here: the disc is now a pale tint and the
|
|
20
|
+
* initials carry the colour. Do not "restore" the saturated version. It is not
|
|
21
|
+
* a style preference that changed.
|
|
22
|
+
*
|
|
23
|
+
* THREE RUNGS, THREE TOKENS
|
|
24
|
+
*
|
|
25
|
+
* 0% color/<hue>/300 the deepest tint, at the TOP
|
|
26
|
+
* 60% color/<hue>/200
|
|
27
|
+
* 100% color/<hue>/50 nearly white, at the bottom
|
|
28
|
+
*
|
|
29
|
+
* Every stop is variable-bound in Figma, so all three are real tokens and the
|
|
30
|
+
* `color-mix` midpoint the old two-rung version needed is gone. Slate is the
|
|
31
|
+
* gray ramp; every other colour is its own hue at the same three rungs, which
|
|
32
|
+
* is what makes this a system rather than seven pictures.
|
|
33
|
+
*/
|
|
34
|
+
.ion-avatar-gradient {
|
|
35
|
+
--ion-avatar-gradient-size: var(--spacing-40);
|
|
36
|
+
--ion-avatar-gradient-font-size: var(--type-body);
|
|
37
|
+
--ion-avatar-gradient-line-height: var(--type-body-line-height);
|
|
38
|
+
--ion-avatar-gradient-font-weight: var(--font-weight-medium);
|
|
39
|
+
|
|
40
|
+
/* Slate, which is also the block default — Figma's default variant. */
|
|
41
|
+
--ion-avatar-gradient-from: var(--color-gray-300);
|
|
42
|
+
--ion-avatar-gradient-mid: var(--color-gray-200);
|
|
43
|
+
--ion-avatar-gradient-to: var(--color-gray-50);
|
|
44
|
+
|
|
45
|
+
/*
|
|
46
|
+
* The initials, from the primitive ramp rather than a semantic text token.
|
|
47
|
+
*
|
|
48
|
+
* NOT AN OVERSIGHT, AND DO NOT "FIX" IT TO `--text-*`. The disc under this
|
|
49
|
+
* text is built from primitives, so it does not theme; a semantic foreground
|
|
50
|
+
* would flip in the dark theme and leave the background alone. An earlier
|
|
51
|
+
* draft did exactly that and the contrast gate caught it at 1.05:1. A
|
|
52
|
+
* foreground has to theme exactly as much as the background it sits on.
|
|
53
|
+
*
|
|
54
|
+
* In Figma these are bound to a `palette/1`..`palette/7` collection the token
|
|
55
|
+
* export does not yet cover. Each one resolves to the primitive named here,
|
|
56
|
+
* so nothing is lost today — see AGENTS.md.
|
|
57
|
+
*/
|
|
58
|
+
--ion-avatar-gradient-text: var(--color-gray-600);
|
|
59
|
+
|
|
60
|
+
/*
|
|
61
|
+
* The sheen: a white radial highlight low and centre, over the linear
|
|
62
|
+
* gradient. Identical in proportion at all four sizes — Figma expresses it as
|
|
63
|
+
* a userSpaceOnUse radial scaled per size, and the ratios come out the same
|
|
64
|
+
* to three decimals, so it is written once as percentages.
|
|
65
|
+
*/
|
|
66
|
+
--ion-avatar-gradient-sheen: 18%;
|
|
67
|
+
--ion-avatar-gradient-sheen-mid: 3.6%;
|
|
68
|
+
|
|
69
|
+
/*
|
|
70
|
+
* Bevel. Two inner shadows and no drop shadow — the redesign dropped the
|
|
71
|
+
* lift, and these no longer scale with the box the way they did: Figma now
|
|
72
|
+
* carries the same 1px / -2px offsets at all four sizes.
|
|
73
|
+
*
|
|
74
|
+
* A LOCAL, BECAUSE A RAW COLOUR IN A `box-shadow` DECLARATION IS BANNED — and
|
|
75
|
+
* the ban is right. Its reason is that a raw colour is a place dark mode has
|
|
76
|
+
* to be fixed by hand, once per occurrence; naming it here makes this file
|
|
77
|
+
* exactly one such place instead of four, which is the same trade
|
|
78
|
+
* `elevation.css` makes for the shared ramp. These are local Figma effects,
|
|
79
|
+
* not effect STYLES, so they are not in that ramp and cannot be: the exporter
|
|
80
|
+
* only sees named styles, and there is nothing here for it to name.
|
|
81
|
+
*/
|
|
82
|
+
--ion-avatar-gradient-bevel:
|
|
83
|
+
inset 0 -2px 4px 0 rgb(0 0 0 / 12%),
|
|
84
|
+
inset 0 1px 1.4px 0 rgb(255 255 255 / 24%);
|
|
85
|
+
|
|
86
|
+
display: inline-flex;
|
|
87
|
+
align-items: center;
|
|
88
|
+
justify-content: center;
|
|
89
|
+
flex-shrink: 0;
|
|
90
|
+
overflow: hidden;
|
|
91
|
+
width: var(--ion-avatar-gradient-size);
|
|
92
|
+
height: var(--ion-avatar-gradient-size);
|
|
93
|
+
border-radius: var(--radius-full);
|
|
94
|
+
color: var(--ion-avatar-gradient-text);
|
|
95
|
+
|
|
96
|
+
/*
|
|
97
|
+
* The flat colour under the gradient is the `to` end, and that choice is
|
|
98
|
+
* load-bearing twice over. It is the fallback if `background-image` never
|
|
99
|
+
* paints, and it is the palest part of the disc — so it is also the worst
|
|
100
|
+
* case for the dark initials, which is what makes `verify-contrast` measure
|
|
101
|
+
* the pairing that actually needs measuring rather than a flattering one.
|
|
102
|
+
*
|
|
103
|
+
* It was the `from` end before the redesign, for the same reason read the
|
|
104
|
+
* other way round: the text was white then, so its worst case was the
|
|
105
|
+
* lightest stop it sat on. Flip this if the disc is ever inverted again.
|
|
106
|
+
*/
|
|
107
|
+
background-color: var(--ion-avatar-gradient-to);
|
|
108
|
+
background-image:
|
|
109
|
+
radial-gradient(
|
|
110
|
+
55.556% 64.103% at 50% 70.513%,
|
|
111
|
+
rgb(255 255 255 / var(--ion-avatar-gradient-sheen)) 0%,
|
|
112
|
+
rgb(255 255 255 / var(--ion-avatar-gradient-sheen-mid)) 60%,
|
|
113
|
+
rgb(255 255 255 / 0%) 100%
|
|
114
|
+
),
|
|
115
|
+
linear-gradient(
|
|
116
|
+
180deg,
|
|
117
|
+
var(--ion-avatar-gradient-from) 0%,
|
|
118
|
+
var(--ion-avatar-gradient-mid) 60%,
|
|
119
|
+
var(--ion-avatar-gradient-to) 100%
|
|
120
|
+
);
|
|
121
|
+
box-shadow: var(--ion-avatar-gradient-bevel);
|
|
122
|
+
font-family: var(--font-family-sans);
|
|
123
|
+
font-size: var(--ion-avatar-gradient-font-size);
|
|
124
|
+
line-height: var(--ion-avatar-gradient-line-height);
|
|
125
|
+
font-weight: var(--ion-avatar-gradient-font-weight);
|
|
126
|
+
user-select: none;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.ion-avatar-gradient--mini {
|
|
130
|
+
--ion-avatar-gradient-size: var(--spacing-24);
|
|
131
|
+
--ion-avatar-gradient-font-size: var(--type-caption);
|
|
132
|
+
--ion-avatar-gradient-line-height: var(--type-caption-line-height);
|
|
133
|
+
--ion-avatar-gradient-font-weight: var(--font-weight-regular);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.ion-avatar-gradient--sm {
|
|
137
|
+
--ion-avatar-gradient-size: var(--spacing-32);
|
|
138
|
+
--ion-avatar-gradient-font-size: var(--type-body-sm);
|
|
139
|
+
--ion-avatar-gradient-line-height: var(--type-body-sm-line-height);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.ion-avatar-gradient--lg {
|
|
143
|
+
--ion-avatar-gradient-size: var(--spacing-48);
|
|
144
|
+
--ion-avatar-gradient-font-size: var(--type-body-lg);
|
|
145
|
+
--ion-avatar-gradient-line-height: var(--type-body-lg-line-height);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/*
|
|
149
|
+
* The seven colours. Each declares its own `color` and `background-color`
|
|
150
|
+
* rather than inheriting them from the block: the contrast gate pairs what one
|
|
151
|
+
* rule declares, and a colour it has to find on a different scope resolves
|
|
152
|
+
* through the cascade to something that never renders together.
|
|
153
|
+
*
|
|
154
|
+
* All seven take `600`, including pink. It took `700` here until the 2026-08-28
|
|
155
|
+
* primitive re-export, and that was not a design choice — the pink ramp shifted
|
|
156
|
+
* down a rung, so the value this component wants (#c3135c) simply moved from
|
|
157
|
+
* `700` to `600`. Renaming under a stable name is the failure the token
|
|
158
|
+
* pipeline is built to catch; it caught this one.
|
|
159
|
+
*/
|
|
160
|
+
.ion-avatar-gradient--slate,
|
|
161
|
+
.ion-avatar-gradient--blue,
|
|
162
|
+
.ion-avatar-gradient--violet,
|
|
163
|
+
.ion-avatar-gradient--pink,
|
|
164
|
+
.ion-avatar-gradient--orange,
|
|
165
|
+
.ion-avatar-gradient--green,
|
|
166
|
+
.ion-avatar-gradient--red {
|
|
167
|
+
color: var(--ion-avatar-gradient-text);
|
|
168
|
+
background-color: var(--ion-avatar-gradient-to);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.ion-avatar-gradient--slate {
|
|
172
|
+
--ion-avatar-gradient-from: var(--color-gray-300);
|
|
173
|
+
--ion-avatar-gradient-mid: var(--color-gray-200);
|
|
174
|
+
--ion-avatar-gradient-to: var(--color-gray-50);
|
|
175
|
+
--ion-avatar-gradient-text: var(--color-gray-600);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.ion-avatar-gradient--blue {
|
|
179
|
+
--ion-avatar-gradient-from: var(--color-blue-300);
|
|
180
|
+
--ion-avatar-gradient-mid: var(--color-blue-200);
|
|
181
|
+
--ion-avatar-gradient-to: var(--color-blue-50);
|
|
182
|
+
--ion-avatar-gradient-text: var(--color-blue-600);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.ion-avatar-gradient--violet {
|
|
186
|
+
--ion-avatar-gradient-from: var(--color-purple-300);
|
|
187
|
+
--ion-avatar-gradient-mid: var(--color-purple-200);
|
|
188
|
+
--ion-avatar-gradient-to: var(--color-purple-50);
|
|
189
|
+
--ion-avatar-gradient-text: var(--color-purple-600);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.ion-avatar-gradient--pink {
|
|
193
|
+
--ion-avatar-gradient-from: var(--color-pink-300);
|
|
194
|
+
--ion-avatar-gradient-mid: var(--color-pink-200);
|
|
195
|
+
--ion-avatar-gradient-to: var(--color-pink-50);
|
|
196
|
+
--ion-avatar-gradient-text: var(--color-pink-600);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.ion-avatar-gradient--orange {
|
|
200
|
+
--ion-avatar-gradient-from: var(--color-orange-300);
|
|
201
|
+
--ion-avatar-gradient-mid: var(--color-orange-200);
|
|
202
|
+
--ion-avatar-gradient-to: var(--color-orange-50);
|
|
203
|
+
--ion-avatar-gradient-text: var(--color-orange-600);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.ion-avatar-gradient--green {
|
|
207
|
+
--ion-avatar-gradient-from: var(--color-green-300);
|
|
208
|
+
--ion-avatar-gradient-mid: var(--color-green-200);
|
|
209
|
+
--ion-avatar-gradient-to: var(--color-green-50);
|
|
210
|
+
--ion-avatar-gradient-text: var(--color-green-600);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.ion-avatar-gradient--red {
|
|
214
|
+
--ion-avatar-gradient-from: var(--color-red-300);
|
|
215
|
+
--ion-avatar-gradient-mid: var(--color-red-200);
|
|
216
|
+
--ion-avatar-gradient-to: var(--color-red-50);
|
|
217
|
+
--ion-avatar-gradient-text: var(--color-red-600);
|
|
218
|
+
}
|
package/dist/styles/avatar.css
CHANGED
|
@@ -1,52 +1,116 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* Avatar
|
|
3
3
|
*
|
|
4
|
-
* Measured from Figma `Avatar` (
|
|
4
|
+
* Measured from Figma `Avatar` (74:164) and `Avatar Group` (74:279).
|
|
5
5
|
*
|
|
6
|
-
* mini 24 type 12/18 icon 12 square radius/xs
|
|
7
|
-
* small 32 type 14/20
|
|
8
|
-
* medium 40 type 16/24
|
|
9
|
-
* large 48 type 20/32
|
|
6
|
+
* mini 24 type 12/18 regular icon 12 square radius/xs
|
|
7
|
+
* small 32 type 14/20 medium icon 16 square radius/sm
|
|
8
|
+
* medium 40 type 16/24 medium icon 20 square radius/md
|
|
9
|
+
* large 48 type 20/32 medium icon 24 square radius/md
|
|
10
10
|
*
|
|
11
11
|
* Circle is radius/full at every size; square steps up with the box, which is
|
|
12
12
|
* concentric-corner arithmetic rather than one shared corner.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* Two things here were wrong against Figma until the 2026-08-27 re-export, and
|
|
15
|
+
* both were invisible because they are one token apart from something plausible:
|
|
16
|
+
*
|
|
17
|
+
* Large used `type/h5` (20/28). The style Figma applies is Body/Large
|
|
18
|
+
* Emphasis (20/32). Same font-size, four pixels of leading apart.
|
|
19
|
+
*
|
|
20
|
+
* Mini used font-weight/medium. Figma's Mini is the Caption style, which is
|
|
21
|
+
* font-weight/regular. Every other size is Emphasis and stays medium.
|
|
22
|
+
*
|
|
23
|
+
* Surface and border depend on the Type variant, which is the other correction:
|
|
24
|
+
* Character and Icon sit on surface/muted behind border/strong, but Image sits
|
|
25
|
+
* on surface/placeholder behind border/subtle — a photo needs a quieter frame
|
|
26
|
+
* than an empty box does. The border is what separates overlapping avatars in a
|
|
27
|
+
* group, so it is not decoration and must not be dropped from either.
|
|
28
|
+
*
|
|
29
|
+
* THE ROOT DOES NOT CLIP, AND THAT IS LODAD-BEARING
|
|
30
|
+
*
|
|
31
|
+
* `.ion-avatar__media` carries the surface, the border and the `overflow` that
|
|
32
|
+
* crops the photo. The root carries only geometry. They cannot be the same box:
|
|
33
|
+
* an indicator sits in a corner that is OUTSIDE a circle's clip path, so a root
|
|
34
|
+
* that clipped the image would slice the indicator in half. Figma draws it the
|
|
35
|
+
* same way — an unclipped frame around a clipped `Media` child, with the ring
|
|
36
|
+
* and the indicators as siblings of it.
|
|
17
37
|
*/
|
|
18
38
|
.ion-avatar {
|
|
19
39
|
--ion-avatar-size: var(--spacing-40);
|
|
20
40
|
--ion-avatar-radius: var(--radius-md);
|
|
21
41
|
--ion-avatar-font-size: var(--type-body);
|
|
22
42
|
--ion-avatar-line-height: var(--type-body-line-height);
|
|
43
|
+
--ion-avatar-font-weight: var(--font-weight-medium);
|
|
23
44
|
--ion-avatar-icon: var(--icon-size-md);
|
|
45
|
+
--ion-avatar-surface: var(--surface-muted);
|
|
46
|
+
--ion-avatar-border: var(--border-strong);
|
|
47
|
+
|
|
48
|
+
/* Ring and indicator geometry, indexed by size the way everything else is. */
|
|
49
|
+
--ion-avatar-ring-width: var(--border-width-thick);
|
|
50
|
+
--ion-avatar-indicator-size: var(--spacing-14);
|
|
51
|
+
|
|
52
|
+
/*
|
|
53
|
+
* 1.5px, and it cannot be a token: the border ladder is 1 / 2 / 4 and Figma
|
|
54
|
+
* puts a half-step here so the 14px indicator keeps the same visual gap the
|
|
55
|
+
* 16px one gets from 2px. Written once, as a local, rather than inline —
|
|
56
|
+
* a raw length in a `border-width` declaration is a theming bug everywhere
|
|
57
|
+
* else in this system and the lint rule is right to say so.
|
|
58
|
+
*/
|
|
59
|
+
--ion-avatar-indicator-ring: 1.5px;
|
|
60
|
+
|
|
61
|
+
/*
|
|
62
|
+
* The glyph inside the top indicator: 11 / 10 / 8 / 6 against a 16 / 14 / 12
|
|
63
|
+
* / 8 dot. Not a ratio and not a rung on the icon ladder — Figma tuned these
|
|
64
|
+
* four by eye, and the smallest is below anything `--icon-size-*` carries.
|
|
65
|
+
*/
|
|
66
|
+
--ion-avatar-indicator-icon: 10px;
|
|
24
67
|
|
|
68
|
+
position: relative;
|
|
25
69
|
display: inline-flex;
|
|
26
|
-
align-items: center;
|
|
27
|
-
justify-content: center;
|
|
28
70
|
flex-shrink: 0;
|
|
29
|
-
overflow: hidden;
|
|
30
71
|
width: var(--ion-avatar-size);
|
|
31
72
|
height: var(--ion-avatar-size);
|
|
32
|
-
background-color: var(--surface-muted);
|
|
33
|
-
border-style: solid;
|
|
34
|
-
border-width: var(--border-width-default);
|
|
35
|
-
border-color: var(--border-strong);
|
|
36
73
|
border-radius: var(--ion-avatar-radius);
|
|
37
74
|
color: var(--text-tertiary);
|
|
38
75
|
font-family: var(--font-family-sans);
|
|
39
76
|
font-size: var(--ion-avatar-font-size);
|
|
40
77
|
line-height: var(--ion-avatar-line-height);
|
|
41
|
-
font-weight: var(--font-weight
|
|
78
|
+
font-weight: var(--ion-avatar-font-weight);
|
|
42
79
|
user-select: none;
|
|
43
80
|
}
|
|
44
81
|
|
|
82
|
+
.ion-avatar__media {
|
|
83
|
+
position: absolute;
|
|
84
|
+
inset: 0;
|
|
85
|
+
display: flex;
|
|
86
|
+
align-items: center;
|
|
87
|
+
justify-content: center;
|
|
88
|
+
overflow: hidden;
|
|
89
|
+
background-color: var(--ion-avatar-surface);
|
|
90
|
+
border-style: solid;
|
|
91
|
+
border-width: var(--border-width-default);
|
|
92
|
+
border-color: var(--ion-avatar-border);
|
|
93
|
+
border-radius: inherit;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* Figma's `Type=Image`: a photo gets the placeholder surface and the quiet
|
|
97
|
+
* border. Applied from `showImage`, not from `src`, so a 404 that falls back
|
|
98
|
+
* to initials also falls back to the Character variant's frame. */
|
|
99
|
+
.ion-avatar--image {
|
|
100
|
+
--ion-avatar-surface: var(--surface-placeholder);
|
|
101
|
+
--ion-avatar-border: var(--border-subtle);
|
|
102
|
+
}
|
|
103
|
+
|
|
45
104
|
.ion-avatar--mini {
|
|
46
105
|
--ion-avatar-size: var(--spacing-24);
|
|
47
106
|
--ion-avatar-radius: var(--radius-xs);
|
|
48
107
|
--ion-avatar-font-size: var(--type-caption);
|
|
49
108
|
--ion-avatar-line-height: var(--type-caption-line-height);
|
|
109
|
+
--ion-avatar-font-weight: var(--font-weight-regular);
|
|
110
|
+
--ion-avatar-ring-width: var(--border-width-default);
|
|
111
|
+
--ion-avatar-indicator-size: var(--spacing-8);
|
|
112
|
+
--ion-avatar-indicator-ring: var(--border-width-default);
|
|
113
|
+
--ion-avatar-indicator-icon: 6px;
|
|
50
114
|
|
|
51
115
|
/*
|
|
52
116
|
* `2xs`, not `xs` — this is 12 and must stay 12.
|
|
@@ -64,21 +128,28 @@
|
|
|
64
128
|
--ion-avatar-font-size: var(--type-body-sm);
|
|
65
129
|
--ion-avatar-line-height: var(--type-body-sm-line-height);
|
|
66
130
|
--ion-avatar-icon: var(--icon-size-sm);
|
|
131
|
+
--ion-avatar-ring-width: var(--border-width-default);
|
|
132
|
+
--ion-avatar-indicator-size: var(--spacing-12);
|
|
133
|
+
--ion-avatar-indicator-ring: var(--border-width-default);
|
|
134
|
+
--ion-avatar-indicator-icon: 8px;
|
|
67
135
|
}
|
|
68
136
|
|
|
69
137
|
/* Large shares Medium's corner — the square ladder stops climbing at radius/md. */
|
|
70
138
|
.ion-avatar--lg {
|
|
71
139
|
--ion-avatar-size: var(--spacing-48);
|
|
72
|
-
--ion-avatar-font-size: var(--type-
|
|
73
|
-
--ion-avatar-line-height: var(--type-
|
|
140
|
+
--ion-avatar-font-size: var(--type-body-lg);
|
|
141
|
+
--ion-avatar-line-height: var(--type-body-lg-line-height);
|
|
74
142
|
--ion-avatar-icon: var(--icon-size-lg);
|
|
143
|
+
--ion-avatar-indicator-size: var(--spacing-16);
|
|
144
|
+
--ion-avatar-indicator-ring: var(--border-width-thick);
|
|
145
|
+
--ion-avatar-indicator-icon: 11px;
|
|
75
146
|
}
|
|
76
147
|
|
|
77
148
|
.ion-avatar--circle {
|
|
78
149
|
--ion-avatar-radius: var(--radius-full);
|
|
79
150
|
}
|
|
80
151
|
|
|
81
|
-
/* The image fills the box and is cropped by `overflow: hidden` on the
|
|
152
|
+
/* The image fills the box and is cropped by `overflow: hidden` on the media,
|
|
82
153
|
* so it inherits the corner without repeating it. */
|
|
83
154
|
.ion-avatar__image {
|
|
84
155
|
width: 100%;
|
|
@@ -99,6 +170,101 @@
|
|
|
99
170
|
height: var(--ion-avatar-icon);
|
|
100
171
|
}
|
|
101
172
|
|
|
173
|
+
/*
|
|
174
|
+
* Ring — Figma's `Show Ring`.
|
|
175
|
+
*
|
|
176
|
+
* Drawn INSIDE the avatar's own edge, over the media, at the same size as the
|
|
177
|
+
* box. Not an `outline` and not a `box-shadow` spread: both of those sit
|
|
178
|
+
* outside the element and would change the avatar's footprint, which breaks the
|
|
179
|
+
* group's overlap arithmetic and any layout that measured the avatar. Figma
|
|
180
|
+
* draws a same-size overlay, so this does too.
|
|
181
|
+
*/
|
|
182
|
+
.ion-avatar__ring {
|
|
183
|
+
position: absolute;
|
|
184
|
+
inset: 0;
|
|
185
|
+
pointer-events: none;
|
|
186
|
+
border-style: solid;
|
|
187
|
+
border-width: var(--ion-avatar-ring-width);
|
|
188
|
+
border-color: var(--surface-primary);
|
|
189
|
+
border-radius: inherit;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/*
|
|
193
|
+
* Indicators — Figma's `Show Top Indicator` / `Show Bottom Indicator`, each an
|
|
194
|
+
* instance of the `Status Indicator` component (1008:1764).
|
|
195
|
+
*
|
|
196
|
+
* Both sit flush in a corner: Figma's `left` is size minus indicator at every
|
|
197
|
+
* size and shape, which is `right: 0`. The border is `ring/offset`, so the dot
|
|
198
|
+
* reads as separate from the photo underneath it rather than as part of it —
|
|
199
|
+
* that is why it is a border and not a gap.
|
|
200
|
+
*/
|
|
201
|
+
.ion-avatar__indicator {
|
|
202
|
+
position: absolute;
|
|
203
|
+
right: 0;
|
|
204
|
+
display: inline-flex;
|
|
205
|
+
align-items: center;
|
|
206
|
+
justify-content: center;
|
|
207
|
+
width: var(--ion-avatar-indicator-size);
|
|
208
|
+
height: var(--ion-avatar-indicator-size);
|
|
209
|
+
border-style: solid;
|
|
210
|
+
border-width: var(--ion-avatar-indicator-ring);
|
|
211
|
+
border-color: var(--ring-offset);
|
|
212
|
+
border-radius: var(--radius-full);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.ion-avatar__indicator--top {
|
|
216
|
+
top: 0;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.ion-avatar__indicator--bottom {
|
|
220
|
+
bottom: 0;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.ion-avatar__indicator svg {
|
|
224
|
+
width: var(--ion-avatar-indicator-icon);
|
|
225
|
+
height: var(--ion-avatar-indicator-icon);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/*
|
|
229
|
+
* The six intents, spelled the same way Badge and Alert spell them. Colour and
|
|
230
|
+
* surface are declared together per intent rather than hoisted to the block:
|
|
231
|
+
* the contrast gate pairs what one rule declares, and a foreground it has to
|
|
232
|
+
* find on a different element resolves to the block's `--text-tertiary` and
|
|
233
|
+
* measures a pairing that never renders.
|
|
234
|
+
*/
|
|
235
|
+
.ion-avatar__indicator--neutral,
|
|
236
|
+
.ion-avatar__indicator--primary,
|
|
237
|
+
.ion-avatar__indicator--success,
|
|
238
|
+
.ion-avatar__indicator--warning,
|
|
239
|
+
.ion-avatar__indicator--error,
|
|
240
|
+
.ion-avatar__indicator--information {
|
|
241
|
+
color: var(--text-on-color);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.ion-avatar__indicator--neutral {
|
|
245
|
+
background-color: var(--surface-inverse);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.ion-avatar__indicator--primary {
|
|
249
|
+
background-color: var(--surface-primary);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.ion-avatar__indicator--success {
|
|
253
|
+
background-color: var(--surface-success);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.ion-avatar__indicator--warning {
|
|
257
|
+
background-color: var(--surface-warning);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.ion-avatar__indicator--error {
|
|
261
|
+
background-color: var(--surface-error);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.ion-avatar__indicator--information {
|
|
265
|
+
background-color: var(--surface-information);
|
|
266
|
+
}
|
|
267
|
+
|
|
102
268
|
/*
|
|
103
269
|
* Avatar Group
|
|
104
270
|
*
|
package/dist/styles/button.css
CHANGED
|
@@ -17,16 +17,29 @@
|
|
|
17
17
|
--ion-button-elevation: var(--ion-shadow-none);
|
|
18
18
|
|
|
19
19
|
/*
|
|
20
|
-
* ELEVATION IS INDEXED BY SIZE
|
|
20
|
+
* ELEVATION IS INDEXED BY SIZE; THE VARIANT PICKS THE FAMILY
|
|
21
21
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* the size modifiers reassign and the variants select from. This replaces the
|
|
22
|
+
* Each of these is a rung reassigned by the size modifiers, and a variant
|
|
23
|
+
* selects one. That is the whole structure — no variant restates a shadow
|
|
24
|
+
* value, and no size modifier knows which variant is asking. It replaces the
|
|
26
25
|
* old per-variant `button-raised` / `button-surface` pair, which encoded a
|
|
27
26
|
* distinction Figma no longer draws.
|
|
27
|
+
*
|
|
28
|
+
* `raised` and `shadow` are two DIFFERENT rest families, and which one a
|
|
29
|
+
* variant takes is a real design decision rather than a spelling of the same
|
|
30
|
+
* idea. `Raised/Lifted` is embossed — two inset highlights that read as a
|
|
31
|
+
* bevel — and it only works over a solid, saturated fill. `Shadow` is a plain
|
|
32
|
+
* drop shadow. The two light-surfaced variants, Secondary and Primary Soft,
|
|
33
|
+
* are on `shadow`; the four solid ones are on `raised`. Measured across all
|
|
34
|
+
* four sizes of both Figma sets on 2026-08-28.
|
|
35
|
+
*
|
|
36
|
+
* The two ramps are not the same shape, which is the trap here. `raised` runs
|
|
37
|
+
* xs / sm / lg / lg, but `shadow` runs xs / sm / md / md — Large and XLarge
|
|
38
|
+
* share `Shadow/md`, where the raised family jumps to `lg`. Assuming one ramp
|
|
39
|
+
* from the other puts Large one rung too heavy.
|
|
28
40
|
*/
|
|
29
41
|
--ion-button-raised: var(--ion-shadow-raised-lifted-sm);
|
|
42
|
+
--ion-button-shadow: var(--ion-shadow-shadow-sm);
|
|
30
43
|
--ion-button-inset-lifted: var(--ion-shadow-inset-lifted-sm);
|
|
31
44
|
--ion-button-inset-flush: var(--ion-shadow-inset-flush-sm);
|
|
32
45
|
|
|
@@ -137,6 +150,7 @@
|
|
|
137
150
|
--ion-button-font-size: var(--type-body-sm);
|
|
138
151
|
--ion-button-line-height: var(--type-body-sm-line-height);
|
|
139
152
|
--ion-button-raised: var(--ion-shadow-raised-lifted-xs);
|
|
153
|
+
--ion-button-shadow: var(--ion-shadow-shadow-xs);
|
|
140
154
|
--ion-button-inset-lifted: var(--ion-shadow-inset-lifted-xs);
|
|
141
155
|
--ion-button-inset-flush: var(--ion-shadow-inset-flush-xs);
|
|
142
156
|
}
|
|
@@ -149,6 +163,9 @@
|
|
|
149
163
|
--ion-button-font-size: var(--type-body-md);
|
|
150
164
|
--ion-button-line-height: var(--type-body-md-line-height);
|
|
151
165
|
--ion-button-raised: var(--ion-shadow-raised-lifted-lg);
|
|
166
|
+
|
|
167
|
+
/* `md`, not `lg` — the shadow ramp tops out a rung below the raised one. */
|
|
168
|
+
--ion-button-shadow: var(--ion-shadow-shadow-md);
|
|
152
169
|
--ion-button-inset-lifted: var(--ion-shadow-inset-lifted-lg);
|
|
153
170
|
--ion-button-inset-flush: var(--ion-shadow-inset-flush-lg);
|
|
154
171
|
}
|
|
@@ -179,13 +196,15 @@
|
|
|
179
196
|
* the one rung where the icon does NOT run ahead of its label (24 vs 20, where
|
|
180
197
|
* Large is 24 vs 18). Reproduced as measured.
|
|
181
198
|
*
|
|
182
|
-
* Elevation reuses the
|
|
183
|
-
* be the easy wrong answer. `Raised/Lifted/xl` and `Raised/Flush/xl` DO
|
|
184
|
-
* effect styles — it is the *inset* families that stop at lg, so there
|
|
185
|
-
* complete xl rung to press into. Figma binds Raised/Lifted/lg,
|
|
186
|
-
* and Inset/Flush/lg on XLarge exactly as on Large
|
|
199
|
+
* Elevation reuses the Large rungs, and this is where "there is no xl rung"
|
|
200
|
+
* would be the easy wrong answer. `Raised/Lifted/xl` and `Raised/Flush/xl` DO
|
|
201
|
+
* exist as effect styles — it is the *inset* families that stop at lg, so there
|
|
202
|
+
* is no complete xl rung to press into. Figma binds Raised/Lifted/lg,
|
|
203
|
+
* Inset/Lifted/lg and Inset/Flush/lg on XLarge exactly as on Large, and
|
|
204
|
+
* `Shadow/md` for the two shadow-family variants — also exactly as on Large,
|
|
205
|
+
* even though `Shadow/lg` and `Shadow/xl` both exist. These four lines restate
|
|
187
206
|
* the inherited defaults on purpose, so the next reader does not have to go and
|
|
188
|
-
* check which half of
|
|
207
|
+
* check which half of each family exists.
|
|
189
208
|
*/
|
|
190
209
|
.ion-button--xl {
|
|
191
210
|
--ion-button-size: var(--spacing-56);
|
|
@@ -195,6 +214,7 @@
|
|
|
195
214
|
--ion-button-font-size: var(--type-body-lg);
|
|
196
215
|
--ion-button-line-height: var(--type-body-lg-line-height);
|
|
197
216
|
--ion-button-raised: var(--ion-shadow-raised-lifted-lg);
|
|
217
|
+
--ion-button-shadow: var(--ion-shadow-shadow-md);
|
|
198
218
|
--ion-button-inset-lifted: var(--ion-shadow-inset-lifted-lg);
|
|
199
219
|
--ion-button-inset-flush: var(--ion-shadow-inset-flush-lg);
|
|
200
220
|
}
|
|
@@ -232,9 +252,9 @@
|
|
|
232
252
|
*
|
|
233
253
|
* HOVER DOES NOT CHANGE ELEVATION — only the surface colour moves. Measured:
|
|
234
254
|
* every Hover variant in Figma carries the same effect style as its Default,
|
|
235
|
-
* across all
|
|
236
|
-
* from the old `button-raised-hover` step, which encodes
|
|
237
|
-
* current design no longer draws.
|
|
255
|
+
* across all six elevated types and both Figma sets. Reproduced as measured
|
|
256
|
+
* rather than preserved from the old `button-raised-hover` step, which encodes
|
|
257
|
+
* a difference the current design no longer draws.
|
|
238
258
|
*/
|
|
239
259
|
|
|
240
260
|
/* Primary Brand Variant */
|
|
@@ -314,10 +334,12 @@
|
|
|
314
334
|
* (secondary). Measured on all four sizes.
|
|
315
335
|
*
|
|
316
336
|
* Elevation presses flush like Secondary, not lifted like Brand — a tinted
|
|
317
|
-
* surface has too little contrast for the lifted inset to register.
|
|
337
|
+
* surface has too little contrast for the lifted inset to register. It rests on
|
|
338
|
+
* the `shadow` family for the same reason: the embossed `Raised/Lifted` bevel
|
|
339
|
+
* needs a solid saturated fill under it, and a subtle tint is not one.
|
|
318
340
|
*/
|
|
319
341
|
.ion-button--primary-soft {
|
|
320
|
-
--ion-button-elevation: var(--ion-button-
|
|
342
|
+
--ion-button-elevation: var(--ion-button-shadow);
|
|
321
343
|
--ion-button-pressed: var(--ion-button-inset-flush);
|
|
322
344
|
|
|
323
345
|
background-color: var(--surface-primary-subtle);
|
|
@@ -343,9 +365,16 @@
|
|
|
343
365
|
border-color: var(--border-primary);
|
|
344
366
|
}
|
|
345
367
|
|
|
346
|
-
/*
|
|
368
|
+
/*
|
|
369
|
+
* Secondary Variant — presses flush, losing its lift; brand and destructive keep
|
|
370
|
+
* theirs.
|
|
371
|
+
*
|
|
372
|
+
* Rests on the `shadow` family, not `raised`. Figma moved it there along with
|
|
373
|
+
* Primary Soft: both sit on a light surface, and `Raised/Lifted`'s inset
|
|
374
|
+
* highlight reads as a smudge over white rather than as a bevel.
|
|
375
|
+
*/
|
|
347
376
|
.ion-button--secondary {
|
|
348
|
-
--ion-button-elevation: var(--ion-button-
|
|
377
|
+
--ion-button-elevation: var(--ion-button-shadow);
|
|
349
378
|
--ion-button-pressed: var(--ion-button-inset-flush);
|
|
350
379
|
|
|
351
380
|
background-color: var(--surface-default);
|
package/dist/styles/index.css
CHANGED