Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow for custom ion-toast positioning #17499

Closed
jordanpurinton opened this issue Feb 14, 2019 · 18 comments
Closed

feat: allow for custom ion-toast positioning #17499

jordanpurinton opened this issue Feb 14, 2019 · 18 comments
Labels
package: core @ionic/core package type: feature request a new feature, enhancement, or improvement

Comments

@jordanpurinton
Copy link
Contributor

jordanpurinton commented Feb 14, 2019

Feature Request

Ionic version:
[x] 4.x

Describe the Feature Request
Currently with the implementation of toasts in Ionic v4, we have no way of adding some padding or margin to an ion-toast. I'd like to be able to customize this position in order to adhere to the material design principle outlined here.

Describe Preferred Solution
Ideally, this would be something that could be added in the CSS custom properties.

@ionitron-bot ionitron-bot bot added the triage label Feb 14, 2019
@liamdebeasi liamdebeasi added the type: feature request a new feature, enhancement, or improvement label Feb 14, 2019
@ionitron-bot ionitron-bot bot removed the triage label Feb 14, 2019
@joelmeaders
Copy link

joelmeaders commented Mar 15, 2019

I see there is style applied directly to the ion-toast>div (below), but there's no "easy" way to modify this. In my example I'd like to move it up by 70px so it doesn't cover my ion-tabs.

Style when using 'bottom' position.

element.style {
    bottom: calc(8px + var(--ion-safe-area-bottom, 0px));
    opacity: 1;
}

Changing 8px to something like var(--ion-toast-y-offset, 8px) would be amazing.

I'm currently using the following in global.scss to do this and it's probably not the best way.

#ion-overlay-2 {
	top: -70px;
}

@jbae1024
Copy link

jbae1024 commented Mar 18, 2019

I do not know.... I use it like this. Is there any other problem?

css

.md .tabs-bottom {
  transform: translateY(-56px) !important;
}
.ios .tabs-bottom {
  transform: translateY(-50px) !important;
}
.md .header-top {
  top: 56px !important;
}
.ios .header-top {
  top: 44px !important;
}

ts

const toast = await this.toastController.create({
            message: 'This is message',
            position: 'bottom',
            duration: 1000,
            color: 'warning',
            cssClass: 'tabs-bottom'
        });
        toast.present();

@joelmeaders
Copy link

I do not know.... I use it like this. Is there any other problem?

That is much better! The issue with mine is that subsequent toasts aren't covered since ion-overlay-# increments on each call.

Thank you.

@brandyscarney brandyscarney added the package: core @ionic/core package label Jun 5, 2019
@bene-we
Copy link

bene-we commented Jul 11, 2019

I use a FAB in bottom tabbar, since that I have to translate by at least -64px .

image
I also noticed there is a slight shadow above the toast, a built in solution would be very nice!

@liamdebeasi liamdebeasi changed the title Allow toast position to be customizable to adhere to material design feat: allow for custom ion-toast positioning Jun 29, 2020
@Yohandah
Copy link

Yohandah commented Aug 5, 2020

I am in need of this too!

I'd like to add that it would be nice to have a right and left option as well for the desktop mode, and maybe by default bind the toast to the ion-content so it doesn't overlap with the toolbar.

Snackbar in wide layout
https://material.io/components/snackbars#placement

@darkguy2008
Copy link

4 months more and we can give this bug a happy birthday!

I mean, seriously. How hard it is to have the tabs show where they should? I have a tabbed interface (using ion-tabs) and the toast appears above the tabs, giving BAD UX as the user cannot switch between tabs until the toast has dismissed.

We're in Ionic 6 already. Any solutions to this?

@josuecaceres
Copy link

No sé.... Lo uso así. ¿Hay algún otro problema?

Css

.md .tabs-bottom {
transform: translateY(-56px) !important;
}
.ios .tabs-bottom {
transform: translateY(-50px) !important;
}
.md .header-top {
top: 56px !important;
}
.ios .header-top {
top: 44px !important;
}
Ts

const toast = await this.toastController.create({
message: 'This is message',
position: 'bottom',
duration: 1000,
color: 'warning',
cssClass: 'tabs-bottom'
});
toast.present();

this is enough for me, thank you

@darkdread
Copy link

Any solution for Ionic 5.x? We can select the container shadow part, but there's no way to target the wrapper with just CSS since it's in a shadow dom...

@map-lo
Copy link

map-lo commented Aug 19, 2021

@darkdread I found a solution for Ionic 5.x. Provide a custom enter animation including an offset for the tabs bar.

// ...
export const iosEnterAnimation = (baseEl: ShadowRoot, position: string, offset = 10): Animation => {
    const baseAnimation = createAnimation();
    const wrapperAnimation = createAnimation();

    const hostEl = baseEl.host || baseEl;
    const wrapperEl = baseEl.querySelector('.toast-wrapper') as HTMLElement;

    const bottom = `calc(-${offset}px - var(--ion-safe-area-bottom, 0px))`;
    const top = `calc(${offset}px + var(--ion-safe-area-top, 0px))`;

    wrapperAnimation.addElement(wrapperEl);

    switch (position) {
        case 'top':
            wrapperAnimation.fromTo('transform', 'translateY(-100%)', `translateY(${top})`);
            break;
        case 'middle':
            const topPosition = Math.floor(
                (hostEl as Element).clientHeight / 2 - wrapperEl.clientHeight / 2
            );
            wrapperEl.style.top = `${topPosition}px`;
            wrapperAnimation.fromTo('opacity', 0.01, 1);
            break;
        default:
            wrapperAnimation.fromTo('transform', 'translateY(100%)', `translateY(${bottom})`);
            break;
    }
    return baseAnimation
        .addElement(hostEl)
        .easing('cubic-bezier(.155,1.105,.295,1.12)')
        .duration(400)
        .addAnimation(wrapperAnimation);
};
// opts used as toastController.create(opts)
if (this.tabsVisible) {
    opts.enterAnimation = (baseEl: ShadowRoot) => iosEnterAnimation(baseEl, opts.position, 60)
}
const toast = await toastController.create(opts);
// ...

@angusho1
Copy link

angusho1 commented Jan 13, 2022

@darkdread I found a solution for Ionic 5.x. Provide a custom enter animation including an offset for the tabs bar.

// ...
export const iosEnterAnimation = (baseEl: ShadowRoot, position: string, offset = 10): Animation => {
    const baseAnimation = createAnimation();
    const wrapperAnimation = createAnimation();

    const hostEl = baseEl.host || baseEl;
    const wrapperEl = baseEl.querySelector('.toast-wrapper') as HTMLElement;

    const bottom = `calc(-${offset}px - var(--ion-safe-area-bottom, 0px))`;
    const top = `calc(${offset}px + var(--ion-safe-area-top, 0px))`;

    wrapperAnimation.addElement(wrapperEl);

    switch (position) {
        case 'top':
            wrapperAnimation.fromTo('transform', 'translateY(-100%)', `translateY(${top})`);
            break;
        case 'middle':
            const topPosition = Math.floor(
                (hostEl as Element).clientHeight / 2 - wrapperEl.clientHeight / 2
            );
            wrapperEl.style.top = `${topPosition}px`;
            wrapperAnimation.fromTo('opacity', 0.01, 1);
            break;
        default:
            wrapperAnimation.fromTo('transform', 'translateY(100%)', `translateY(${bottom})`);
            break;
    }
    return baseAnimation
        .addElement(hostEl)
        .easing('cubic-bezier(.155,1.105,.295,1.12)')
        .duration(400)
        .addAnimation(wrapperAnimation);
};
// opts used as toastController.create(opts)
if (this.tabsVisible) {
    opts.enterAnimation = (baseEl: ShadowRoot) => iosEnterAnimation(baseEl, opts.position, 60)
}
const toast = await toastController.create(opts);
// ...

This worked perfectly for me, thank you. For anyone confused, remember to import the following:

import { createAnimation } from '@ionic/angular';
import { Animation } from '@ionic/angular';

EDIT: Do you know how I could accomplish this same functionality on Android?

@j4vs
Copy link

j4vs commented Jan 19, 2022

4 months more and we can give this bug a happy birthday!

I mean, seriously. How hard it is to have the tabs show where they should? I have a tabbed interface (using ion-tabs) and the toast appears above the tabs, giving BAD UX as the user cannot switch between tabs until the toast has dismissed.

We're in Ionic 6 already. Any solutions to this?

I found a solution for Ionic 6

Ts

this.toastController.create({
      message: 'Message to show',
      cssClass: 'customToast',
      duration: 1000,
      position: 'top'
});

css

ion-toast.customToast {
    --background: none;
    &::part(container) {
        background: #FF812D;
        color: #fff;
        border-radius: 10px;
        border: 2px solid #fff;
        font-size: 18px;
        margin-top: 50px;
    }
}

Now you can change the top or bottom or font size or other properties

@ryubal-sh
Copy link

ryubal-sh commented Apr 29, 2022

For me this was more than enough.

ion-toast {
	transform: translateY(-80px); // I'm using taller than normal tabs
}

@scriptify
Copy link

For React users, I've found a dirty workaround. This should really be supported by design.
ref doesn't seem to be called for me, so I had to use querySelector.
⚠ Don't try this at home.

const DangerousToast = ({ ... }: Props) => {
  useEffect(() => {
    if (!isVisible) return;
    const toastElem = document.querySelector('ion-toast');
    if (toastElem) {
      const style = document.createElement('style');
      style.innerHTML = '.toast-wrapper { bottom: 100px !important; }';
      toastElem.shadowRoot?.appendChild(style);
    }
  }, [isVisible]);

  return (
    <>
      <IonToast
        isOpen={...}
        message="Blabla"
        position="bottom"
      />
    </>
  );
};

@fbl773
Copy link

fbl773 commented Jul 5, 2022

It would be sweet if I could just shove the toast in a <div> or something on my page. If I could define the toast in my html, that would be ideal.

@judsonmusic
Copy link

judsonmusic commented Jan 17, 2023

I am using angular with ionic and this is the best solution ive come up with.
async presentToast(position: 'top' | 'middle' | 'bottom', message: string) {

await this._toastController
   .create({
     message,
     duration: 1500,
     position: position,
     color: 'success',
     cssClass: `toast-custom-${position}`
   })
   .then(toast => {
     toast.present();
   });
}
  :ng-deep .toast-custom-bottom {
  transform: translateY(-50px) !important;
}

Notice i am using a custom class that is dynamically created but i can style however i want to. Cheers :)

@liamdebeasi liamdebeasi added type: feature request a new feature, enhancement, or improvement and removed type: feature request a new feature, enhancement, or improvement labels Jun 6, 2023
@0x7061
Copy link

0x7061 commented Jun 28, 2023

@judsonmusic Great find. Sadly the ::ng-deep pseudo-class is deprecated now.

averyjohnston added a commit that referenced this issue Oct 4, 2023
…28248)

Issue number: resolves #17499

---------

<!-- 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. -->

Currently, there isn't a way to position toasts such that they don't
overlap navigation elements such as headers, footers, and FABs.

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

Added the new `positionAnchor` property, which specifies an element that
the toast's position should be anchored to.

While the name can be tweaked, we should take care to keep the relation
between it and the `position` property clear. The `position` acts as a
sort of "origin" point, and the toast is moved from there to sit near
the chosen anchor element. This is important because it helps clarify
why the toast sits above the anchor for `position="bottom"` and vice
versa.

I chose not to rename the `position` prop itself to avoid breaking
changes.

Docs PR: ionic-team/ionic-docs#3158

## 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. -->

---------

Co-authored-by: ionitron <[email protected]>
Co-authored-by: Liam DeBeasi <[email protected]>
@averyjohnston
Copy link
Contributor

Hi folks, this feature has been added via #28248 and will be available in a future minor release of Ionic. Thank you!

Copy link

ionitron-bot bot commented Nov 3, 2023

Thanks for the issue! This issue is being locked to prevent comments that are not relevant to the original issue. If this is still an issue with the latest version of Ionic, please create a new issue and ensure the template is fully filled out.

@ionitron-bot ionitron-bot bot locked and limited conversation to collaborators Nov 3, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
package: core @ionic/core package type: feature request a new feature, enhancement, or improvement
Projects
None yet
Development

No branches or pull requests