mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-11-06 22:29:44 +08:00
fix(fab-button): position is correct with custom sizes (#28195)
Issue number: resolves #22564 --------- <!-- 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 current behavior? <!-- Please describe the current behavior that you are modifying. --> Changing the size of the FAB button causes it to be positioned incorrectly. This was happening because we set position values based on the assumption that the default FAB button would always be 56px x 56px. ## What is the new behavior? <!-- Please describe the behavior or changes that are being added by this PR. --> - FAB and FAB List positioning is now computed based on intrinsic size ## Does this introduce a breaking change? - [ ] Yes - [x] No <!-- If this introduces a breaking change, please describe the impact and migration path for existing applications below. --> ## Other information <!-- Any other information that is important to this PR such as screenshots of how the component looks before and after the change. --> Dev build: `7.4.1-dev.11695395641.14897417` --------- Co-authored-by: ionitron <hi@ionicframework.com>
This commit is contained in:
@ -199,7 +199,7 @@
|
||||
// --------------------------------------------------
|
||||
|
||||
:host(.fab-button-small) {
|
||||
@include margin(($fab-size - $fab-small-size) * 0.5);
|
||||
@include margin($fab-button-small-margin);
|
||||
|
||||
width: #{$fab-small-size};
|
||||
height: #{$fab-small-size};
|
||||
|
||||
@ -11,3 +11,6 @@ $fab-small-size: 40px !default;
|
||||
|
||||
/// @prop - Border radius of the FAB button
|
||||
$fab-border-radius: 50% !default;
|
||||
|
||||
/// @prop - Margin applied to the small FAB button
|
||||
$fab-button-small-margin: 8px !default;
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
// --------------------------------------------------
|
||||
|
||||
:host {
|
||||
@include margin($fab-size + $fab-list-margin, 0);
|
||||
@include margin(calc(100% + #{$fab-list-margin}), 0);
|
||||
|
||||
display: none;
|
||||
position: absolute;
|
||||
@ -13,8 +13,15 @@
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
min-width: $fab-size;
|
||||
min-height: $fab-size;
|
||||
/**
|
||||
* The list should be centered relative to the parent
|
||||
* FAB button. We set minimum dimensions so the
|
||||
* FAB list can be centered relative to the small FAB button.
|
||||
* However, the small FAB button adds start/end margin, so we need
|
||||
* to account for that in the FAB list dimensions.
|
||||
*/
|
||||
min-width: $fab-small-size + ($fab-button-small-margin * 2);
|
||||
min-height: $fab-small-size + ($fab-button-small-margin * 2);
|
||||
}
|
||||
|
||||
:host(.fab-list-active) {
|
||||
@ -59,14 +66,14 @@
|
||||
}
|
||||
|
||||
:host(.fab-list-side-start) {
|
||||
@include margin(0, $fab-size + $fab-list-margin);
|
||||
@include margin(0, calc(100% + #{$fab-list-margin}));
|
||||
@include position-horizontal(null, 0);
|
||||
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
:host(.fab-list-side-end) {
|
||||
@include margin(0, $fab-size + $fab-list-margin);
|
||||
@include margin(0, calc(100% + #{$fab-list-margin}));
|
||||
@include position(null, null, null, 0);
|
||||
|
||||
flex-direction: row;
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
@import "./fab.vars";
|
||||
@import "../fab-list/fab-list.vars";
|
||||
|
||||
// Floating Action Button Container
|
||||
// --------------------------------------------------
|
||||
@ -6,6 +7,9 @@
|
||||
:host {
|
||||
position: absolute;
|
||||
|
||||
width: fit-content;
|
||||
height: fit-content;
|
||||
|
||||
z-index: $z-index-fixed-content;
|
||||
}
|
||||
|
||||
@ -14,8 +18,8 @@
|
||||
// --------------------------------------------------
|
||||
|
||||
:host(.fab-horizontal-center) {
|
||||
@include position(null, null, null, 50%);
|
||||
@include margin-horizontal(-$fab-size * 0.5, null);
|
||||
@include position(null, 0px, null, 0px);
|
||||
@include margin(null, auto);
|
||||
}
|
||||
|
||||
:host(.fab-horizontal-start) {
|
||||
@ -38,22 +42,93 @@
|
||||
top: $fab-content-margin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the top value since edge
|
||||
* styles use margin-top.
|
||||
*/
|
||||
:host(.fab-vertical-top.fab-edge) {
|
||||
top: -$fab-size * 0.5;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* We need to adjust the parent FAB button up by half
|
||||
* its height so that half of it sits on the header. As a result,
|
||||
* we target the slotted ion-fab-button instead of targeting the host.
|
||||
*/
|
||||
:host(.fab-vertical-top.fab-edge) ::slotted(ion-fab-button) {
|
||||
margin-top: -50%;
|
||||
}
|
||||
|
||||
/**
|
||||
* The small FAB button adds top and bottom margin. We need to account for
|
||||
* that margin when we adjust the FAB button for edge styles since we
|
||||
* are overriding the margin-top value below.
|
||||
*/
|
||||
:host(.fab-vertical-top.fab-edge) ::slotted(ion-fab-button.fab-button-small) {
|
||||
margin-top: calc((-100% + $fab-button-small-margin * 2) / 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Since we are adjusting the FAB button we also need
|
||||
* to adjust the sibling ion-fab-list otherwise there will be
|
||||
* a gap between the parent FAB button and the associated list.
|
||||
*/
|
||||
:host(.fab-vertical-top.fab-edge) ::slotted(ion-fab-list.fab-list-side-start),
|
||||
:host(.fab-vertical-top.fab-edge) ::slotted(ion-fab-list.fab-list-side-end) {
|
||||
@include margin(-50%, null, null, null);
|
||||
}
|
||||
|
||||
:host(.fab-vertical-top.fab-edge) ::slotted(ion-fab-list.fab-list-side-top),
|
||||
:host(.fab-vertical-top.fab-edge) ::slotted(ion-fab-list.fab-list-side-bottom) {
|
||||
@include margin(calc(50% + #{$fab-list-margin}) null, null, null);
|
||||
}
|
||||
|
||||
:host(.fab-vertical-bottom) {
|
||||
bottom: $fab-content-margin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the bottom value since edge
|
||||
* styles use margin-bottom.
|
||||
*/
|
||||
:host(.fab-vertical-bottom.fab-edge) {
|
||||
bottom: -$fab-size * 0.5;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* We need to adjust the parent FAB button down by half
|
||||
* its height so that half of it sits on the footer. As a result,
|
||||
* we target the slotted ion-fab-button instead of targeting the host.
|
||||
*/
|
||||
:host(.fab-vertical-bottom.fab-edge) ::slotted(ion-fab-button) {
|
||||
margin-bottom: -50%;
|
||||
}
|
||||
|
||||
/**
|
||||
* The small FAB button adds top and bottom margin. We need to account for
|
||||
* that margin when we adjust the FAB button for edge styles since we
|
||||
* are overriding the margin-bottom value below.
|
||||
*/
|
||||
:host(.fab-vertical-bottom.fab-edge) ::slotted(ion-fab-button.fab-button-small) {
|
||||
margin-bottom: calc((-100% + $fab-button-small-margin * 2) / 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Since we are adjusting the FAB button we also need
|
||||
* to adjust the sibling ion-fab-list otherwise there will be
|
||||
* a gap between the parent FAB button and the associated list.
|
||||
*/
|
||||
:host(.fab-vertical-bottom.fab-edge) ::slotted(ion-fab-list.fab-list-side-start),
|
||||
:host(.fab-vertical-bottom.fab-edge) ::slotted(ion-fab-list.fab-list-side-end) {
|
||||
@include margin(null, null, -50%, null);
|
||||
}
|
||||
|
||||
:host(.fab-vertical-bottom.fab-edge) ::slotted(ion-fab-list.fab-list-side-top),
|
||||
:host(.fab-vertical-bottom.fab-edge) ::slotted(ion-fab-list.fab-list-side-bottom) {
|
||||
@include margin(null, null, calc(50% + #{$fab-list-margin}) null);
|
||||
}
|
||||
|
||||
:host(.fab-vertical-center) {
|
||||
@include margin(-$fab-size * 0.5, null, null, null);
|
||||
|
||||
top: 50%;
|
||||
@include position(0px, null, 0px, null);
|
||||
@include margin(auto, null);
|
||||
}
|
||||
|
||||
17
core/src/components/fab/test/custom-size/fab.e2e.ts
Normal file
17
core/src/components/fab/test/custom-size/fab.e2e.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { configs, test, Viewports } from '@utils/test/playwright';
|
||||
|
||||
/**
|
||||
* This behavior does not vary across modes
|
||||
*/
|
||||
configs({ modes: ['ios'] }).forEach(({ title, config, screenshot }) => {
|
||||
test.describe(title('fab: custom size'), () => {
|
||||
test('should position fabs correctly with custom sizes', async ({ page }) => {
|
||||
await page.goto(`/src/components/fab/test/custom-size`, config);
|
||||
|
||||
await page.setViewportSize(Viewports.tablet.landscape);
|
||||
|
||||
await expect(page).toHaveScreenshot(screenshot(`fab-custom-size`));
|
||||
});
|
||||
});
|
||||
});
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 75 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 116 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 68 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 75 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 116 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 68 KiB |
241
core/src/components/fab/test/custom-size/index.html
Normal file
241
core/src/components/fab/test/custom-size/index.html
Normal file
@ -0,0 +1,241 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Floating Action Button - Custom Size</title>
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"
|
||||
/>
|
||||
<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>
|
||||
.ruler-y {
|
||||
height: 100%;
|
||||
width: 1px;
|
||||
|
||||
background: red;
|
||||
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
margin: 0 auto;
|
||||
|
||||
z-index: 10000;
|
||||
}
|
||||
|
||||
.ruler-x {
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
|
||||
background: red;
|
||||
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
margin: 0 auto;
|
||||
|
||||
z-index: 10000;
|
||||
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
ion-fab-button {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<ion-app>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Floating Action Button - Custom Size</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="ion-padding" id="content">
|
||||
<div class="ruler-y"></div>
|
||||
<div class="ruler-x"></div>
|
||||
|
||||
<ion-fab vertical="top" horizontal="start" slot="fixed" edge="true">
|
||||
<ion-fab-button activated="true">
|
||||
<ion-icon name="add"></ion-icon>
|
||||
</ion-fab-button>
|
||||
<ion-fab-list side="bottom" activated="true">
|
||||
<ion-fab-button show="true">
|
||||
<ion-icon name="logo-facebook"></ion-icon>
|
||||
</ion-fab-button>
|
||||
<ion-fab-button show="true">
|
||||
<ion-icon name="logo-vimeo"></ion-icon>
|
||||
</ion-fab-button>
|
||||
<ion-fab-button show="true">
|
||||
<ion-icon name="logo-instagram"></ion-icon>
|
||||
</ion-fab-button>
|
||||
</ion-fab-list>
|
||||
<ion-fab-list side="end" activated="true">
|
||||
<ion-fab-button show="true">
|
||||
<ion-icon name="logo-facebook"></ion-icon>
|
||||
</ion-fab-button>
|
||||
<ion-fab-button show="true">
|
||||
<ion-icon name="logo-vimeo"></ion-icon>
|
||||
</ion-fab-button>
|
||||
<ion-fab-button show="true">
|
||||
<ion-icon name="logo-instagram"></ion-icon>
|
||||
</ion-fab-button>
|
||||
</ion-fab-list>
|
||||
</ion-fab>
|
||||
|
||||
<ion-fab vertical="top" horizontal="end" slot="fixed" edge="true">
|
||||
<ion-fab-button activated="true">
|
||||
<ion-icon name="add"></ion-icon>
|
||||
</ion-fab-button>
|
||||
<ion-fab-list side="start" activated="true">
|
||||
<ion-fab-button show="true">
|
||||
<ion-icon name="logo-facebook"></ion-icon>
|
||||
</ion-fab-button>
|
||||
<ion-fab-button show="true">
|
||||
<ion-icon name="logo-vimeo"></ion-icon>
|
||||
</ion-fab-button>
|
||||
<ion-fab-button show="true">
|
||||
<ion-icon name="logo-instagram"></ion-icon>
|
||||
</ion-fab-button>
|
||||
</ion-fab-list>
|
||||
<ion-fab-list side="bottom" activated="true">
|
||||
<ion-fab-button show="true">
|
||||
<ion-icon name="logo-facebook"></ion-icon>
|
||||
</ion-fab-button>
|
||||
<ion-fab-button show="true">
|
||||
<ion-icon name="logo-vimeo"></ion-icon>
|
||||
</ion-fab-button>
|
||||
<ion-fab-button show="true">
|
||||
<ion-icon name="logo-instagram"></ion-icon>
|
||||
</ion-fab-button>
|
||||
</ion-fab-list>
|
||||
</ion-fab>
|
||||
|
||||
<ion-fab vertical="bottom" horizontal="start" slot="fixed" edge="true">
|
||||
<ion-fab-button activated="true">
|
||||
<ion-icon name="add"></ion-icon>
|
||||
</ion-fab-button>
|
||||
<ion-fab-list side="top" activated="true">
|
||||
<ion-fab-button show="true">
|
||||
<ion-icon name="logo-facebook"></ion-icon>
|
||||
</ion-fab-button>
|
||||
<ion-fab-button show="true">
|
||||
<ion-icon name="logo-vimeo"></ion-icon>
|
||||
</ion-fab-button>
|
||||
<ion-fab-button show="true">
|
||||
<ion-icon name="logo-instagram"></ion-icon>
|
||||
</ion-fab-button>
|
||||
</ion-fab-list>
|
||||
<ion-fab-list side="end" activated="true">
|
||||
<ion-fab-button show="true">
|
||||
<ion-icon name="logo-facebook"></ion-icon>
|
||||
</ion-fab-button>
|
||||
<ion-fab-button show="true">
|
||||
<ion-icon name="logo-vimeo"></ion-icon>
|
||||
</ion-fab-button>
|
||||
<ion-fab-button show="true">
|
||||
<ion-icon name="logo-instagram"></ion-icon>
|
||||
</ion-fab-button>
|
||||
</ion-fab-list>
|
||||
</ion-fab>
|
||||
|
||||
<ion-fab vertical="bottom" horizontal="end" slot="fixed" edge="true">
|
||||
<ion-fab-button activated="true">
|
||||
<ion-icon name="add"></ion-icon>
|
||||
</ion-fab-button>
|
||||
<ion-fab-list side="start" activated="true">
|
||||
<ion-fab-button show="true">
|
||||
<ion-icon name="logo-facebook"></ion-icon>
|
||||
</ion-fab-button>
|
||||
<ion-fab-button show="true">
|
||||
<ion-icon name="logo-vimeo"></ion-icon>
|
||||
</ion-fab-button>
|
||||
<ion-fab-button show="true">
|
||||
<ion-icon name="logo-instagram"></ion-icon>
|
||||
</ion-fab-button>
|
||||
</ion-fab-list>
|
||||
<ion-fab-list side="top" activated="true">
|
||||
<ion-fab-button show="true">
|
||||
<ion-icon name="logo-facebook"></ion-icon>
|
||||
</ion-fab-button>
|
||||
<ion-fab-button show="true">
|
||||
<ion-icon name="logo-vimeo"></ion-icon>
|
||||
</ion-fab-button>
|
||||
<ion-fab-button show="true">
|
||||
<ion-icon name="logo-instagram"></ion-icon>
|
||||
</ion-fab-button>
|
||||
</ion-fab-list>
|
||||
</ion-fab>
|
||||
|
||||
<ion-fab vertical="center" horizontal="center" slot="fixed">
|
||||
<ion-fab-button activated="true">
|
||||
<ion-icon name="add"></ion-icon>
|
||||
</ion-fab-button>
|
||||
<ion-fab-list side="start" activated="true">
|
||||
<ion-fab-button show="true">
|
||||
<ion-icon name="logo-facebook"></ion-icon>
|
||||
</ion-fab-button>
|
||||
<ion-fab-button show="true">
|
||||
<ion-icon name="logo-vimeo"></ion-icon>
|
||||
</ion-fab-button>
|
||||
<ion-fab-button show="true">
|
||||
<ion-icon name="logo-instagram"></ion-icon>
|
||||
</ion-fab-button>
|
||||
</ion-fab-list>
|
||||
<ion-fab-list side="top" activated="true">
|
||||
<ion-fab-button show="true">
|
||||
<ion-icon name="logo-facebook"></ion-icon>
|
||||
</ion-fab-button>
|
||||
<ion-fab-button show="true">
|
||||
<ion-icon name="logo-vimeo"></ion-icon>
|
||||
</ion-fab-button>
|
||||
<ion-fab-button show="true">
|
||||
<ion-icon name="logo-instagram"></ion-icon>
|
||||
</ion-fab-button>
|
||||
</ion-fab-list>
|
||||
<ion-fab-list side="bottom" activated="true">
|
||||
<ion-fab-button show="true">
|
||||
<ion-icon name="logo-facebook"></ion-icon>
|
||||
</ion-fab-button>
|
||||
<ion-fab-button show="true">
|
||||
<ion-icon name="logo-vimeo"></ion-icon>
|
||||
</ion-fab-button>
|
||||
<ion-fab-button show="true">
|
||||
<ion-icon name="logo-instagram"></ion-icon>
|
||||
</ion-fab-button>
|
||||
</ion-fab-list>
|
||||
<ion-fab-list side="end" activated="true">
|
||||
<ion-fab-button show="true">
|
||||
<ion-icon name="logo-facebook"></ion-icon>
|
||||
</ion-fab-button>
|
||||
<ion-fab-button show="true">
|
||||
<ion-icon name="logo-vimeo"></ion-icon>
|
||||
</ion-fab-button>
|
||||
<ion-fab-button show="true">
|
||||
<ion-icon name="logo-instagram"></ion-icon>
|
||||
</ion-fab-button>
|
||||
</ion-fab-list>
|
||||
</ion-fab>
|
||||
</ion-content>
|
||||
|
||||
<ion-footer>
|
||||
<ion-toolbar>
|
||||
<ion-title>Floating Action Button - Basic</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-footer>
|
||||
</ion-app>
|
||||
</body>
|
||||
</html>
|
||||
@ -4,6 +4,10 @@ export const Viewports = {
|
||||
width: 768,
|
||||
height: 900,
|
||||
},
|
||||
landscape: {
|
||||
width: 900,
|
||||
height: 768,
|
||||
},
|
||||
},
|
||||
// Based off https://github.com/ionic-team/ionic-framework/blob/2fe23d9d46c3593843c781c57340332e5a86fd64/core/src/components/split-pane/split-pane.tsx#L9-L13
|
||||
large: {
|
||||
|
||||
Reference in New Issue
Block a user