Compare commits
15 Commits
sp/next
...
liamdebeas
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
01ace15b2d | ||
|
|
7ce115993a | ||
|
|
77c67f2362 | ||
|
|
a8c06c118e | ||
|
|
19f7be8877 | ||
|
|
1c4b6bd8f2 | ||
|
|
17f5be1edd | ||
|
|
efdaf38520 | ||
|
|
b11c630410 | ||
|
|
5234224700 | ||
|
|
6d6fd4af1b | ||
|
|
7cfee535ac | ||
|
|
167e311474 | ||
|
|
6c500fd6b2 | ||
|
|
6e477b743e |
@@ -29,4 +29,4 @@ runs:
|
||||
with:
|
||||
name: ionic-core
|
||||
output: core/CoreBuild.zip
|
||||
paths: core/dist core/components core/css core/hydrate core/loader core/src/components.d.ts
|
||||
paths: core/dist core/components core/src/foundations core/css core/hydrate core/loader core/src/components.d.ts
|
||||
|
||||
@@ -31,4 +31,4 @@ runs:
|
||||
with:
|
||||
name: ionic-core
|
||||
output: core/CoreBuild.zip
|
||||
paths: core/dist core/components core/css core/hydrate core/loader core/src/components.d.ts core/api.txt
|
||||
paths: core/dist core/components core/src/foundations core/css core/hydrate core/loader core/src/components.d.ts core/api.txt
|
||||
|
||||
@@ -30,6 +30,7 @@ This is a comprehensive list of the breaking changes introduced in the major ver
|
||||
- [Progress bar](#version-8x-progress-bar)
|
||||
- [Radio](#version-8x-radio)
|
||||
- [Range](#version-8x-range)
|
||||
- [Searchbar](#version-8x-searchbar)
|
||||
- [Select](#version-8x-select)
|
||||
- [Textarea](#version-8x-textarea)
|
||||
- [Toggle](#version-8x-toggle)
|
||||
@@ -264,6 +265,10 @@ For more information on styling toast buttons, refer to the [Toast Theming docum
|
||||
|
||||
- The `legacy` property and support for the legacy syntax, which involved placing an `ion-range` inside of an `ion-item` with an `ion-label`, have been removed. Ionic will also no longer attempt to automatically associate form controls with sibling `<label>` elements as these label elements are now used inside the form control. Developers should provide a label (either visible text or `aria-label`) directly to the form control. For more information on migrating from the legacy range syntax, refer to the [Range documentation](https://ionicframework.com/docs/api/range#migrating-from-legacy-range-syntax).
|
||||
|
||||
<h4 id="version-8x-searchbar">Searchbar</h4>
|
||||
|
||||
- The `autocapitalize` property now defaults to `'off'`.
|
||||
|
||||
<h4 id="version-8x-select">Select</h4>
|
||||
|
||||
- The `legacy` property and support for the legacy syntax, which involved placing an `ion-select` inside of an `ion-item` with an `ion-label`, have been removed. Ionic will also no longer attempt to automatically associate form controls with sibling `<label>` elements as these label elements are now used inside the form control. Developers should provide a label (either visible text or `aria-label`) directly to the form control. For more information on migrating from the legacy select syntax, refer to the [Select documentation](https://ionicframework.com/docs/api/select#migrating-from-legacy-select-syntax).
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
</h1>
|
||||
|
||||
<p align="center">
|
||||
Ionic is an open source app development toolkit for building modern, fast, top-quality cross-platform native and Progressive Web Apps from a single codebase with JavaScript and the Web.
|
||||
Ionic is an open source app development toolkit for building modern, fast, top-quality cross-platform native and Progressive Web Apps from a single codebase with JavaScript and the Web. test 123
|
||||
</p>
|
||||
<p align="center">
|
||||
Ionic is based on <a href="https://www.webcomponents.org/introduction">Web Components</a>, which enables significant performance, usability, and feature improvements alongside support for popular web frameworks like <a href="https://angular.io/">Angular</a>, <a href="https://reactjs.com/">React</a>, and <a href="https://vuejs.org/">Vue</a>.
|
||||
|
||||
@@ -23,10 +23,10 @@ rules:
|
||||
at-rule-empty-line-before:
|
||||
- always
|
||||
- except:
|
||||
- blockless-after-blockless
|
||||
- first-nested
|
||||
- blockless-after-blockless
|
||||
- first-nested
|
||||
ignore:
|
||||
- after-comment
|
||||
- after-comment
|
||||
|
||||
block-closing-brace-newline-before:
|
||||
- always
|
||||
@@ -40,14 +40,13 @@ rules:
|
||||
custom-property-empty-line-before:
|
||||
- always
|
||||
- except:
|
||||
- after-comment
|
||||
- after-custom-property
|
||||
- first-nested
|
||||
- after-comment
|
||||
- after-custom-property
|
||||
- first-nested
|
||||
|
||||
declaration-no-important:
|
||||
- true
|
||||
|
||||
|
||||
order/order:
|
||||
- custom-properties
|
||||
- dollar-variables
|
||||
@@ -57,216 +56,212 @@ rules:
|
||||
|
||||
# https://github.com/sasstools/sass-lint/blob/develop/lib/config/property-sort-orders/smacss.yml
|
||||
order/properties-order:
|
||||
|
||||
# Box
|
||||
|
||||
- emptyLineBefore: always
|
||||
properties:
|
||||
- display
|
||||
- position
|
||||
- top
|
||||
- right
|
||||
- bottom
|
||||
- left
|
||||
- display
|
||||
- position
|
||||
- top
|
||||
- right
|
||||
- bottom
|
||||
- left
|
||||
|
||||
- emptyLineBefore: always
|
||||
properties:
|
||||
- flex
|
||||
- flex-basis
|
||||
- flex-direction
|
||||
- flex-flow
|
||||
- flex-grow
|
||||
- flex-shrink
|
||||
- flex-wrap
|
||||
- align-content
|
||||
- align-items
|
||||
- align-self
|
||||
- justify-content
|
||||
- order
|
||||
- flex
|
||||
- flex-basis
|
||||
- flex-direction
|
||||
- flex-flow
|
||||
- flex-grow
|
||||
- flex-shrink
|
||||
- flex-wrap
|
||||
- align-content
|
||||
- align-items
|
||||
- align-self
|
||||
- justify-content
|
||||
- order
|
||||
|
||||
- emptyLineBefore: always
|
||||
properties:
|
||||
- width
|
||||
- min-width
|
||||
- max-width
|
||||
- width
|
||||
- min-width
|
||||
- max-width
|
||||
|
||||
- height
|
||||
- min-height
|
||||
- max-height
|
||||
- height
|
||||
- min-height
|
||||
- max-height
|
||||
|
||||
- emptyLineBefore: always
|
||||
properties:
|
||||
- margin
|
||||
- margin-top
|
||||
- margin-right
|
||||
- margin-bottom
|
||||
- margin-left
|
||||
- margin
|
||||
- margin-top
|
||||
- margin-right
|
||||
- margin-bottom
|
||||
- margin-left
|
||||
|
||||
- emptyLineBefore: always
|
||||
properties:
|
||||
- padding
|
||||
- padding-top
|
||||
- padding-right
|
||||
- padding-bottom
|
||||
- padding-left
|
||||
- padding
|
||||
- padding-top
|
||||
- padding-right
|
||||
- padding-bottom
|
||||
- padding-left
|
||||
|
||||
- emptyLineBefore: always
|
||||
properties:
|
||||
- float
|
||||
- clear
|
||||
- float
|
||||
- clear
|
||||
|
||||
- emptyLineBefore: always
|
||||
properties:
|
||||
- columns
|
||||
- column-gap
|
||||
- column-fill
|
||||
- column-rule
|
||||
- column-span
|
||||
- column-count
|
||||
- column-width
|
||||
- columns
|
||||
- column-gap
|
||||
- column-fill
|
||||
- column-rule
|
||||
- column-span
|
||||
- column-count
|
||||
- column-width
|
||||
|
||||
- emptyLineBefore: always
|
||||
properties:
|
||||
- transform
|
||||
- transform-box
|
||||
- transform-origin
|
||||
- transform-style
|
||||
- transform
|
||||
- transform-box
|
||||
- transform-origin
|
||||
- transform-style
|
||||
|
||||
- emptyLineBefore: always
|
||||
properties:
|
||||
- transition
|
||||
- transition-delay
|
||||
- transition-duration
|
||||
- transition-property
|
||||
- transition-timing-function
|
||||
- transition
|
||||
- transition-delay
|
||||
- transition-duration
|
||||
- transition-property
|
||||
- transition-timing-function
|
||||
|
||||
# Border
|
||||
|
||||
- emptyLineBefore: always
|
||||
properties:
|
||||
- border
|
||||
- border-top
|
||||
- border-right
|
||||
- border-bottom
|
||||
- border-left
|
||||
- border-width
|
||||
- border-top-width
|
||||
- border-right-width
|
||||
- border-bottom-width
|
||||
- border-left-width
|
||||
- border
|
||||
- border-top
|
||||
- border-right
|
||||
- border-bottom
|
||||
- border-left
|
||||
- border-width
|
||||
- border-top-width
|
||||
- border-right-width
|
||||
- border-bottom-width
|
||||
- border-left-width
|
||||
|
||||
- border-style
|
||||
- border-top-style
|
||||
- border-right-style
|
||||
- border-bottom-style
|
||||
- border-left-style
|
||||
- border-style
|
||||
- border-top-style
|
||||
- border-right-style
|
||||
- border-bottom-style
|
||||
- border-left-style
|
||||
|
||||
- border-radius
|
||||
- border-top-left-radius
|
||||
- border-top-right-radius
|
||||
- border-bottom-left-radius
|
||||
- border-bottom-right-radius
|
||||
- border-radius
|
||||
- border-top-left-radius
|
||||
- border-top-right-radius
|
||||
- border-bottom-left-radius
|
||||
- border-bottom-right-radius
|
||||
|
||||
- border-color
|
||||
- border-top-color
|
||||
- border-right-color
|
||||
- border-bottom-color
|
||||
- border-left-color
|
||||
- border-color
|
||||
- border-top-color
|
||||
- border-right-color
|
||||
- border-bottom-color
|
||||
- border-left-color
|
||||
|
||||
- emptyLineBefore: always
|
||||
properties:
|
||||
- outline
|
||||
- outline-color
|
||||
- outline-offset
|
||||
- outline-style
|
||||
- outline-width
|
||||
- outline
|
||||
- outline-color
|
||||
- outline-offset
|
||||
- outline-style
|
||||
- outline-width
|
||||
|
||||
# Background
|
||||
|
||||
- emptyLineBefore: always
|
||||
properties:
|
||||
- background
|
||||
- background-attachment
|
||||
- background-clip
|
||||
- background-color
|
||||
- background-image
|
||||
- background-repeat
|
||||
- background-position
|
||||
- background-size
|
||||
- background
|
||||
- background-attachment
|
||||
- background-clip
|
||||
- background-color
|
||||
- background-image
|
||||
- background-repeat
|
||||
- background-position
|
||||
- background-size
|
||||
|
||||
# Text
|
||||
# Text
|
||||
|
||||
- color
|
||||
- color
|
||||
|
||||
- emptyLineBefore: always
|
||||
properties:
|
||||
- font
|
||||
- font-family
|
||||
- font-size
|
||||
- font-smoothing
|
||||
- font-style
|
||||
- font-variant
|
||||
- font-weight
|
||||
- font
|
||||
- font-family
|
||||
- font-size
|
||||
- font-smoothing
|
||||
- font-style
|
||||
- font-variant
|
||||
- font-weight
|
||||
|
||||
- emptyLineBefore: always
|
||||
properties:
|
||||
- letter-spacing
|
||||
- line-height
|
||||
- list-style
|
||||
- letter-spacing
|
||||
- line-height
|
||||
- list-style
|
||||
|
||||
- emptyLineBefore: always
|
||||
properties:
|
||||
- text-align
|
||||
- text-decoration
|
||||
- text-indent
|
||||
- text-overflow
|
||||
- text-rendering
|
||||
- text-shadow
|
||||
- text-transform
|
||||
- text-wrap
|
||||
- text-align
|
||||
- text-decoration
|
||||
- text-indent
|
||||
- text-overflow
|
||||
- text-rendering
|
||||
- text-shadow
|
||||
- text-transform
|
||||
- text-wrap
|
||||
|
||||
- emptyLineBefore: always
|
||||
properties:
|
||||
- white-space
|
||||
- word-spacing
|
||||
- white-space
|
||||
- word-spacing
|
||||
|
||||
# Other
|
||||
|
||||
- emptyLineBefore: always
|
||||
properties:
|
||||
- border-collapse
|
||||
- border-spacing
|
||||
- box-shadow
|
||||
- caption-side
|
||||
- contain
|
||||
- content
|
||||
- cursor
|
||||
- direction
|
||||
- empty-cells
|
||||
- object-fit
|
||||
- opacity
|
||||
- overflow
|
||||
- quotes
|
||||
- speak
|
||||
- table-layout
|
||||
- touch-action
|
||||
- user-select
|
||||
- vertical-align
|
||||
- visibility
|
||||
- z-index
|
||||
- border-collapse
|
||||
- border-spacing
|
||||
- box-shadow
|
||||
- caption-side
|
||||
- contain
|
||||
- content
|
||||
- cursor
|
||||
- direction
|
||||
- empty-cells
|
||||
- object-fit
|
||||
- opacity
|
||||
- overflow
|
||||
- quotes
|
||||
- speak
|
||||
- table-layout
|
||||
- touch-action
|
||||
- user-select
|
||||
- vertical-align
|
||||
- visibility
|
||||
- z-index
|
||||
|
||||
property-disallowed-list:
|
||||
- background-position
|
||||
- right
|
||||
- left
|
||||
- float
|
||||
- padding
|
||||
- padding-left
|
||||
- padding-right
|
||||
- margin
|
||||
- margin-left
|
||||
- margin-right
|
||||
- border-radius
|
||||
- border-top-left-radius
|
||||
- border-top-right-radius
|
||||
- border-bottom-right-radius
|
||||
|
||||
12
core/api.txt
@@ -603,7 +603,7 @@ ion-input,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secon
|
||||
ion-input,prop,counter,boolean,false,false,false
|
||||
ion-input,prop,counterFormatter,((inputLength: number, maxLength: number) => string) | undefined,undefined,false,false
|
||||
ion-input,prop,debounce,number | undefined,undefined,false,false
|
||||
ion-input,prop,disabled,boolean,false,false,false
|
||||
ion-input,prop,disabled,boolean,false,false,true
|
||||
ion-input,prop,enterkeyhint,"done" | "enter" | "go" | "next" | "previous" | "search" | "send" | undefined,undefined,false,false
|
||||
ion-input,prop,errorText,string | undefined,undefined,false,false
|
||||
ion-input,prop,fill,"outline" | "solid" | undefined,undefined,false,false
|
||||
@@ -620,7 +620,7 @@ ion-input,prop,multiple,boolean | undefined,undefined,false,false
|
||||
ion-input,prop,name,string,this.inputId,false,false
|
||||
ion-input,prop,pattern,string | undefined,undefined,false,false
|
||||
ion-input,prop,placeholder,string | undefined,undefined,false,false
|
||||
ion-input,prop,readonly,boolean,false,false,false
|
||||
ion-input,prop,readonly,boolean,false,false,true
|
||||
ion-input,prop,required,boolean,false,false,false
|
||||
ion-input,prop,shape,"round" | undefined,undefined,false,false
|
||||
ion-input,prop,spellcheck,boolean,false,false,false
|
||||
@@ -653,6 +653,12 @@ ion-input,css-prop,--placeholder-font-style
|
||||
ion-input,css-prop,--placeholder-font-weight
|
||||
ion-input,css-prop,--placeholder-opacity
|
||||
|
||||
ion-input-password-toggle,shadow
|
||||
ion-input-password-toggle,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record<never, never> | undefined,undefined,false,true
|
||||
ion-input-password-toggle,prop,hideIcon,string | undefined,undefined,false,false
|
||||
ion-input-password-toggle,prop,mode,"ios" | "md",undefined,false,false
|
||||
ion-input-password-toggle,prop,showIcon,string | undefined,undefined,false,false
|
||||
|
||||
ion-item,shadow
|
||||
ion-item,prop,button,boolean,false,false,false
|
||||
ion-item,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record<never, never> | undefined,undefined,false,true
|
||||
@@ -1265,7 +1271,7 @@ ion-row,prop,theme,"ios" | "md" | "ionic",undefined,false,false
|
||||
|
||||
ion-searchbar,scoped
|
||||
ion-searchbar,prop,animated,boolean,false,false,false
|
||||
ion-searchbar,prop,autocapitalize,string,undefined,true,false
|
||||
ion-searchbar,prop,autocapitalize,string,'off',false,false
|
||||
ion-searchbar,prop,autocomplete,"name" | "email" | "tel" | "url" | "on" | "off" | "honorific-prefix" | "given-name" | "additional-name" | "family-name" | "honorific-suffix" | "nickname" | "username" | "new-password" | "current-password" | "one-time-code" | "organization-title" | "organization" | "street-address" | "address-line1" | "address-line2" | "address-line3" | "address-level4" | "address-level3" | "address-level2" | "address-level1" | "country" | "country-name" | "postal-code" | "cc-name" | "cc-given-name" | "cc-additional-name" | "cc-family-name" | "cc-number" | "cc-exp" | "cc-exp-month" | "cc-exp-year" | "cc-csc" | "cc-type" | "transaction-currency" | "transaction-amount" | "language" | "bday" | "bday-day" | "bday-month" | "bday-year" | "sex" | "tel-country-code" | "tel-national" | "tel-area-code" | "tel-local" | "tel-extension" | "impp" | "photo",'off',false,false
|
||||
ion-searchbar,prop,autocorrect,"off" | "on",'off',false,false
|
||||
ion-searchbar,prop,cancelButtonIcon,string,config.get('backButtonIcon', arrowBackSharp) as string,false,false
|
||||
|
||||
1647
core/package-lock.json
generated
@@ -67,14 +67,16 @@
|
||||
"rollup": "^2.26.4",
|
||||
"sass": "^1.33.0",
|
||||
"serve": "^14.0.1",
|
||||
"style-dictionary": "^3.9.2",
|
||||
"stylelint": "^13.13.1",
|
||||
"stylelint-order": "^4.1.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "npm run clean && npm run build.css && stencil build --es5 --docs-json dist/docs.json",
|
||||
"build": "npm run clean && npm run build.tokens && npm run build.css && stencil build --es5 --docs-json dist/docs.json",
|
||||
"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",
|
||||
"clean": "node scripts/clean.js",
|
||||
"css.minify": "cleancss -O2 -o ./css/ionic.bundle.css ./css/ionic.bundle.css",
|
||||
"css.sass": "sass --embed-sources src/css:./css",
|
||||
|
||||
174
core/scripts/tokens.js
Normal file
@@ -0,0 +1,174 @@
|
||||
/* 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';
|
||||
|
||||
// CSS vanilla :root format
|
||||
StyleDictionary.registerFormat({
|
||||
name: 'rootFormat',
|
||||
formatter({ dictionary, file }) {
|
||||
// Add a prefix to all variable names
|
||||
const prefixedVariables = dictionary.allProperties.map((prop) => {
|
||||
return ` --${variablesPrefix}-${prop.name}: ${prop.value};`;
|
||||
});
|
||||
|
||||
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) => {
|
||||
return `$${variablesPrefix}-${prop.name}: var(--${variablesPrefix}-${prop.name}, ${prop.value});`;
|
||||
});
|
||||
|
||||
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: $ionic-${prop.name};\n};
|
||||
.${variablesPrefix}-padding-${className} {\n padding: $ionic-${prop.name};\n}`;
|
||||
break;
|
||||
default:
|
||||
utilityClass = `.${variablesPrefix}-${className} {\n ${tokenType}: $ionic-${prop.name};\n}`;
|
||||
}
|
||||
|
||||
return utilityClass;
|
||||
});
|
||||
|
||||
return [fileHeader({ file }), '@import "./ionic.vars";\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(_a) {
|
||||
var contents = _a.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();
|
||||
49
core/src/components.d.ts
vendored
@@ -1471,6 +1471,25 @@ export namespace Components {
|
||||
*/
|
||||
"value"?: string | number | null;
|
||||
}
|
||||
interface IonInputPasswordToggle {
|
||||
/**
|
||||
* The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics).
|
||||
*/
|
||||
"color"?: Color;
|
||||
/**
|
||||
* The icon that can be used to represent hiding a password. If not set, the "eyeOff" Ionicon will be used.
|
||||
*/
|
||||
"hideIcon"?: string;
|
||||
/**
|
||||
* The mode determines which platform styles to use.
|
||||
*/
|
||||
"mode"?: "ios" | "md";
|
||||
/**
|
||||
* The icon that can be used to represent showing a password. If not set, the "eye" Ionicon will be used.
|
||||
*/
|
||||
"showIcon"?: string;
|
||||
"type": TextFieldTypes;
|
||||
}
|
||||
interface IonItem {
|
||||
/**
|
||||
* If `true`, a button tag will be rendered and the item will be tappable.
|
||||
@@ -4323,6 +4342,12 @@ declare global {
|
||||
prototype: HTMLIonInputElement;
|
||||
new (): HTMLIonInputElement;
|
||||
};
|
||||
interface HTMLIonInputPasswordToggleElement extends Components.IonInputPasswordToggle, HTMLStencilElement {
|
||||
}
|
||||
var HTMLIonInputPasswordToggleElement: {
|
||||
prototype: HTMLIonInputPasswordToggleElement;
|
||||
new (): HTMLIonInputPasswordToggleElement;
|
||||
};
|
||||
interface HTMLIonItemElement extends Components.IonItem, HTMLStencilElement {
|
||||
}
|
||||
var HTMLIonItemElement: {
|
||||
@@ -5147,6 +5172,7 @@ declare global {
|
||||
"ion-infinite-scroll": HTMLIonInfiniteScrollElement;
|
||||
"ion-infinite-scroll-content": HTMLIonInfiniteScrollContentElement;
|
||||
"ion-input": HTMLIonInputElement;
|
||||
"ion-input-password-toggle": HTMLIonInputPasswordToggleElement;
|
||||
"ion-item": HTMLIonItemElement;
|
||||
"ion-item-divider": HTMLIonItemDividerElement;
|
||||
"ion-item-group": HTMLIonItemGroupElement;
|
||||
@@ -6689,6 +6715,25 @@ declare namespace LocalJSX {
|
||||
*/
|
||||
"value"?: string | number | null;
|
||||
}
|
||||
interface IonInputPasswordToggle {
|
||||
/**
|
||||
* The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics).
|
||||
*/
|
||||
"color"?: Color;
|
||||
/**
|
||||
* The icon that can be used to represent hiding a password. If not set, the "eyeOff" Ionicon will be used.
|
||||
*/
|
||||
"hideIcon"?: string;
|
||||
/**
|
||||
* The mode determines which platform styles to use.
|
||||
*/
|
||||
"mode"?: "ios" | "md";
|
||||
/**
|
||||
* The icon that can be used to represent showing a password. If not set, the "eye" Ionicon will be used.
|
||||
*/
|
||||
"showIcon"?: string;
|
||||
"type"?: TextFieldTypes;
|
||||
}
|
||||
interface IonItem {
|
||||
/**
|
||||
* If `true`, a button tag will be rendered and the item will be tappable.
|
||||
@@ -8121,7 +8166,7 @@ declare namespace LocalJSX {
|
||||
/**
|
||||
* Indicates whether and how the text value should be automatically capitalized as it is entered/edited by the user. Available options: `"off"`, `"none"`, `"on"`, `"sentences"`, `"words"`, `"characters"`.
|
||||
*/
|
||||
"autocapitalize": string;
|
||||
"autocapitalize"?: string;
|
||||
/**
|
||||
* Set the input's autocomplete property.
|
||||
*/
|
||||
@@ -9064,6 +9109,7 @@ declare namespace LocalJSX {
|
||||
"ion-infinite-scroll": IonInfiniteScroll;
|
||||
"ion-infinite-scroll-content": IonInfiniteScrollContent;
|
||||
"ion-input": IonInput;
|
||||
"ion-input-password-toggle": IonInputPasswordToggle;
|
||||
"ion-item": IonItem;
|
||||
"ion-item-divider": IonItemDivider;
|
||||
"ion-item-group": IonItemGroup;
|
||||
@@ -9162,6 +9208,7 @@ declare module "@stencil/core" {
|
||||
"ion-infinite-scroll": LocalJSX.IonInfiniteScroll & JSXBase.HTMLAttributes<HTMLIonInfiniteScrollElement>;
|
||||
"ion-infinite-scroll-content": LocalJSX.IonInfiniteScrollContent & JSXBase.HTMLAttributes<HTMLIonInfiniteScrollContentElement>;
|
||||
"ion-input": LocalJSX.IonInput & JSXBase.HTMLAttributes<HTMLIonInputElement>;
|
||||
"ion-input-password-toggle": LocalJSX.IonInputPasswordToggle & JSXBase.HTMLAttributes<HTMLIonInputPasswordToggleElement>;
|
||||
"ion-item": LocalJSX.IonItem & JSXBase.HTMLAttributes<HTMLIonItemElement>;
|
||||
"ion-item-divider": LocalJSX.IonItemDivider & JSXBase.HTMLAttributes<HTMLIonItemDividerElement>;
|
||||
"ion-item-group": LocalJSX.IonItemGroup & JSXBase.HTMLAttributes<HTMLIonItemGroupElement>;
|
||||
|
||||
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
@@ -142,6 +142,7 @@
|
||||
|
||||
// Button Shape Rectangular
|
||||
// -------------------------------------------------------------------------------
|
||||
|
||||
:host(.button-rectangular) {
|
||||
--border-radius: #{$button-ionic-rectangular-border};
|
||||
}
|
||||
@@ -168,6 +169,11 @@
|
||||
--background-activated: #{ion-color(primary, shade)};
|
||||
}
|
||||
|
||||
:host(.button-solid.ion-color.ion-activated) .button-native::after {
|
||||
background: #{current-color(shade)};
|
||||
}
|
||||
|
||||
|
||||
// Fill Outline Button
|
||||
// --------------------------------------------------
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { configs, test } from '@utils/test/playwright';
|
||||
/**
|
||||
* Fill="clear" does not render differently based on the direction.
|
||||
*/
|
||||
configs({ directions: ['ltr'], themes: ['ios', 'md', 'ionic'] }).forEach(({ title, config, screenshot }) => {
|
||||
configs({ directions: ['ltr'], modes: ['ios', 'md', 'ionic-md'] }).forEach(({ title, config, screenshot }) => {
|
||||
test.describe(title('button: fill: clear'), () => {
|
||||
test('should not have visual regressions', async ({ page }) => {
|
||||
await page.goto(`/src/components/button/test/clear`, config);
|
||||
|
||||
@@ -74,7 +74,7 @@ configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
|
||||
/**
|
||||
* Shape="rectangular" is only available in the Ionic theme.
|
||||
*/
|
||||
configs({ directions: ['ltr'], themes: ['ionic'] }).forEach(({ title, screenshot, config }) => {
|
||||
configs({ directions: ['ltr'], modes: ['ionic-md'] }).forEach(({ title, screenshot, config }) => {
|
||||
test.describe(title('button: shape'), () => {
|
||||
test.describe('rectangular', () => {
|
||||
test('should not have visual regressions', async ({ page }) => {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { configs, test } from '@utils/test/playwright';
|
||||
|
||||
// TODO: FW-6077 - Limit this test to just the Ionic theme on MD mode.
|
||||
configs({ directions: ['ltr'], themes: ['ionic', 'md', 'ios'] }).forEach(({ title, screenshot, config }) => {
|
||||
configs({ directions: ['ltr'], modes: ['ionic-md', 'md', 'ios'] }).forEach(({ title, screenshot, config }) => {
|
||||
test.describe(title('button: size'), () => {
|
||||
test('should render small buttons', async ({ page }) => {
|
||||
await page.setContent(
|
||||
@@ -65,7 +64,7 @@ configs({ directions: ['ltr'], themes: ['ionic', 'md', 'ios'] }).forEach(({ titl
|
||||
/**
|
||||
* The following tests are specific to the Ionic theme and do not depend on the text direction.
|
||||
*/
|
||||
configs({ directions: ['ltr'], themes: ['ionic'] }).forEach(({ title, screenshot, config }) => {
|
||||
configs({ directions: ['ltr'], modes: ['ionic-md'] }).forEach(({ title, screenshot, config }) => {
|
||||
test.describe(title('button: size'), () => {
|
||||
test('should render xsmall buttons', async ({ page }) => {
|
||||
await page.setContent(`<ion-button size="xsmall" fill="solid">X-Small Button</ion-button>`, config);
|
||||
|
||||
84
core/src/components/button/test/states/button.e2e.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { configs, test } from '@utils/test/playwright';
|
||||
|
||||
configs({ directions: ['ltr'], modes: ['ionic-md', 'md', 'ios'] }).forEach(({ title, screenshot, config }) => {
|
||||
test.describe(title('button: activated'), () => {
|
||||
test('should render solid button', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-button class="ion-activated">Button</ion-button>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const button = page.locator('ion-button');
|
||||
|
||||
await expect(button).toHaveScreenshot(screenshot(`button-activated-solid`));
|
||||
});
|
||||
|
||||
test('should render solid button with color', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-button color="tertiary" class="ion-activated">Button</ion-button>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const button = page.locator('ion-button');
|
||||
|
||||
await expect(button).toHaveScreenshot(screenshot(`button-activated-solid-color`));
|
||||
});
|
||||
|
||||
test('should render outline button', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-button fill="outline" class="ion-activated">Button</ion-button>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const button = page.locator('ion-button');
|
||||
|
||||
await expect(button).toHaveScreenshot(screenshot(`button-activated-outline`));
|
||||
});
|
||||
|
||||
test('should render outline button with color', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-button color="tertiary" fill="outline" class="ion-activated">Button</ion-button>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const button = page.locator('ion-button');
|
||||
|
||||
await expect(button).toHaveScreenshot(screenshot(`button-activated-outline-color`));
|
||||
});
|
||||
|
||||
test('should render clear button', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-button fill="clear" class="ion-activated">Button</ion-button>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const button = page.locator('ion-button');
|
||||
|
||||
await expect(button).toHaveScreenshot(screenshot(`button-activated-clear`));
|
||||
});
|
||||
|
||||
test('should render clear button with color', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-button color="tertiary" fill="clear" class="ion-activated">Button</ion-button>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const button = page.locator('ion-button');
|
||||
|
||||
await expect(button).toHaveScreenshot(screenshot(`button-activated-clear-color`));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 945 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 909 B |
|
After Width: | Height: | Size: 902 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 919 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 986 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1014 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
89
core/src/components/chip/chip.ionic.scss
Normal file
@@ -0,0 +1,89 @@
|
||||
@use "../../themes/ionic.mixins" as mixins;
|
||||
@use "../../foundations/ionic.vars.scss" as tokens;
|
||||
|
||||
// Chip
|
||||
// --------------------------------------------------
|
||||
|
||||
// TODO(ROU-4870): there is no token yet for these ones, but it should be created in the future, once UX team has figma tokens done
|
||||
$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: #{tokens.$ionic-color-neutral-10};
|
||||
--border-color: transparent;
|
||||
--color: #{tokens.$ionic-color-neutral-900};
|
||||
--focus-ring-color: #{$ionic-states-focus-primary};
|
||||
--focus-ring-width: #{tokens.$ionic-border-size-medium};
|
||||
|
||||
@include mixins.font-smoothing;
|
||||
@include mixins.padding(tokens.$ionic-space-xs, tokens.$ionic-space-xxs);
|
||||
@include mixins.border-radius(tokens.$ionic-border-radius-rounded-large);
|
||||
|
||||
display: inline-flex;
|
||||
|
||||
position: relative;
|
||||
|
||||
align-items: center;
|
||||
|
||||
gap: tokens.$ionic-space-xxxs;
|
||||
|
||||
min-height: 32px;
|
||||
|
||||
border-width: tokens.$ionic-border-size-small;
|
||||
border-style: solid;
|
||||
|
||||
border-color: var(--border-color);
|
||||
|
||||
background: var(--background);
|
||||
color: var(--color);
|
||||
|
||||
font-family: tokens.$ionic-font-family;
|
||||
font-size: tokens.$ionic-font-size-m;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
box-sizing: border-box;
|
||||
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
// Outline Chip
|
||||
// ---------------------------------------------
|
||||
|
||||
:host(.chip-outline) {
|
||||
--background: transparent;
|
||||
border-color: tokens.$ionic-color-neutral-100;
|
||||
}
|
||||
|
||||
// Chip: Focus
|
||||
// ---------------------------------------------
|
||||
|
||||
:host(.ion-focused) {
|
||||
outline: var(--focus-ring-width) solid var(--focus-ring-color);
|
||||
outline-offset: var(--focus-ring-width);
|
||||
}
|
||||
|
||||
// Chip: Hover
|
||||
// ---------------------------------------------
|
||||
|
||||
@media (any-hover: hover) {
|
||||
:host(:hover) {
|
||||
--background: #{$ionic-states-hover};
|
||||
}
|
||||
}
|
||||
|
||||
// Chip: Disabled
|
||||
// ---------------------------------------------
|
||||
|
||||
:host(.chip-disabled) {
|
||||
opacity: 0.6;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
// Chip Icon
|
||||
// ---------------------------------------------
|
||||
::slotted(ion-icon) {
|
||||
font-size: tokens.$ionic-font-size-l;
|
||||
}
|
||||
@@ -14,7 +14,7 @@ import type { Color } from '../../interface';
|
||||
styleUrls: {
|
||||
ios: 'chip.ios.scss',
|
||||
md: 'chip.md.scss',
|
||||
ionic: 'chip.md.scss',
|
||||
ionic: 'chip.ionic.scss',
|
||||
},
|
||||
shadow: true,
|
||||
})
|
||||
@@ -47,6 +47,7 @@ export class Chip implements ComponentInterface {
|
||||
'chip-outline': this.outline,
|
||||
'chip-disabled': this.disabled,
|
||||
'ion-activatable': true,
|
||||
'ion-focusable': true,
|
||||
})}
|
||||
>
|
||||
<slot></slot>
|
||||
|
||||
83
core/src/components/chip/test/theme-ionic/index.html
Normal file
@@ -0,0 +1,83 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Chip - Ionic States</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-content>
|
||||
<h2>Info Chip</h2>
|
||||
|
||||
<h3>Default</h3>
|
||||
<p>
|
||||
<ion-chip>
|
||||
<ion-label>Default</ion-label>
|
||||
</ion-chip>
|
||||
</p>
|
||||
|
||||
<h3>Focus</h3>
|
||||
<p>
|
||||
<ion-chip class="ion-focused">
|
||||
<ion-label>Focused</ion-label>
|
||||
</ion-chip>
|
||||
</p>
|
||||
|
||||
<h3>Disabled</h3>
|
||||
<p>
|
||||
<ion-chip disabled>
|
||||
<ion-label>Disabled</ion-label>
|
||||
</ion-chip>
|
||||
</p>
|
||||
|
||||
<h3>Outline</h3>
|
||||
<p>
|
||||
<ion-chip outline="true">
|
||||
<ion-label>Default</ion-label>
|
||||
</ion-chip>
|
||||
|
||||
<ion-chip class="ion-focused" outline="true">
|
||||
<ion-label>Focus</ion-label>
|
||||
</ion-chip>
|
||||
</p>
|
||||
|
||||
<h3>With Icon</h3>
|
||||
<p>
|
||||
<ion-chip>
|
||||
<ion-icon icon="calendar"></ion-icon>
|
||||
<ion-label>Icon</ion-label>
|
||||
</ion-chip>
|
||||
<ion-chip>
|
||||
<ion-label>Icon</ion-label>
|
||||
<ion-icon icon="calendar"></ion-icon>
|
||||
</ion-chip>
|
||||
</p>
|
||||
</ion-content>
|
||||
</ion-app>
|
||||
|
||||
<style>
|
||||
h2,
|
||||
h3 {
|
||||
padding-left: 16px;
|
||||
}
|
||||
|
||||
p {
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
ion-chip + ion-chip {
|
||||
margin-inline-start: 16px;
|
||||
}
|
||||
</style>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,152 @@
|
||||
import type { ComponentInterface } from '@stencil/core';
|
||||
import { Component, Element, Host, Prop, h, Watch } from '@stencil/core';
|
||||
import { printIonWarning } from '@utils/logging';
|
||||
import { createColorClasses } from '@utils/theme';
|
||||
import { eyeOff, eye } from 'ionicons/icons';
|
||||
|
||||
import { getIonMode } from '../../global/ionic-global';
|
||||
import type { Color, TextFieldTypes } from '../../interface';
|
||||
|
||||
/**
|
||||
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
|
||||
*/
|
||||
@Component({
|
||||
tag: 'ion-input-password-toggle',
|
||||
/**
|
||||
* Empty CSS files are required in order for the mode to be inherited to the
|
||||
* inner ion-button. Otherwise, the setMode callback provided to Stencil will not get called
|
||||
* and we will default to MD mode.
|
||||
*/
|
||||
styleUrls: {
|
||||
ios: 'input-password-toggle.scss',
|
||||
md: 'input-password-toggle.scss',
|
||||
},
|
||||
shadow: true,
|
||||
})
|
||||
export class InputPasswordToggle implements ComponentInterface {
|
||||
private inputElRef!: HTMLIonInputElement | null;
|
||||
|
||||
@Element() el!: HTMLIonInputElement;
|
||||
|
||||
/**
|
||||
* The color to use from your application's color palette.
|
||||
* Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`.
|
||||
* For more information on colors, see [theming](/docs/theming/basics).
|
||||
*/
|
||||
@Prop({ reflect: true }) color?: Color;
|
||||
|
||||
/**
|
||||
* The icon that can be used to represent showing a password. If not set, the "eye" Ionicon will be used.
|
||||
*/
|
||||
@Prop() showIcon?: string;
|
||||
|
||||
/**
|
||||
* The icon that can be used to represent hiding a password. If not set, the "eyeOff" Ionicon will be used.
|
||||
*/
|
||||
@Prop() hideIcon?: string;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
@Prop({ mutable: true }) type: TextFieldTypes = 'password';
|
||||
|
||||
/**
|
||||
* Whenever the input type changes we need to re-run validation to ensure the password
|
||||
* toggle is being used with the correct input type. If the application changes the type
|
||||
* outside of this component we also need to re-render so the correct icon is shown.
|
||||
*/
|
||||
@Watch('type')
|
||||
onTypeChange(newValue: TextFieldTypes) {
|
||||
if (newValue !== 'text' && newValue !== 'password') {
|
||||
printIonWarning(
|
||||
`ion-input-password-toggle only supports inputs of type "text" or "password". Input of type "${newValue}" is not compatible.`,
|
||||
this.el
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
const { el } = this;
|
||||
|
||||
const inputElRef = (this.inputElRef = el.closest('ion-input'));
|
||||
|
||||
if (!inputElRef) {
|
||||
printIonWarning(
|
||||
'No ancestor ion-input found for ion-input-password-toggle. This component must be slotted inside of an ion-input.',
|
||||
el
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Important: Set the type in connectedCallback because the default value
|
||||
* of this.type may not always be accurate. Usually inputs have the "password" type
|
||||
* but it is possible to have the input to initially have the "text" type. In that scenario
|
||||
* the wrong icon will show briefly before switching to the correct icon. Setting the
|
||||
* type here allows us to avoid that flicker.
|
||||
*/
|
||||
this.type = inputElRef.type;
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
this.inputElRef = null;
|
||||
}
|
||||
|
||||
private togglePasswordVisibility = () => {
|
||||
const { inputElRef } = this;
|
||||
|
||||
if (!inputElRef) {
|
||||
return;
|
||||
}
|
||||
|
||||
inputElRef.type = inputElRef.type === 'text' ? 'password' : 'text';
|
||||
};
|
||||
|
||||
render() {
|
||||
const { color, type } = this;
|
||||
|
||||
const mode = getIonMode(this);
|
||||
|
||||
const showPasswordIcon = this.showIcon ?? eye;
|
||||
const hidePasswordIcon = this.hideIcon ?? eyeOff;
|
||||
|
||||
const isPasswordVisible = type === 'text';
|
||||
|
||||
return (
|
||||
<Host
|
||||
class={createColorClasses(color, {
|
||||
[mode]: true,
|
||||
})}
|
||||
>
|
||||
<ion-button
|
||||
mode={mode}
|
||||
color={color}
|
||||
fill="clear"
|
||||
shape="round"
|
||||
aria-checked={isPasswordVisible ? 'true' : 'false'}
|
||||
aria-label="show password"
|
||||
role="switch"
|
||||
type="button"
|
||||
onPointerDown={(ev) => {
|
||||
/**
|
||||
* This prevents mobile browsers from
|
||||
* blurring the input when the password toggle
|
||||
* button is activated.
|
||||
*/
|
||||
ev.preventDefault();
|
||||
}}
|
||||
onClick={this.togglePasswordVisibility}
|
||||
>
|
||||
<ion-icon
|
||||
slot="icon-only"
|
||||
aria-hidden="true"
|
||||
icon={isPasswordVisible ? hidePasswordIcon : showPasswordIcon}
|
||||
></ion-icon>
|
||||
</ion-button>
|
||||
</Host>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import AxeBuilder from '@axe-core/playwright';
|
||||
import { expect } from '@playwright/test';
|
||||
import { configs, test } from '@utils/test/playwright';
|
||||
|
||||
configs({ directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
test.describe(title('input password toggle: a11y'), () => {
|
||||
test('should not have accessibility violations', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<main>
|
||||
<ion-input label="input" type="password">
|
||||
<ion-input-password-toggle slot="end"></ion-input-password-toggle>
|
||||
</ion-input>
|
||||
</main>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const results = await new AxeBuilder({ page }).analyze();
|
||||
expect(results.violations).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,76 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Input - Toggle Password</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>
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, minmax(250px, 1fr));
|
||||
grid-row-gap: 20px;
|
||||
grid-column-gap: 20px;
|
||||
}
|
||||
h2 {
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
|
||||
color: #6f7378;
|
||||
|
||||
margin-top: 10px;
|
||||
}
|
||||
@media screen and (max-width: 800px) {
|
||||
.grid {
|
||||
grid-template-columns: 1fr;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<ion-app>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Input - Basic</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content id="content" class="ion-padding">
|
||||
<div class="grid">
|
||||
<div class="grid-item">
|
||||
<h2>Default</h2>
|
||||
<ion-input type="password" value="supersecurepassword" label="Password">
|
||||
<ion-input-password-toggle slot="end"></ion-input-password-toggle>
|
||||
</ion-input>
|
||||
</div>
|
||||
<div class="grid-item">
|
||||
<h2>Custom Icon</h2>
|
||||
<ion-input type="password" value="supersecurepassword" label="Password">
|
||||
<ion-input-password-toggle show-icon="trash" slot="end"></ion-input-password-toggle>
|
||||
</ion-input>
|
||||
</div>
|
||||
<div class="grid-item">
|
||||
<h2>Custom Mode/Color</h2>
|
||||
<ion-input type="password" value="supersecurepassword" label="Password">
|
||||
<ion-input-password-toggle
|
||||
color="danger"
|
||||
mode="ios"
|
||||
show-icon="trash"
|
||||
slot="end"
|
||||
></ion-input-password-toggle>
|
||||
</ion-input>
|
||||
</div>
|
||||
</div>
|
||||
</ion-content>
|
||||
</ion-app>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,50 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { configs, test } from '@utils/test/playwright';
|
||||
|
||||
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
|
||||
test.describe(title('input password toggle: states'), () => {
|
||||
test('should be hidden when inside of a readonly input', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-input label="input" type="password" readonly="true">
|
||||
<ion-input-password-toggle slot="end"></ion-input-password-toggle>
|
||||
</ion-input>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const inputPasswordToggle = page.locator('ion-input-password-toggle');
|
||||
await expect(inputPasswordToggle).toBeHidden();
|
||||
});
|
||||
test('should be hidden when inside of a disabled input', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-input label="input" type="password" disabled="true">
|
||||
<ion-input-password-toggle slot="end"></ion-input-password-toggle>
|
||||
</ion-input>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const inputPasswordToggle = page.locator('ion-input-password-toggle');
|
||||
await expect(inputPasswordToggle).toBeHidden();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe(title('input password toggle: rendering'), () => {
|
||||
test('should not have visual regressions', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-input label="input" type="password">
|
||||
<ion-input-password-toggle slot="end"></ion-input-password-toggle>
|
||||
</ion-input>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const inputPasswordToggle = page.locator('ion-input-password-toggle');
|
||||
|
||||
await expect(inputPasswordToggle).toHaveScreenshot(screenshot('input-password-toggle'));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
After Width: | Height: | Size: 719 B |
|
After Width: | Height: | Size: 748 B |
|
After Width: | Height: | Size: 624 B |
@@ -0,0 +1,115 @@
|
||||
import { h } from '@stencil/core';
|
||||
import { newSpecPage } from '@stencil/core/testing';
|
||||
|
||||
import { Input } from '../../input/input';
|
||||
import { InputPasswordToggle } from '../input-password-toggle';
|
||||
import { Button } from '../../button/button';
|
||||
import { initialize } from '../../../global/ionic-global';
|
||||
|
||||
describe('input password toggle', () => {
|
||||
it('should toggle input type when clicked', async () => {
|
||||
const page = await newSpecPage({
|
||||
components: [Input, InputPasswordToggle, Button],
|
||||
template: () => (
|
||||
<ion-input type="password">
|
||||
<ion-input-password-toggle slot="end"></ion-input-password-toggle>
|
||||
</ion-input>
|
||||
),
|
||||
});
|
||||
|
||||
const inputPasswordToggle = page.body.querySelector('ion-input-password-toggle')!;
|
||||
const button = inputPasswordToggle.shadowRoot!.querySelector('ion-button')!;
|
||||
const input = page.body.querySelector('ion-input')!;
|
||||
|
||||
expect(input.type).toBe('password');
|
||||
|
||||
button.click();
|
||||
await page.waitForChanges();
|
||||
|
||||
expect(input.type).toBe('text');
|
||||
|
||||
button.click();
|
||||
await page.waitForChanges();
|
||||
|
||||
expect(input.type).toBe('password');
|
||||
});
|
||||
|
||||
it('should render custom icons', async () => {
|
||||
const page = await newSpecPage({
|
||||
components: [Input, InputPasswordToggle, Button],
|
||||
template: () => (
|
||||
<ion-input type="password">
|
||||
<ion-input-password-toggle showIcon="show" hideIcon="hide" slot="end"></ion-input-password-toggle>
|
||||
</ion-input>
|
||||
),
|
||||
});
|
||||
|
||||
const inputPasswordToggle = page.body.querySelector('ion-input-password-toggle')!;
|
||||
const button = inputPasswordToggle.shadowRoot!.querySelector('ion-button')!;
|
||||
const icon = inputPasswordToggle.shadowRoot!.querySelector('ion-icon')!;
|
||||
|
||||
// Grab the attribute to test since we are not actually passing in a valid SVG
|
||||
expect(icon.getAttribute('icon')).toBe('show');
|
||||
|
||||
button.click();
|
||||
await page.waitForChanges();
|
||||
|
||||
expect(icon.getAttribute('icon')).toBe('hide');
|
||||
});
|
||||
|
||||
it('changing the type on the input should update the icon used in password toggle', async () => {
|
||||
const page = await newSpecPage({
|
||||
components: [Input, InputPasswordToggle, Button],
|
||||
template: () => (
|
||||
<ion-input type="password">
|
||||
<ion-input-password-toggle showIcon="show" hideIcon="hide" slot="end"></ion-input-password-toggle>
|
||||
</ion-input>
|
||||
),
|
||||
});
|
||||
|
||||
const inputPasswordToggle = page.body.querySelector('ion-input-password-toggle')!;
|
||||
const input = page.body.querySelector('ion-input')!;
|
||||
const icon = inputPasswordToggle.shadowRoot!.querySelector('ion-icon')!;
|
||||
|
||||
// Grab the attribute to test since we are not actually passing in a valid SVG
|
||||
expect(icon.getAttribute('icon')).toBe('show');
|
||||
|
||||
input.type = 'text';
|
||||
await page.waitForChanges();
|
||||
|
||||
expect(icon.getAttribute('icon')).toBe('hide');
|
||||
|
||||
input.type = 'password';
|
||||
await page.waitForChanges();
|
||||
|
||||
expect(icon.getAttribute('icon')).toBe('show');
|
||||
});
|
||||
|
||||
it('should inherit the mode and color to internal ionic components', async () => {
|
||||
/**
|
||||
* This initialize script tells Stencil how to determine the mode on components.
|
||||
* This is required for any getIonMode internal logic to function properly in spec tests.
|
||||
*/
|
||||
initialize();
|
||||
|
||||
const page = await newSpecPage({
|
||||
components: [Input, InputPasswordToggle, Button],
|
||||
template: () => (
|
||||
<ion-input type="password" color="primary">
|
||||
<ion-input-password-toggle slot="end" mode="ios" color="danger"></ion-input-password-toggle>
|
||||
</ion-input>
|
||||
),
|
||||
});
|
||||
|
||||
const inputPasswordToggle = page.body.querySelector('ion-input-password-toggle')!;
|
||||
const button = inputPasswordToggle.shadowRoot!.querySelector('ion-button')!;
|
||||
|
||||
await page.waitForChanges();
|
||||
|
||||
// mode is a virtual prop so we need to access it as an attribute
|
||||
expect(button.getAttribute('mode')).toBe('ios');
|
||||
|
||||
// color is an actual prop so we can access the element property
|
||||
expect(button.color).toBe('danger');
|
||||
});
|
||||
});
|
||||
@@ -605,3 +605,12 @@
|
||||
margin-inline-start: $form-control-label-margin;
|
||||
margin-inline-end: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* The input password toggle component should be hidden when the input is readonly/disabled
|
||||
* because it is not possible to edit a password.
|
||||
*/
|
||||
:host([disabled]) ::slotted(ion-input-password-toggle),
|
||||
:host([readonly]) ::slotted(ion-input-password-toggle) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ export class Input implements ComponentInterface {
|
||||
/**
|
||||
* If `true`, the user cannot interact with the input.
|
||||
*/
|
||||
@Prop() disabled = false;
|
||||
@Prop({ reflect: true }) disabled = false;
|
||||
|
||||
/**
|
||||
* A hint to the browser for which enter key to display.
|
||||
@@ -228,7 +228,7 @@ export class Input implements ComponentInterface {
|
||||
/**
|
||||
* If `true`, the user cannot modify the value.
|
||||
*/
|
||||
@Prop() readonly = false;
|
||||
@Prop({ reflect: true }) readonly = false;
|
||||
|
||||
/**
|
||||
* If `true`, the user must fill in a value before submitting a form.
|
||||
@@ -256,6 +256,20 @@ export class Input implements ComponentInterface {
|
||||
*/
|
||||
@Prop() type: TextFieldTypes = 'text';
|
||||
|
||||
/**
|
||||
* Whenever the type on the input changes we need
|
||||
* to update the internal type prop on the password
|
||||
* toggle so that that correct icon is shown.
|
||||
*/
|
||||
@Watch('type')
|
||||
onTypeChange() {
|
||||
const passwordToggle = this.el.querySelector('ion-input-password-toggle');
|
||||
|
||||
if (passwordToggle) {
|
||||
passwordToggle.type = this.type;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The value of the input.
|
||||
*/
|
||||
@@ -346,6 +360,14 @@ export class Input implements ComponentInterface {
|
||||
|
||||
componentDidLoad() {
|
||||
this.originalIonInput = this.ionInput;
|
||||
|
||||
/**
|
||||
* Set the type on the password toggle in the event that this input's
|
||||
* type was set async and does not match the default type for the password toggle.
|
||||
* This can happen when the type is bound using a JS framework binding syntax
|
||||
* such as [type] in Angular.
|
||||
*/
|
||||
this.onTypeChange();
|
||||
}
|
||||
|
||||
componentDidRender() {
|
||||
|
||||
@@ -80,26 +80,11 @@ export class Searchbar implements ComponentInterface {
|
||||
*/
|
||||
@Prop() animated = false;
|
||||
|
||||
/**
|
||||
* Prior to the addition of this property
|
||||
* autocapitalize was enabled by default on iOS
|
||||
* and disabled by default on Android
|
||||
* for Searchbar. The autocapitalize type on HTMLElement
|
||||
* requires that it be a string and never undefined.
|
||||
* However, setting it to a string value would be a breaking change
|
||||
* in behavior, so we use "!" to tell TypeScript that this property
|
||||
* is always defined so we can rely on the browser defaults. Browsers
|
||||
* will automatically set a default value if the developer does not set one.
|
||||
*
|
||||
* In the future, this property will default to "off" to align with
|
||||
* Input and Textarea, and the "!" will not be needed.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Indicates whether and how the text value should be automatically capitalized as it is entered/edited by the user.
|
||||
* Available options: `"off"`, `"none"`, `"on"`, `"sentences"`, `"words"`, `"characters"`.
|
||||
*/
|
||||
@Prop() autocapitalize!: string;
|
||||
@Prop() autocapitalize: string = 'off';
|
||||
|
||||
/**
|
||||
* Set the input's autocomplete property.
|
||||
|
||||
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
107
core/src/css/test/typography/basic/typography.e2e.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { configs, test } from '@utils/test/playwright';
|
||||
|
||||
/**
|
||||
* Typography styles are only available in the Ionic theme
|
||||
*/
|
||||
configs({ modes: ['ionic-md'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
|
||||
test.describe(title('typography: global styles'), () => {
|
||||
test('should apply the globals styles for each semantic tag', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<link href="/css/typography.ionic.css" rel="stylesheet" />
|
||||
<div>
|
||||
<h1>H1: The quick brown fox jumps over the lazy dog</h1>
|
||||
<h2>H2: The quick brown fox jumps over the lazy dog</h2>
|
||||
<h3>H3: The quick brown fox jumps over the lazy dog</h3>
|
||||
<h4>H4: The quick brown fox jumps over the lazy dog</h4>
|
||||
<h5>H5: The quick brown fox jumps over the lazy dog</h5>
|
||||
<h6>H6: The quick brown fox jumps over the lazy dog</h6>
|
||||
<p><ion-text class="ionic-font-size-l"><p>Tag_P FontLarge: The quick brown fox jumps over the lazy dog</p></ion-text><p>
|
||||
<p><ion-text class="ionic-font-size-m">Tag_None FontMedium: The quick brown fox jumps over the lazydog</ion-text></p>
|
||||
<p><ion-text class="ionic-font-size-s"><span>Tag_Span FontSmall: The quick brown fox jumps over the lazy dog</span></ion-text></p>
|
||||
</div>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const div = page.locator('div');
|
||||
await expect(div).toHaveScreenshot(screenshot(`ionic-semantic-global-styles`));
|
||||
});
|
||||
|
||||
test('should apply the utility classes for each heading', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<link href="/css/typography.ionic.css" rel="stylesheet" />
|
||||
<div>
|
||||
<h1 class="ionic-heading2 ionic-font-weight-light">Tag H1 - Style Heading 2 - Font Light</h1>
|
||||
<h2 class="ionic-heading3 ionic-font-weight-light">Tag H2 - Style Heading 3 - Font Light</h2>
|
||||
<h3 class="ionic-heading4">Tag H3 - Style Heading 4 - Font Regular</h3>
|
||||
<h4 class="ionic-heading5 ionic-font-weight-medium">Tag H4 - Style Heading 5 - Font Medium</h4>
|
||||
<h5 class="ionic-heading6">Tag H5 - Style Heading 6 - Font Semibold</h5>
|
||||
<h6 class="ionic-heading1 ionic-font-weight-bold">Tag H6 - Style Heading 1 - Font Bold</h6>
|
||||
</div>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const div = page.locator('div');
|
||||
await expect(div).toHaveScreenshot(screenshot(`ionic-utility-classes-semantic-tags`));
|
||||
});
|
||||
|
||||
test('should apply the heading classes on ion-text element', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<link href="/css/typography.ionic.css" rel="stylesheet" />
|
||||
<div>
|
||||
<p><ion-text class="ionic-heading1">Utility class 'ionic-heading1'</ion-text></p>
|
||||
<p><ion-text class="ionic-heading2">Utility class 'ionic-heading2'</ion-text></p>
|
||||
<p><ion-text class="ionic-heading3">Utility class 'ionic-heading3'</ion-text></p>
|
||||
<p><ion-text class="ionic-heading4">Utility class 'ionic-heading4'</ion-text></p>
|
||||
<p><ion-text class="ionic-heading5">Utility class 'ionic-heading5'</ion-text></p>
|
||||
<p><ion-text class="ionic-heading6">Utility class 'ionic-heading6'</ion-text></p>
|
||||
</div>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const div = page.locator('div');
|
||||
await expect(div).toHaveScreenshot(screenshot(`ionic-utility-classes-headings`));
|
||||
});
|
||||
|
||||
test('should apply the utility classes for font weights', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<link href="/css/typography.ionic.css" rel="stylesheet" />
|
||||
<div>
|
||||
<p><ion-text class="ionic-font-weight-light">Utility class 'ionic-font-light'</ion-text></p>
|
||||
<p><ion-text class="ionic-font-weight-regular">Utility class 'ionic-font-regular'</ion-text></p>
|
||||
<p><ion-text class="ionic-font-weight-medium">Utility class 'ionic-font-medium'</ion-text></p>
|
||||
<p><ion-text class="ionic-font-weight-semi-bold">Utility class 'ionic-font-semibold'</ion-text></p>
|
||||
<p><ion-text class="ionic-font-weight-bold">Utility class 'ionic-font-bold'</ion-text></p>
|
||||
</div>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const div = page.locator('div');
|
||||
await expect(div).toHaveScreenshot(screenshot(`ionic-utility-classes-font-weights`));
|
||||
});
|
||||
|
||||
test('should apply the utility classes for font display', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<link href="/css/typography.ionic.css" rel="stylesheet" />
|
||||
<div>
|
||||
<p><ion-text class="ionic-display-s">Utility class 'ionic-display-s'</ion-text></p>
|
||||
<p><ion-text class="ionic-display-m">Utility class 'ionic-display-m'</ion-text></p>
|
||||
</div>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const div = page.locator('div');
|
||||
await expect(div).toHaveScreenshot(screenshot(`ionic-utility-classes-font-displays`));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 30 KiB |