Compare commits

..

16 Commits

Author SHA1 Message Date
Bernardo Cardoso
36dfc50fa3 Merge branch 'next' into ionic-colors-support 2024-05-28 15:58:21 +01:00
Bernardo Cardoso
3b93bb4a9b feat(tokens): add html preview based on design-tokens (#29545)
Issue number: internal

---------

<!-- Please do not submit updates to dependencies unless it fixes an
issue. -->

<!-- Please try to limit your pull request to one type (bugfix, feature,
etc). Submit multiple pull requests if needed. -->

## What is the new behavior?
<!-- Please describe the behavior or changes that are being added by
this PR. -->

- Added new files to scripts/tokens for the html template and css to
style the generated preview
- Added Style Dictionary register for generating html elements for each
main token

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

<!--
  If this introduces a breaking change:
1. Describe the impact and migration path for existing applications
below.
  2. Update the BREAKING.md file with the breaking change.
3. Add "BREAKING CHANGE: [...]" to the commit description when merging.
See
https://github.com/ionic-team/ionic-framework/blob/main/docs/CONTRIBUTING.md#footer
for more information.
-->
2024-05-28 15:57:30 +01:00
Brandy Carney
b4ce7129b2 feat(avatar): add styles for xsmall size in ionic theme (#29548)
Issue number: internal

---------

## What is the current behavior?
Avatar does not have styles for the `"xsmall"` size in the ionic theme.

## What is the new behavior?
- Adds the styles for the xsmall size (width, height, padding, font
size, font weight and line height)
- Adds e2e test for xsmall size to the existing avatar test for sizes

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

## Other information

[Preview](https://ionic-framework-git-rou-10733-ionic1.vercel.app/src/components/avatar/test/size?ionic:theme=ionic)
2024-05-28 09:48:02 -04:00
Bernardo Cardoso
1ded2c5191 update iopnic clear button snapshots 2024-05-28 09:28:55 +01:00
Bernardo Cardoso
c8679f8b1b now color utilities affect variables 2024-05-28 07:21:55 +01:00
Bernardo Cardoso
6f089c0536 improve cretaeColorClasses function 2024-05-28 07:20:44 +01:00
Brandy Carney
7f8be3e18c feat(avatar): add styles for large size in ionic theme (#29541)
Issue number: internal

---------

## What is the current behavior?
Avatar does not have styles for the `"large"` size in the ionic theme.

## What is the new behavior?
- Adds the styles for the large size (width, height, padding, font size)
- Adds e2e test for large size to the existing avatar test for sizes

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

## Other information

[Preview](https://ionic-framework-git-rou-10736-ionic1.vercel.app/src/components/avatar/test/size?ionic:theme=ionic)
2024-05-23 12:44:35 -04:00
Brandy Carney
fea59b73aa feat(avatar): add styles for small size in ionic theme (#29540)
Issue number: internal

---------

## What is the current behavior?
Avatar does not have styles for the `"small"` size in the ionic theme.

## What is the new behavior?
- Adds the styles for the small size (width, height, padding, font size)
- Adds e2e test for small size to the existing avatar test for sizes

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

## Other information

[Preview](https://ionic-framework-git-rou-10734-ionic1.vercel.app/src/components/avatar/test/size?ionic:theme=ionic)
2024-05-23 12:28:00 -04:00
Brandy Carney
b9af47ae0d feat(avatar): add styles for default (medium) size in ionic theme (#29538)
Issue number: internal

---------

## What is the current behavior?
Avatar does not have any styles in the ionic theme.

## What is the new behavior?
- Adds background, border and font styles for avatar
- Adds the styles for the medium size and defaults the size to medium
- Adds e2e test for avatar sizes

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

## Other information

[Preview](https://ionic-framework-git-rou-10735-ionic1.vercel.app/src/components/avatar/test/size?ionic:theme=ionic)
2024-05-23 12:12:31 -04:00
Bernardo Cardoso
b21f95cced feat(tokens): add rgba for color tokens with 8 chars (#29542)
Issue number: internal

---------

<!-- Please do not submit updates to dependencies unless it fixes an
issue. -->

<!-- Please try to limit your pull request to one type (bugfix, feature,
etc). Submit multiple pull requests if needed. -->

## What is the new behavior?
<!-- Please describe the behavior or changes that are being added by
this PR. -->


- Added new method to detect colors with eight characters
- Added logic to generate an rgba when the above is true. This is to
ensure better compatibility with older devices that don't support colors
with more than six characters.

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

<!--
  If this introduces a breaking change:
1. Describe the impact and migration path for existing applications
below.
  2. Update the BREAKING.md file with the breaking change.
3. Add "BREAKING CHANGE: [...]" to the commit description when merging.
See
https://github.com/ionic-team/ionic-framework/blob/main/docs/CONTRIBUTING.md#footer
for more information.
-->
2024-05-23 15:19:02 +01:00
Maria Hutt
1c035af1a2 feat(badge): add xlarge size for ionic theme (#29530) 2024-05-22 14:00:43 -07:00
Maria Hutt
258dabfb9e feat(badge): add large size for ionic theme (#29529) 2024-05-22 11:58:09 -07:00
Maria Hutt
15f8c55046 feat(badge): add medium size for ionic theme (#29528) 2024-05-22 11:32:48 -07:00
Maria Hutt
5c0b5c32d0 feat(badge): add small/default size for ionic theme (#29526) 2024-05-22 11:11:37 -07:00
Bernardo Cardoso
888b0c8284 feat(tokens): update design-tokens (#29533)
Issue number: internal

---------

<!-- Please do not submit updates to dependencies unless it fixes an
issue. -->

<!-- Please try to limit your pull request to one type (bugfix, feature,
etc). Submit multiple pull requests if needed. -->

## What is the new behavior?
<!-- Please describe the behavior or changes that are being added by
this PR. -->

Added an update to Design Tokens, requested by UX/UI Team:

- New semantic colors added
- Guidelines token was removed 
- Hover, disabled and pressed tokens now correctly appear on Theme JSON
- Removed _color.styles.tokens.json_ file, as it was now empty.

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

<!--
  If this introduces a breaking change:
1. Describe the impact and migration path for existing applications
below.
  2. Update the BREAKING.md file with the breaking change.
3. Add "BREAKING CHANGE: [...]" to the commit description when merging.
See
https://github.com/ionic-team/ionic-framework/blob/main/docs/CONTRIBUTING.md#footer
for more information.
-->
2024-05-22 18:22:57 +01:00
Bernardo Cardoso
b6325e49be feat(tokens): add new tokens json files (#29525)
Issue number: internal

---------

<!-- Please do not submit updates to dependencies unless it fixes an
issue. -->

<!-- Please try to limit your pull request to one type (bugfix, feature,
etc). Submit multiple pull requests if needed. -->

## What is the new behavior?
<!-- Please describe the behavior or changes that are being added by
this PR. -->

- Added new structure of JSON files that are generated from Figma
Variables
- Adjusted tokens.js script to correctly translate the new JSON format
- Adjusted tokens usage on components and CSS files
- Added html template to auto-generate html preview based on Design
Tokens (wip)
- Updated snapshots, as some tokens values changed, specially colors.

## Does this introduce a breaking change?

- [x] Yes
- [ ] No

<!--
  If this introduces a breaking change:
1. Describe the impact and migration path for existing applications
below.
  2. Update the BREAKING.md file with the breaking change.
3. Add "BREAKING CHANGE: [...]" to the commit description when merging.
See
https://github.com/ionic-team/ionic-framework/blob/main/docs/CONTRIBUTING.md#footer
for more information.
-->


## Other information

<!-- Any other information that is important to this PR such as
screenshots of how the component looks before and after the change. -->

---------

Co-authored-by: Maria Hutt <thetaPC@users.noreply.github.com>
2024-05-22 16:50:21 +01:00
160 changed files with 9687 additions and 1793 deletions

View File

@@ -15,6 +15,7 @@ ignoreFiles:
- src/themes/functions.string.scss
- src/themes/native.theme.default.scss
- src/css/themes/*.scss
- scripts/tokens/*.css
indentation: 2

View File

@@ -184,6 +184,7 @@ ion-app,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-avatar,shadow
ion-avatar,prop,mode,"ios" | "md",undefined,false,false
ion-avatar,prop,size,"large" | "medium" | "small" | "xsmall" | undefined,undefined,false,false
ion-avatar,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-avatar,css-prop,--border-radius,ionic
ion-avatar,css-prop,--border-radius,ios
@@ -310,6 +311,7 @@ ion-backdrop,event,ionBackdropTap,void,true
ion-badge,shadow
ion-badge,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record<never, never> | undefined,undefined,false,true
ion-badge,prop,mode,"ios" | "md",undefined,false,false
ion-badge,prop,size,"large" | "medium" | "small" | "xlarge" | undefined,undefined,false,false
ion-badge,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-badge,css-prop,--background,ionic
ion-badge,css-prop,--background,ios

View File

@@ -77,7 +77,7 @@
"build.css": "npm run css.sass && npm run css.minify",
"build.debug": "npm run clean && stencil build --debug",
"build.docs.json": "stencil build --docs-json dist/docs.json",
"build.tokens": "node ./scripts/tokens.js && npm run lint.sass.fix && npm run prettier.tokens",
"build.tokens": "node ./scripts/tokens/index.js && npm run lint.fix && npm run prettier.tokens",
"clean": "node scripts/clean.js",
"css.minify": "cleancss -O2 -o ./css/ionic.bundle.css ./css/ionic.bundle.css",
"css.sass": "sass --embed-sources --style compressed src/css:./css",
@@ -90,7 +90,7 @@
"lint.ts.fix": "npm run eslint -- --fix",
"prerender.e2e": "node scripts/testing/prerender.js",
"prettier": "prettier \"./src/**/*.{html,ts,tsx,js,jsx,scss}\"",
"prettier.tokens": "prettier \"./src/foundations/*.scss\" --write --cache",
"prettier.tokens": "prettier \"./src/foundations/*.{scss, html}\" --write --cache",
"start": "npm run build.css && stencil build --dev --watch --serve",
"test": "npm run test.spec && npm run test.e2e",
"test.spec": "stencil test --spec --max-workers=2",

View File

@@ -1,197 +0,0 @@
/* For generating Design Tokens, we use Style Dictionary for several reasons:
- It's prepared to easily generate tokens for multiple types of outputs (CSS, SCSS, iOS, Android, documentation, etc.).
- It also works very well out of the box with any kind of Design Tokens formats, like Figma Tokens, as well as APIs to adjust to more custom ones.
- It is probably the most well-known and widely used Design Tokens tool. It has also been regularly maintained for a long time.
- It can easily scale to different necessities we might have in the future.
*/
// eslint-disable-next-line @typescript-eslint/no-var-requires
const StyleDictionary = require('style-dictionary');
const { fileHeader } = StyleDictionary.formatHelpers;
// Empty for as an example of how we can add some extra variables, not from the tokens JSON
const customVariables = ``;
// Prefix for all generated variables
const variablesPrefix = 'ionic';
function hexToRgb(hex) {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result
? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16),
}
: null;
}
// CSS vanilla :root format
StyleDictionary.registerFormat({
name: 'rootFormat',
formatter({ dictionary, file }) {
// Add a prefix to all variable names
const prefixedVariables = dictionary.allProperties.map((prop) => {
const rgb = hexToRgb(prop.value);
return ` --${variablesPrefix}-${prop.name}: ${prop.value};${
rgb ? `\n --${variablesPrefix}-${prop.name}-rgb: ${rgb.r}, ${rgb.g}, ${rgb.b};` : ``
}`;
});
return (
fileHeader({ file }) +
':root {\n' +
prefixedVariables.join('\n') + // Join all prefixed variables with a newline
customVariables +
'\n}\n'
);
},
});
// scss variables format
StyleDictionary.registerFormat({
name: 'scssVariablesFormat',
formatter({ dictionary, file }) {
// Add a prefix to all variable names
const prefixedVariables = dictionary.allProperties.map((prop) => {
const rgb = hexToRgb(prop.value);
return `$${variablesPrefix}-${prop.name}: var(--${variablesPrefix}-${prop.name}, ${prop.value});${
rgb
? `\n$${variablesPrefix}-${prop.name}-rgb: var(--${variablesPrefix}-${prop.name}-rgb, ${rgb.r}, ${rgb.g}, ${rgb.b});`
: ``
}`;
});
return (
fileHeader({ file }) +
prefixedVariables.join('\n') + // Join all prefixed variables with a newline
customVariables +
'\n'
);
},
});
// Create utility-classes
StyleDictionary.registerFormat({
name: 'cssUtilityClassesFormat',
formatter({ dictionary, file }) {
const utilityClasses = dictionary.allProperties.map((prop) => {
let tokenType = prop.attributes.category;
const className = `${prop.name}`;
let utilityClass = '';
switch (tokenType) {
case 'color':
utilityClass = `.${variablesPrefix}-${className} {\n color: $ionic-${prop.name};\n}
.${variablesPrefix}-background-${className} {\n background-color: $ionic-${prop.name};\n}`;
break;
case 'border':
const borderAttribute = prop.attributes.type === 'radius' ? 'border-radius' : 'border-width';
utilityClass = `.${variablesPrefix}-${className} {\n ${borderAttribute}: $ionic-${prop.name};\n}`;
break;
case 'font':
let fontAttribute;
switch (prop.attributes.type) {
case 'size':
fontAttribute = 'font-size';
break;
case 'weight':
fontAttribute = 'font-weight';
break;
case 'line-height':
fontAttribute = 'line-height';
break;
case 'letter-spacing':
fontAttribute = 'letter-spacing';
break;
case 'family':
return;
}
utilityClass = `.${variablesPrefix}-${className} {\n ${fontAttribute}: $ionic-${prop.name};\n}`;
break;
case 'elevation':
utilityClass = `.${variablesPrefix}-${className} {\n box-shadow: $ionic-${prop.name};\n}`;
break;
case 'space':
utilityClass = `.${variablesPrefix}-margin-${className} {\n --margin-start: #{$ionic-${prop.name}};\n --margin-end: #{$ionic-${prop.name}};\n --margin-top: #{$ionic-${prop.name}};\n --margin-bottom: #{$ionic-${prop.name}};\n\n @include margin(${prop.value});\n};\n
.${variablesPrefix}-padding-${className} {\n --padding-start: #{$ionic-${prop.name}};\n --padding-end: #{$ionic-${prop.name}};\n --padding-top: #{$ionic-${prop.name}};\n --padding-bottom: #{$ionic-${prop.name}};\n\n @include padding(${prop.value});\n};\n`;
break;
default:
utilityClass = `.${variablesPrefix}-${className} {\n ${tokenType}: $ionic-${prop.name};\n}`;
}
return utilityClass;
});
return [
fileHeader({ file }),
'@import "./ionic.vars";\n@import "../themes/mixins";\n',
utilityClasses.join('\n'),
].join('\n');
},
});
// Make Style Dictionary comply with the $ format on properties from W3C Guidelines
const w3cTokenJsonParser = {
pattern: /\.json|\.tokens\.json|\.tokens$/,
parse({ contents }) {
// replace $value with value so that style dictionary recognizes it
var preparedContent = (contents || '{}')
.replace(/"\$?value":/g, '"value":')
// convert $description to comment
.replace(/"\$?description":/g, '"comment":');
//
return JSON.parse(preparedContent);
},
};
StyleDictionary.registerParser(w3cTokenJsonParser);
// Generate Tokens
StyleDictionary.extend({
source: ['./src/foundations/*.json'],
platforms: {
css: {
buildPath: './src/foundations/',
transformGroup: 'css',
files: [
{
destination: 'ionic.root.scss',
format: 'rootFormat',
options: {
outputReferences: true,
fileHeader: `myFileHeader`,
},
},
],
},
scss: {
buildPath: './src/foundations/',
transformGroup: 'scss',
files: [
{
destination: 'ionic.vars.scss',
format: 'scssVariablesFormat',
options: {
outputReferences: true,
fileHeader: `myFileHeader`,
},
},
{
destination: 'ionic.utility.scss',
format: 'cssUtilityClassesFormat',
options: {
outputReferences: true,
fileHeader: `myFileHeader`,
},
},
],
},
},
fileHeader: {
myFileHeader: () => {
return [`This is an auto-generated file, please do not change it directly.`, `Ionic Design System`];
},
},
}).buildAllPlatforms();

View File

@@ -0,0 +1,362 @@
/* eslint-disable @typescript-eslint/no-var-requires */
// For generating Design Tokens, we use Style Dictionary for several reasons:
// - It's prepared to easily generate tokens for multiple types of outputs (CSS, SCSS, iOS, Android, documentation, etc.).
// - It also works very well out of the box with any kind of Design Tokens formats, like Figma Tokens, as well as APIs to adjust to more custom ones.
// - It is probably the most well-known and widely used Design Tokens tool. It has also been regularly maintained for a long time.
// - It can easily scale to different necessities we might have in the future.
const fs = require('fs');
const path = require('path');
const StyleDictionary = require('style-dictionary');
const targetPath = './src/foundations/';
const {
variablesPrefix,
getRgbaValue,
hexToRgb,
generateShadowValue,
generateFontFamilyValue,
generateTypographyValue,
generateRgbValue,
generateColorUtilityClasses,
generateFontUtilityClass,
generateSpaceUtilityClasses,
generateTypographyUtilityClass,
} = require('./utilities');
const { fileHeader } = StyleDictionary.formatHelpers;
// CSS vanilla :root format
StyleDictionary.registerFormat({
name: 'rootFormat',
formatter({ dictionary, file }) {
/*
* This will loop through all tokens and based on the type it will
* call a utility function that will return the expected format for the CSS Variable
*/
const prefixedVariables = dictionary.allProperties
.filter((prop) => prop['$type'] !== 'typography')
.map((prop) => {
if (prop.attributes.category.startsWith('Elevation')) {
const cssShadow = prop.value.map(generateShadowValue).join(', ');
return `--${variablesPrefix}-${prop.name}: ${cssShadow};`;
} else if (prop.attributes.category.match('font-family')) {
return generateFontFamilyValue(prop);
} else {
const rgb = hexToRgb(prop.value);
prop.value = getRgbaValue(prop.value);
return ` --${variablesPrefix}-${prop.name}: ${prop.value};${
rgb ? `\n --${variablesPrefix}-${prop.name}-rgb: ${rgb.r}, ${rgb.g}, ${rgb.b};` : ``
}`;
}
});
return fileHeader({ file }) + ':root {\n' + prefixedVariables.join('\n') + '\n}\n';
},
});
// SCSS variables format
StyleDictionary.registerFormat({
name: 'scssVariablesFormat',
formatter({ dictionary, file }) {
const typographyProperties = dictionary.allProperties.filter((prop) => prop['$type'] === 'typography');
const otherProperties = dictionary.allProperties.filter((prop) => prop['$type'] !== 'typography');
// Make sure the reused scss variables are defined first, to avoid compilation errors
const sortedProperties = [...otherProperties, ...typographyProperties];
const prefixedVariables = sortedProperties.map((prop) => {
if (prop.attributes.category.startsWith('Elevation')) {
const cssShadow = prop.value.map(generateShadowValue).join(', ');
return `$${variablesPrefix}-${prop.name}: var(--${variablesPrefix}-${prop.name}, ${cssShadow});`;
} else if (prop.attributes.category.match('font-family')) {
return generateFontFamilyValue(prop, 'scss');
} else if (prop['$type'] === 'typography') {
return generateTypographyValue(prop, dictionary);
} else {
return generateRgbValue(prop);
}
});
return fileHeader({ file }) + prefixedVariables.join('\n') + '\n';
},
});
// Create utility-classes
StyleDictionary.registerFormat({
name: 'cssUtilityClassesFormat',
formatter({ dictionary, file }) {
const utilityClasses = dictionary.allProperties.map((prop) => {
let tokenType = prop.attributes.category;
const className = `${prop.name}`;
let utilityClass = '';
if (tokenType.startsWith('Elevation')) {
return `.${variablesPrefix}-${className} {\n box-shadow: $ionic-${prop.name};\n}`;
} else if (prop['$type'] === 'typography') {
return generateTypographyUtilityClass(prop, dictionary);
/*
* Not creating for the tokens below, as they make no sense to exist as utility-classes.
* Font-family should be defined on global scope, not component.
* Scale its an abstract token group, to be used by other tokens, like the space ones.
*/
} else if (prop.attributes.category.match('font-family') || tokenType === 'scale') {
return;
}
const tokenTypeLower = tokenType.toLowerCase();
switch (tokenTypeLower) {
case 'color':
case 'state':
case 'guidelines':
case 'disabled':
case 'hover':
case 'pressed':
utilityClass = generateColorUtilityClasses(prop, className);
break;
case 'border-size':
utilityClass = `.${variablesPrefix}-${className} {\n border-width: $ionic-${prop.name};\n}`;
break;
case 'font':
utilityClass = generateFontUtilityClass(prop, className);
break;
case 'space':
utilityClass = generateSpaceUtilityClasses(prop, className);
break;
default:
utilityClass = `.${variablesPrefix}-${className} {\n ${tokenType}: $ionic-${prop.name};\n}`;
}
return utilityClass;
});
return [
fileHeader({ file }),
'@import "./ionic.vars";\n@import "../themes/mixins";\n',
utilityClasses.join('\n'),
].join('\n');
},
});
// Register the custom format to generate HTML
// Load the HTML template
const template = fs.readFileSync(path.join(__dirname, 'preview.template.html'), 'utf8');
StyleDictionary.registerFormat({
name: 'html/tokens',
formatter: function ({ dictionary }) {
// Function to extract numerical value from token name
const extractValue = (tokenName) => {
const match = tokenName.match(/-([0-9]+)/);
return match ? parseInt(match[1], 10) : Number.MAX_SAFE_INTEGER;
};
let colorTokens = `
<table>
<thead>
<tr>
<th>Color</th>
<th>Hex</th>
<th>Token Name</th>
</tr>
</thead>
<tbody>
`;
let fontSizeTokens = '';
let boxShadowTokens = '';
let borderSizeTokens = '';
let borderRadiusTokens = '';
let borderStyleTokens = '';
let fontWeightTokens = '';
let letterSpacingTokens = '';
let spaceTokens = '';
// Collect border-radius and space tokens for separate sorting
let borderRadiusTokenList = [];
let spaceTokenList = [];
dictionary.allProperties.forEach((token) => {
if (token.attributes.category === 'color') {
colorTokens += `
<tr>
<td><div class="color-swatch" style="background-color: ${token.value};"></div></td>
<td>${token.value}</td>
<td>${token.name}</td>
</tr>
`;
} else if (token.attributes.category === 'font-size') {
fontSizeTokens += `
<div class="font-size-token" style="font-size: ${token.value};">
${token.name} (${token.value})
</div>
`;
} else if (token.attributes.category.startsWith('Elevation')) {
const cssShadow = token.value.map(generateShadowValue).join(', ');
boxShadowTokens += `
<div class="shadow-token" style="box-shadow: ${cssShadow};">
${token.name}
</div>
`;
} else if (token.attributes.category === 'border-size' || token.attributes.category === 'border-width') {
borderSizeTokens += `
<div class="border-token" style="border-width: ${token.value};">
${token.name} (${token.value})
</div>
`;
} else if (token.attributes.category === 'border-radius') {
borderRadiusTokenList.push(token); // Collect border-radius tokens
} else if (token.attributes.category === 'border-style') {
borderStyleTokens += `
<div class="border-token" style="border: 1px ${token.value} #000;">
${token.name} (${token.value})
</div>
`;
} else if (token.attributes.category === 'font-weight') {
fontWeightTokens += `
<div class="weight-token" style="font-weight: ${token.value};">
${token.name} (${token.value})
</div>
`;
} else if (token.attributes.category === 'letter-spacing') {
// Convert % to px
const letterSpacingValue = token.value.replace('%', '') + 'px';
letterSpacingTokens += `
<div class="letter-spacing-token" style="letter-spacing: ${letterSpacingValue};">
${token.name} (${letterSpacingValue})
</div>
`;
} else if (token.attributes.category === 'space') {
spaceTokenList.push(token); // Collect space tokens
}
});
// Sort border-radius and space tokens
borderRadiusTokenList.sort((a, b) => extractValue(a.name) - extractValue(b.name));
spaceTokenList.sort((a, b) => extractValue(a.name) - extractValue(b.name));
// Generate HTML for sorted border-radius tokens
borderRadiusTokenList.forEach((token) => {
borderRadiusTokens += `
<div class="border-token" style="border-radius: ${token.value};">
${token.name} (${token.value})
</div>
`;
});
// Generate HTML for sorted space tokens
spaceTokenList.forEach((token) => {
spaceTokens += `
<div class="spacing-wrapper">
<div class="space-token" style="margin: ${token.value};">
${token.name} (${token.value})
</div>
</div>
`;
});
colorTokens += '</tbody></table>';
return template
.replace('{{colorTokens}}', colorTokens)
.replace('{{fontSizeTokens}}', fontSizeTokens)
.replace('{{boxShadowTokens}}', boxShadowTokens)
.replace('{{borderSizeTokens}}', borderSizeTokens)
.replace('{{borderRadiusTokens}}', borderRadiusTokens)
.replace('{{borderStyleTokens}}', borderStyleTokens)
.replace('{{fontWeightTokens}}', fontWeightTokens)
.replace('{{letterSpacingTokens}}', letterSpacingTokens)
.replace('{{spaceTokens}}', spaceTokens);
},
});
// Custom transform to ensure unique token names
StyleDictionary.registerTransform({
name: 'name/cti/kebab-unique',
type: 'name',
transformer: function (prop, options) {
return [options.prefix].concat(prop.path).join('-').toLowerCase();
},
});
// Register the custom transform group for html file generation
StyleDictionary.registerTransformGroup({
name: 'custom',
transforms: ['attribute/cti', 'name/cti/kebab-unique', 'size/rem', 'color/css'],
});
// Make Style Dictionary comply with the $ format on properties from W3C Guidelines
const w3cTokenJsonParser = {
pattern: /\.json|\.tokens\.json|\.tokens$/,
parse({ contents }) {
// replace $value with value so that style dictionary recognizes it
var preparedContent = (contents || '{}')
.replace(/"\$?value":/g, '"value":')
// convert $description to comment
.replace(/"\$?description":/g, '"comment":');
//
return JSON.parse(preparedContent);
},
};
StyleDictionary.registerParser(w3cTokenJsonParser);
// Generate Tokens
StyleDictionary.extend({
source: ['./src/foundations/tokens/*.json', './src/foundations/tokens/theme/*.json'],
platforms: {
css: {
buildPath: targetPath,
transformGroup: 'css',
files: [
{
destination: 'ionic.root.scss',
format: 'rootFormat',
options: {
outputReferences: true,
fileHeader: `myFileHeader`,
},
},
],
},
scss: {
buildPath: targetPath,
transformGroup: 'scss',
files: [
{
destination: 'ionic.vars.scss',
format: 'scssVariablesFormat',
options: {
outputReferences: true,
fileHeader: `myFileHeader`,
},
},
{
destination: 'ionic.utility.scss',
format: 'cssUtilityClassesFormat',
options: {
outputReferences: true,
fileHeader: `myFileHeader`,
},
},
],
},
html: {
transformGroup: 'custom',
buildPath: targetPath,
files: [
{
destination: 'tokens.preview.html',
format: 'html/tokens',
},
],
},
},
fileHeader: {
myFileHeader: () => {
return [`This is an auto-generated file, please do not change it directly.`, `Ionic Design System`];
},
},
}).buildAllPlatforms();

View File

@@ -0,0 +1,63 @@
table {
width: 100%;
border-collapse: collapse;
margin-bottom: 20px;
}
th,
td {
padding: 10px;
border: 1px solid #ccc;
text-align: left;
}
th {
background-color: #f4f4f4;
}
thead th {
position: sticky;
top: 0;
background-color: #f4f4f4;
z-index: 1;
}
.color-swatch {
width: 50px;
height: 50px;
}
.font-size-token,
.weight-token,
.letter-spacing-token {
margin: 10px 0;
}
.border-token,
.shadow-token {
margin: 10px;
padding: 10px;
}
.border-token {
border: 1px solid #000;
}
.spacing-wrapper {
background-color: lightblue;
}
.spacing-wrapper > div {
background-color: #fff;
}
.token-wrapper:has(.spacing-wrapper) {
display: flex;
flex-direction: column;
gap: 20px;
}
hr {
background-color: #ccc;
margin: 20px 0;
}

View File

@@ -0,0 +1,52 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<title>Design Tokens</title>
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
<link rel="stylesheet" href="../../scripts/tokens/preview.styles.css" />
</head>
<body>
<ion-app>
<ion-header>
<ion-toolbar>
<ion-title>Design Tokens - Preview</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding-horizontal">
<h1>Color Tokens</h1>
<div class="token-wrapper">{{colorTokens}}</div>
<hr />
<h1>Font Size Tokens</h1>
<div class="token-wrapper">{{fontSizeTokens}}</div>
<hr />
<h1>Font Weight Tokens</h1>
<div class="token-wrapper">{{fontWeightTokens}}</div>
<hr />
<h1>Letter Spacing Tokens</h1>
<div class="token-wrapper">{{letterSpacingTokens}}</div>
<hr />
<h1>Box Shadow Tokens</h1>
<div class="token-wrapper">{{boxShadowTokens}}</div>
<hr />
<h1>Border Size Tokens</h1>
<div class="token-wrapper">{{borderSizeTokens}}</div>
<hr />
<h1>Border Radius Tokens</h1>
<div class="token-wrapper">{{borderRadiusTokens}}</div>
<hr />
<h1>Border Style Tokens</h1>
<div class="token-wrapper">{{borderStyleTokens}}</div>
<hr />
<h1>Space Tokens</h1>
<div class="token-wrapper">{{spaceTokens}}</div>
</ion-content>
</ion-app>
</body>
</html>

View File

@@ -0,0 +1,179 @@
const variablesPrefix = 'ionic'; // Variable that holds the prefix used on all css and scss variables generated
// Generates a valid rgba() color
function getRgbaValue(propValue) {
// Check if its rgba color
const isRgba = hexToRgba(propValue);
// If it is, then compose rgba() color, otherwise use the normal color
if (isRgba !== null) {
return (propValue = `rgba(${isRgba.r}, ${isRgba.g}, ${isRgba.b},${isRgba.a})`);
} else {
return propValue;
}
}
// Translates an hex color value to rgb
function hexToRgb(hex) {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result
? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16),
}
: null;
}
// Translates an hex color value to rgba
function hexToRgba(hex) {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result
? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16),
a: Math.round((parseInt(result[4], 16) * 100) / 255) / 100,
}
: null;
}
// Generates a valid box-shadow value from a shadow Design Token structure
function generateShadowValue(shadow) {
const color = getRgbaValue(shadow.color);
return `${shadow.offsetX} ${shadow.offsetY} ${shadow.blur} ${shadow.spread} ${color}`;
}
// Generates a valid font-family value from a font-family Design Token structure
function generateFontFamilyValue(prop, variableType = 'css') {
// Remove the last word from the token, as it contains the name of the font, which we don't want to be included on the generated variables
const propName = prop.name.split('-').slice(0, -1).join('-');
return variableType === 'scss'
? `$${variablesPrefix}-${propName}: var(--${variablesPrefix}-${propName}, "${prop.value}", sans-serif);`
: `--${variablesPrefix}-${propName}: "${prop.value}", sans-serif;`;
}
// Generates a typography based scss map from a typography Design Token structure
function generateTypographyValue(prop, dictionary) {
const typography = prop.value;
const fontSizeMap = getTypeMap(dictionary, 'font-size');
const fontWeightMap = getTypeMap(dictionary, 'font-weight');
const lineHeightMap = getTypeMap(dictionary, 'line-height');
const letterSpacingMap = getTypeMap(dictionary, 'letter-spacing');
// This exact format is needed so that it compiles the tokens with the expected lint rules
return `
$${variablesPrefix}-${prop.name}: (
font-family: $ionic-font-family,
font-size: $ionic-font-size-${fontSizeMap[typography.fontSize]},
font-weight: $ionic-font-weight-${fontWeightMap[typography.fontWeight]},
letter-spacing: $ionic-letter-spacing-${letterSpacingMap[typography.letterSpacing] || 0},
line-height: $ionic-line-height-${lineHeightMap[typography.lineHeight]},
text-transform: ${typography.textTransform},
text-decoration: ${typography.textDecoration}
);
`;
}
// To abstract the need to loop across all tokens from a given type
function getTypeMap(dictionary, type) {
return Object.fromEntries(
Object.entries(dictionary.properties[type]).map(([key, token]) => [token.value, token.attributes.type])
);
}
// Generates a final value, based if the Design Token is of type color or not
function generateValue(prop) {
const rgb = hexToRgb(prop.value);
let rgbDeclaration = '';
if (rgb) {
// If the token is color, also add a rgb variable using the same color
rgbDeclaration = `\n$${variablesPrefix}-${prop.name}-rgb: var(--${variablesPrefix}-${prop.name}-rgb, ${rgb.r}, ${rgb.g}, ${rgb.b});`;
}
prop.value = getRgbaValue(prop.value);
return `$${variablesPrefix}-${prop.name}: var(--${variablesPrefix}-${prop.name}, ${prop.value});${rgbDeclaration}`;
}
// Generates a typography based css utility-class from a typography Design Token structure
function generateTypographyUtilityClass(prop, dictionary) {
const typography = prop.value;
const fontSizeMap = getTypeMap(dictionary, 'font-size');
const fontWeightMap = getTypeMap(dictionary, 'font-weight');
const lineHeightMap = getTypeMap(dictionary, 'line-height');
const letterSpacingMap = getTypeMap(dictionary, 'letter-spacing');
// This exact format is needed so that it compiles the tokens with the expected lint rules
return `
.${variablesPrefix}-${prop.name} {
font-family: $ionic-font-family;
font-size: $ionic-font-size-${fontSizeMap[typography.fontSize]};
font-weight: $ionic-font-weight-${fontWeightMap[typography.fontWeight]};
letter-spacing: $ionic-letter-spacing-${letterSpacingMap[typography.letterSpacing] || 0};
line-height: $ionic-line-height-${lineHeightMap[typography.lineHeight]};
text-transform: ${typography.textTransform};
text-decoration: ${typography.textDecoration};
};
`;
}
// Generates a color based css utility-class from a color Design Token structure
function generateColorUtilityClasses(prop, className) {
return `.${variablesPrefix}-${className} {\n --color: $ionic-${prop.name};\n}
.${variablesPrefix}-background-${className} {\n --background: $ionic-${prop.name};\n}`;
}
// Generates a font based css utility-class from a font Design Token structure
function generateFontUtilityClass(prop, className) {
let fontAttribute;
switch (prop.attributes.type) {
case 'size':
fontAttribute = 'font-size';
break;
case 'weight':
fontAttribute = 'font-weight';
break;
case 'line-height':
fontAttribute = 'line-height';
break;
case 'letter-spacing':
fontAttribute = 'letter-spacing';
break;
}
return `.${variablesPrefix}-${className} {\n ${fontAttribute}: $ionic-${prop.name};\n}`;
}
// Generates a margin or padding based css utility-class from a space Design Token structure
function generateSpaceUtilityClasses(prop, className) {
// This exact format is needed so that it compiles the tokens with the expected lint rules
const marginPaddingTemplate = (type) => `
.${variablesPrefix}-${type}-${className} {
--${type}-start: #{$ionic-${prop.name}};
--${type}-end: #{$ionic-${prop.name}};
--${type}-top: #{$ionic-${prop.name}};
--${type}-bottom: #{$ionic-${prop.name}};
@include ${type}($ionic-${prop.name});
};`;
return `${marginPaddingTemplate('margin')}\n${marginPaddingTemplate('padding')}`;
}
// Export all methods to be used on the tokens.js script
module.exports = {
variablesPrefix,
getRgbaValue,
hexToRgb,
hexToRgba,
generateShadowValue,
generateFontFamilyValue,
generateTypographyValue,
generateRgbValue: generateValue,
generateColorUtilityClasses,
generateFontUtilityClass,
generateSpaceUtilityClasses,
generateTypographyUtilityClass,
};

View File

@@ -335,6 +335,10 @@ export namespace Components {
* The mode determines the platform behaviors of the component.
*/
"mode"?: "ios" | "md";
/**
* Set to `"xsmall"` for the smallest size, `"small"` for a compact size, `"medium"` for the default height and width, or to `"large"` for a larger size. Defaults to `"medium"` for the `ionic` theme, undefined for all other themes.
*/
"size"?: `xsmall` | 'small' | 'medium' | 'large';
/**
* The theme determines the visual appearance of the component.
*/
@@ -409,6 +413,10 @@ export namespace Components {
* The mode determines the platform behaviors of the component.
*/
"mode"?: "ios" | "md";
/**
* Set to `"small"` for less height and width. Set to "medium" for slightly larger dimensions. Set to "large" for even greater height and width. Set to `"xlarge"` for the largest badge. Defaults to `"small"` for the `ionic` theme, undefined for all other themes.
*/
"size"?: 'small' | 'medium' | 'large' | 'xlarge';
/**
* The theme determines the visual appearance of the component.
*/
@@ -5559,6 +5567,10 @@ declare namespace LocalJSX {
* The mode determines the platform behaviors of the component.
*/
"mode"?: "ios" | "md";
/**
* Set to `"xsmall"` for the smallest size, `"small"` for a compact size, `"medium"` for the default height and width, or to `"large"` for a larger size. Defaults to `"medium"` for the `ionic` theme, undefined for all other themes.
*/
"size"?: `xsmall` | 'small' | 'medium' | 'large';
/**
* The theme determines the visual appearance of the component.
*/
@@ -5637,6 +5649,10 @@ declare namespace LocalJSX {
* The mode determines the platform behaviors of the component.
*/
"mode"?: "ios" | "md";
/**
* Set to `"small"` for less height and width. Set to "medium" for slightly larger dimensions. Set to "large" for even greater height and width. Set to `"xlarge"` for the largest badge. Defaults to `"small"` for the `ionic` theme, undefined for all other themes.
*/
"size"?: 'small' | 'medium' | 'large' | 'xlarge';
/**
* The theme determines the visual appearance of the component.
*/

View File

@@ -0,0 +1,73 @@
@use "../../themes/ionic/ionic.globals.scss" as globals;
@import "./avatar";
// Ionic Avatar
// --------------------------------------------------
:host {
--padding-top: #{globals.$ionic-space-0};
--padding-bottom: #{globals.$ionic-space-0};
display: flex;
align-items: center;
justify-content: center;
background: globals.$ionic-color-neutral-100;
color: globals.$ionic-color-neutral-800;
font-weight: globals.$ionic-font-weight-regular;
line-height: globals.$ionic-line-height-600;
}
:host(:not(.avatar-image)) {
border: globals.$ionic-border-size-025 solid globals.$ionic-color-neutral-800;
}
// Avatar Sizes
// --------------------------------------------------
:host(.avatar-xsmall) {
--padding-end: #{globals.$ionic-space-050};
--padding-start: #{globals.$ionic-space-050};
width: globals.$ionic-scale-600;
height: globals.$ionic-scale-600;
font-size: globals.$ionic-font-size-300;
font-weight: globals.$ionic-font-weight-medium;
line-height: globals.$ionic-line-height-500;
}
:host(.avatar-small) {
--padding-end: #{globals.$ionic-space-150};
--padding-start: #{globals.$ionic-space-150};
width: globals.$ionic-scale-800;
height: globals.$ionic-scale-800;
font-size: globals.$ionic-font-size-350;
}
:host(.avatar-medium) {
--padding-end: #{globals.$ionic-space-200};
--padding-start: #{globals.$ionic-space-200};
width: globals.$ionic-scale-1000;
height: globals.$ionic-scale-1000;
font-size: globals.$ionic-font-size-400;
}
:host(.avatar-large) {
--padding-end: #{globals.$ionic-space-250};
--padding-start: #{globals.$ionic-space-250};
width: globals.$ionic-scale-1200;
height: globals.$ionic-scale-1200;
font-size: globals.$ionic-font-size-450;
}

View File

@@ -1,5 +1,5 @@
import type { ComponentInterface } from '@stencil/core';
import { Component, Host, h } from '@stencil/core';
import { Component, Element, Host, Prop, h } from '@stencil/core';
import { getIonTheme } from '../../global/ionic-global';
@@ -12,17 +12,51 @@ import { getIonTheme } from '../../global/ionic-global';
styleUrls: {
ios: 'avatar.ios.scss',
md: 'avatar.md.scss',
ionic: 'avatar.md.scss',
ionic: 'avatar.ionic.scss',
},
shadow: true,
})
export class Avatar implements ComponentInterface {
@Element() el!: HTMLElement;
/**
* Set to `"xsmall"` for the smallest size, `"small"` for a compact size, `"medium"`
* for the default height and width, or to `"large"` for a larger size.
*
* Defaults to `"medium"` for the `ionic` theme, undefined for all other themes.
*/
@Prop() size?: `xsmall` | 'small' | 'medium' | 'large';
get hasImage() {
return !!this.el.querySelector('ion-img') || !!this.el.querySelector('img');
}
private getSize(): string | undefined {
const theme = getIonTheme(this);
const { size } = this;
// TODO(ROU-10752): Remove theme check when sizes are defined for all themes.
if (theme !== 'ionic') {
return undefined;
}
if (size === undefined) {
return 'medium';
}
return size;
}
render() {
const theme = getIonTheme(this);
const size = this.getSize();
return (
<Host
class={{
[theme]: true,
[`avatar-${size}`]: size !== undefined,
[`avatar-image`]: this.hasImage,
}}
>
<slot></slot>

View File

@@ -0,0 +1,189 @@
import { expect } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';
/**
* This behavior does not vary across directions.
*/
configs({ directions: ['ltr'], modes: ['ionic-md'] }).forEach(({ config, screenshot, title }) => {
test.describe(title('avatar: size'), () => {
test.describe('xsmall', () => {
test('should not have visual regressions when containing text', async ({ page }) => {
await page.setContent(
`
<ion-avatar size="xsmall">AB</ion-avatar>
`,
config
);
const avatar = page.locator('ion-avatar');
await expect(avatar).toHaveScreenshot(screenshot(`avatar-size-xsmall-text`));
});
test('should not have visual regressions when containing an icon', async ({ page }) => {
await page.setContent(
`
<ion-avatar size="xsmall">
<ion-icon name="person-outline"></ion-icon>
</ion-avatar>
`,
config
);
const avatar = page.locator('ion-avatar');
await expect(avatar).toHaveScreenshot(screenshot(`avatar-size-xsmall-icon`));
});
test('should not have visual regressions when containing an image', async ({ page }) => {
await page.setContent(
`
<ion-avatar size="xsmall">
<img src="/src/components/avatar/test/avatar.svg"/>
</ion-avatar>
`,
config
);
const avatar = page.locator('ion-avatar');
await expect(avatar).toHaveScreenshot(screenshot(`avatar-size-xsmall-image`));
});
});
test.describe('small', () => {
test('should not have visual regressions when containing text', async ({ page }) => {
await page.setContent(
`
<ion-avatar size="small">AB</ion-avatar>
`,
config
);
const avatar = page.locator('ion-avatar');
await expect(avatar).toHaveScreenshot(screenshot(`avatar-size-small-text`));
});
test('should not have visual regressions when containing an icon', async ({ page }) => {
await page.setContent(
`
<ion-avatar size="small">
<ion-icon name="person-outline"></ion-icon>
</ion-avatar>
`,
config
);
const avatar = page.locator('ion-avatar');
await expect(avatar).toHaveScreenshot(screenshot(`avatar-size-small-icon`));
});
test('should not have visual regressions when containing an image', async ({ page }) => {
await page.setContent(
`
<ion-avatar size="small">
<img src="/src/components/avatar/test/avatar.svg"/>
</ion-avatar>
`,
config
);
const avatar = page.locator('ion-avatar');
await expect(avatar).toHaveScreenshot(screenshot(`avatar-size-small-image`));
});
});
test.describe('medium', () => {
test('should not have visual regressions when containing text', async ({ page }) => {
await page.setContent(
`
<ion-avatar size="medium">AB</ion-avatar>
`,
config
);
const avatar = page.locator('ion-avatar');
await expect(avatar).toHaveScreenshot(screenshot(`avatar-size-medium-text`));
});
test('should not have visual regressions when containing an icon', async ({ page }) => {
await page.setContent(
`
<ion-avatar size="medium">
<ion-icon name="person-outline"></ion-icon>
</ion-avatar>
`,
config
);
const avatar = page.locator('ion-avatar');
await expect(avatar).toHaveScreenshot(screenshot(`avatar-size-medium-icon`));
});
test('should not have visual regressions when containing an image', async ({ page }) => {
await page.setContent(
`
<ion-avatar size="medium">
<img src="/src/components/avatar/test/avatar.svg"/>
</ion-avatar>
`,
config
);
const avatar = page.locator('ion-avatar');
await expect(avatar).toHaveScreenshot(screenshot(`avatar-size-medium-image`));
});
});
test.describe('large', () => {
test('should not have visual regressions when containing text', async ({ page }) => {
await page.setContent(
`
<ion-avatar size="large">AB</ion-avatar>
`,
config
);
const avatar = page.locator('ion-avatar');
await expect(avatar).toHaveScreenshot(screenshot(`avatar-size-large-text`));
});
test('should not have visual regressions when containing an icon', async ({ page }) => {
await page.setContent(
`
<ion-avatar size="large">
<ion-icon name="person-outline"></ion-icon>
</ion-avatar>
`,
config
);
const avatar = page.locator('ion-avatar');
await expect(avatar).toHaveScreenshot(screenshot(`avatar-size-large-icon`));
});
test('should not have visual regressions when containing an image', async ({ page }) => {
await page.setContent(
`
<ion-avatar size="large">
<img src="/src/components/avatar/test/avatar.svg"/>
</ion-avatar>
`,
config
);
const avatar = page.locator('ion-avatar');
await expect(avatar).toHaveScreenshot(screenshot(`avatar-size-large-image`));
});
});
});
});

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 652 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 578 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 592 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 740 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 688 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 728 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 798 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 819 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 540 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 521 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 500 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 627 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 585 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 624 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 695 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 717 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 637 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 465 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 460 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 501 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 481 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 480 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 590 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 562 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 390 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 396 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 384 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 438 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 490 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 525 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 456 B

View File

@@ -0,0 +1,86 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<title>Avatar - Size</title>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
<script src="../../../../../scripts/testing/scripts.js"></script>
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
<style>
.container {
display: flex;
gap: 10px;
}
</style>
</head>
<body>
<ion-app>
<ion-header>
<ion-toolbar>
<ion-title>Avatar - Size</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding" id="content" no-bounce>
<h2>Default</h2>
<div class="container">
<ion-avatar>AB</ion-avatar>
<ion-avatar>
<ion-icon name="person-outline"></ion-icon>
</ion-avatar>
<ion-avatar>
<img src="/src/components/avatar/test/avatar.svg" />
</ion-avatar>
</div>
<h2>Text</h2>
<div class="container">
<ion-avatar size="xsmall">AB</ion-avatar>
<ion-avatar size="small">AB</ion-avatar>
<ion-avatar size="medium">AB</ion-avatar>
<ion-avatar size="large">AB</ion-avatar>
</div>
<h2>Icons</h2>
<div class="container">
<ion-avatar size="xsmall">
<ion-icon name="person-outline"></ion-icon>
</ion-avatar>
<ion-avatar size="small">
<ion-icon name="person-outline"></ion-icon>
</ion-avatar>
<ion-avatar size="medium">
<ion-icon name="person-outline"></ion-icon>
</ion-avatar>
<ion-avatar size="large">
<ion-icon name="person-outline"></ion-icon>
</ion-avatar>
</div>
<h2>Images</h2>
<div class="container">
<ion-avatar size="xsmall">
<img src="/src/components/avatar/test/avatar.svg" />
</ion-avatar>
<ion-avatar size="small">
<img src="/src/components/avatar/test/avatar.svg" />
</ion-avatar>
<ion-avatar size="medium">
<img src="/src/components/avatar/test/avatar.svg" />
</ion-avatar>
<ion-avatar size="large">
<img src="/src/components/avatar/test/avatar.svg" />
</ion-avatar>
</div>
</ion-content>
</ion-app>
</body>
</html>

View File

@@ -0,0 +1,68 @@
@use "../../themes/ionic/ionic.globals.scss" as globals;
@import "./badge";
// Ionic Badge
// --------------------------------------------------
:host {
--padding-start: #{globals.$ionic-space-100};
--padding-end: #{globals.$ionic-space-100};
--padding-top: #{globals.$ionic-space-0};
--padding-bottom: #{globals.$ionic-space-0};
display: inline-flex;
align-items: center;
justify-content: center;
font-weight: globals.$ionic-font-weight-medium;
}
// Badge Sizes
// --------------------------------------------------
/* Small Badge */
:host(.badge-small) {
min-width: globals.$ionic-scale-800;
height: globals.$ionic-scale-800;
font-size: globals.$ionic-font-size-400;
line-height: globals.$ionic-line-height-600;
}
/* Medium Badge */
:host(.badge-medium) {
min-width: globals.$ionic-scale-1000;
height: globals.$ionic-scale-1000;
font-size: globals.$ionic-font-size-450;
line-height: globals.$ionic-line-height-700;
}
/* Large Badge */
:host(.badge-large) {
--padding-start: #{globals.$ionic-space-200};
--padding-end: #{globals.$ionic-space-200};
min-width: globals.$ionic-scale-1200;
height: globals.$ionic-scale-1200;
font-size: globals.$ionic-font-size-500;
line-height: globals.$ionic-line-height-700;
}
/* Extra Large Badge */
:host(.badge-xlarge) {
--padding-start: #{globals.$ionic-space-200};
--padding-end: #{globals.$ionic-space-200};
min-width: globals.$ionic-scale-1400;
height: globals.$ionic-scale-1400;
font-size: globals.$ionic-font-size-550;
line-height: globals.$ionic-line-height-700;
}

View File

@@ -14,7 +14,7 @@ import type { Color } from '../../interface';
styleUrls: {
ios: 'badge.ios.scss',
md: 'badge.md.scss',
ionic: 'badge.md.scss',
ionic: 'badge.ionic.scss',
},
shadow: true,
})
@@ -26,12 +26,36 @@ export class Badge implements ComponentInterface {
*/
@Prop({ reflect: true }) color?: Color;
/**
* Set to `"small"` for less height and width. Set to "medium" for slightly larger dimensions. Set to "large" for even greater height and width. Set to `"xlarge"` for the largest badge.
* Defaults to `"small"` for the `ionic` theme, undefined for all other themes.
*/
@Prop() size?: 'small' | 'medium' | 'large' | 'xlarge';
private getSize(): string | undefined {
const theme = getIonTheme(this);
const { size } = this;
// TODO(ROU-10747): Remove theme check when sizes are defined for all themes.
if (theme !== 'ionic') {
return undefined;
}
if (size === undefined) {
return 'small';
}
return size;
}
render() {
const size = this.getSize();
const theme = getIonTheme(this);
return (
<Host
class={createColorClasses(this.color, {
[theme]: true,
[`badge-${size}`]: size !== undefined,
})}
>
<slot></slot>

View File

@@ -0,0 +1,61 @@
import { expect } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';
/**
* This behavior does not vary across directions.
*/
configs({ directions: ['ltr'], modes: ['ionic-md'] }).forEach(({ config, screenshot, title }) => {
test.describe(title('badge: size'), () => {
test('should render small badges', async ({ page }) => {
await page.setContent(
`
<ion-badge size="small">00</ion-badge>
`,
config
);
const badge = page.locator('ion-badge');
await expect(badge).toHaveScreenshot(screenshot(`badge-size-small`));
});
test('should render medium badges', async ({ page }) => {
await page.setContent(
`
<ion-badge size="medium">00</ion-badge>
`,
config
);
const badge = page.locator('ion-badge');
await expect(badge).toHaveScreenshot(screenshot(`badge-size-medium`));
});
test('should render large badges', async ({ page }) => {
await page.setContent(
`
<ion-badge size="large">00</ion-badge>
`,
config
);
const badge = page.locator('ion-badge');
await expect(badge).toHaveScreenshot(screenshot(`badge-size-large`));
});
test('should render xlarge badges', async ({ page }) => {
await page.setContent(
`
<ion-badge size="xlarge">00</ion-badge>
`,
config
);
const badge = page.locator('ion-badge');
await expect(badge).toHaveScreenshot(screenshot(`badge-size-xlarge`));
});
});
});

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 561 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 546 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 501 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 924 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 473 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 459 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 536 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 440 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 632 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 607 B

View File

@@ -0,0 +1,51 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<title>Badge - Size</title>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
<script src="../../../../../scripts/testing/scripts.js"></script>
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
</head>
<body>
<ion-app>
<ion-header>
<ion-toolbar>
<ion-title>Button - Size</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding" id="content" no-bounce>
<ion-list>
<ion-item>
<ion-label>Default</ion-label>
<ion-badge slot="end">00</ion-badge>
</ion-item>
<ion-item>
<ion-label>Small</ion-label>
<ion-badge slot="end" size="small">00</ion-badge>
</ion-item>
<ion-item>
<ion-label>Medium</ion-label>
<ion-badge slot="end" size="medium">00</ion-badge>
</ion-item>
<ion-item>
<ion-label>Large</ion-label>
<ion-badge slot="end" size="large">00</ion-badge>
</ion-item>
<ion-item>
<ion-label>XLarge</ion-label>
<ion-badge slot="end" size="xlarge">00</ion-badge>
</ion-item>
</ion-list>
</ion-content>
</ion-app>
</body>
</html>

View File

@@ -132,20 +132,20 @@
// -------------------------------------------------------------------------------
:host(.button-soft) {
--border-radius: #{globals.$ionic-border-radius-rounded-medium};
--border-radius: #{globals.$ionic-border-radius-200};
}
:host(.button-soft.button-xsmall),
:host(.button-soft.button-small) {
--border-radius: #{globals.$ionic-border-radius-rounded-small};
--border-radius: #{globals.$ionic-border-radius-100};
}
:host(.button-round) {
--border-radius: #{globals.$ionic-border-radius-rounded-full};
--border-radius: #{globals.$ionic-border-radius-full};
}
:host(.button-rectangular) {
--border-radius: #{globals.$ionic-border-radius-square};
--border-radius: #{globals.$ionic-border-radius-0};
}
// Button Focused

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 104 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 132 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 92 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -4,15 +4,15 @@
// --------------------------------------------------
:host {
--background: #{globals.$ionic-color-neutral-0};
--border-radius: #{globals.$ionic-border-radius-rounded-small};
--background: #{globals.$ionic-color-base-white};
--border-radius: #{globals.$ionic-border-radius-100};
@include globals.padding(globals.$ionic-space-base);
@include globals.padding(globals.$ionic-space-400);
@include globals.border-radius(var(--border-radius));
display: block;
border: #{globals.$ionic-border-size-small} solid #{globals.$ionic-color-neutral-50};
border: #{globals.$ionic-border-size-025} solid #{globals.$ionic-color-neutral-500};
background: var(--background);
color: var(--color);
@@ -22,9 +22,9 @@
// ---------------------------------------------
:host(.card-round) {
--border-radius: #{globals.$ionic-border-radius-rounded-large};
--border-radius: #{globals.$ionic-border-radius-400};
}
:host(.card-rectangular) {
--border-radius: #{globals.$ionic-border-radius-square};
--border-radius: #{globals.$ionic-border-radius-0};
}

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -9,13 +9,13 @@ $ionic-states-focus-primary: #9ec4fd;
$ionic-states-hover: #{rgba(#05080f, 0.16)}; // We should review how to make this in the future, as we are setting scss variables with a var() and fallback, and it doesn't work inside a rgba().
:host {
--background: #{globals.$ionic-color-neutral-10};
--background: #{globals.$ionic-color-neutral-100};
--color: #{globals.$ionic-color-neutral-900};
--focus-ring-color: #{$ionic-states-focus-primary};
--focus-ring-width: #{globals.$ionic-border-size-medium};
--focus-ring-width: #{globals.$ionic-border-size-050};
@include globals.font-smoothing;
@include globals.padding(globals.$ionic-space-xxs, globals.$ionic-space-xs);
@include globals.padding(globals.$ionic-space-150, globals.$ionic-space-200);
@include globals.border-radius(var(--border-radius));
display: inline-flex;
@@ -25,7 +25,7 @@ $ionic-states-hover: #{rgba(#05080f, 0.16)}; // We should review how to make thi
align-items: center;
justify-content: center;
gap: globals.$ionic-space-xxxs;
gap: globals.$ionic-space-100;
background: var(--background);
color: var(--color);
@@ -33,7 +33,7 @@ $ionic-states-hover: #{rgba(#05080f, 0.16)}; // We should review how to make thi
font-family: globals.$ionic-font-family;
font-weight: globals.$ionic-font-weight-medium;
line-height: globals.$ionic-font-line-height-full;
line-height: globals.$ionic-line-height-full;
cursor: pointer;
@@ -49,7 +49,7 @@ $ionic-states-hover: #{rgba(#05080f, 0.16)}; // We should review how to make thi
:host(.chip-outline) {
--background: transparent;
border-width: globals.$ionic-border-size-small;
border-width: globals.$ionic-border-size-025;
border-color: globals.$ionic-color-neutral-100;
}
@@ -83,21 +83,21 @@ $ionic-states-hover: #{rgba(#05080f, 0.16)}; // We should review how to make thi
// ---------------------------------------------
:host(.chip-soft) {
--border-radius: #{globals.$ionic-border-radius-rounded-small};
--border-radius: #{globals.$ionic-border-radius-100};
}
:host(.chip-round) {
--border-radius: #{globals.$ionic-border-radius-rounded-large};
--border-radius: #{globals.$ionic-border-radius-400};
}
:host(.chip-rectangular) {
--border-radius: #{globals.$ionic-border-radius-square};
--border-radius: #{globals.$ionic-border-radius-0};
}
// Chip Icon
// ---------------------------------------------
::slotted(ion-icon) {
font-size: globals.$ionic-font-size-l;
font-size: globals.$ionic-font-size-400;
}
// Size
@@ -106,11 +106,11 @@ $ionic-states-hover: #{rgba(#05080f, 0.16)}; // We should review how to make thi
:host(.chip-small) {
min-height: 24px;
font-size: #{tokens.$ionic-font-size-s};
font-size: #{tokens.$ionic-font-size-300};
}
:host(.chip-large) {
min-height: 32px;
font-size: globals.$ionic-font-size-m;
font-size: globals.$ionic-font-size-350;
}

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 965 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1010 B

After

Width:  |  Height:  |  Size: 898 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 888 B

After

Width:  |  Height:  |  Size: 854 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1020 B

After

Width:  |  Height:  |  Size: 1.2 KiB

Some files were not shown because too many files have changed in this diff Show More