How to close headlessui-vue Popover from code - vuejs3

I have a vue component that shows a popover with some content using the headlessui for vue and I want to close it when I click on the content. I have read the headlessui/vue docs for manually handling the opening and closing of a Popover which states:
If you'd rather handle this yourself (perhaps because you need to add an extra wrapper element for one reason or another), you can pass a static prop to the PopoverPanel to tell it to always render, and then use the open slot prop to control when the panel is shown/hidden yourself.
I have:
<Popover v-slot="{ open }">
<PopoverButton>
</PopoverButton>
<div v-if="open">
<PopoverPanel static>
</PopoverPanel>
</div>
</Popover>
and it works so far but I want to close the Popover when I click the some content inside it, essentially I want to know how I can access that "open" in my script. I'm quite new to vue so maybe I'm missing something simple.

One workaround would be to manually click the PopoverButton inside of the panel.
Here's an example how that could work in React:
const buttonRef = useRef();
<Popover>
<Popover.Button ref={buttonRef}>Click me</Popover.Button>
<Popover.Panel>
<button onClick={() => buttonRef.current?.click()}>Content</button>
</Popover.Panel>
</Popover>

Related

Storybook Error: Couldn't find story matching 'components-button--page'

I can't seem to resolve this error I am getting in Storybook. I have the following file called Button.stories.mdx:
import { Meta, Story, ArgsTable, Canvas } from '#storybook/addon-docs/blocks';
import Button from './Button';
import ButtonStory from './Button.stories.tsx'
<Meta title="Components/Button" component={Button} />
export const Template = (args) => <Button {...args } />
# Button Component
---
This Button component is supposed to handle all states for a button. It extends off of the HTML button type so that you should have all native HTML features that a button would provide.
We may make some of these optional props required if we deam that it is important. This is usually an accessibility call.
This button should handle actions that occur on the page and if you want to use a href to make it a link you should surround it within an a tag.
Types supported by aero-design-system:
- Primary
- Secondary
## Quick Start
To create a button, use import the `Button` and give it the following props `props`.
<Canvas>
</Canvas>
<ArgsTable of={Button} args={{
backgroundColor: { control: 'color' }
}} />
And I am getting the following error:
Couldn't find story matching 'components-button--page'.
I have tried placing a blank story in there with that ID but that didn't seem to fix anything. I just got a new error
Uncaught Error: Docs-only story
I haven't been able to see anything related to this on here yet.
In my case, the problem was simple, I was trying to load unexisting story:
http://localhost:6006/?path=/story/spoiler--primary
instead, I should've loaded this :)
http://localhost:6006/?path=/story/testcomponent--primary
Something that storybook's documentation doesn't seem to mention is a few important bits:
<Story/> component must have a name property with at least ONE character
<Story/> component must have at least a single child within it
Meaning, the minimum requirement to get the mdx file to render when using the <Story/> component is this:
<Story name="default">
<Button/>
</Story>
This is regardless of whether the <Story/> component is wrapped around the <Canvas/> component or not.
The second half of the problem is <Canvas/> component, and it has just one condition:
It must have at least a single child within it
so the mimimum requirement for <Canvas/> to render is this:
<Canvas>
<Button/>
</Canvas>
Combining everything together for your case scenario, what you need to do with <Canvas/> is this:
<Canvas>
<Story name="default">
<Button/>
</Story>
</Canvas>
Try setting it that way, then refresh the page.
If you are using storybook v6. Try to check stories property at your .storybook/main.js. Make sure the path/file type is correct.
module.exports={
stories:[
'../src/components/Button.stories.mdx', // default page
'../src/**/*.stories.#(js|jsx|ts|tsx|mdx)'
]
}

How would one close previously opened NgbDropdowns when new NgbDropdowns are opend using the enter key?

I'm a newbie to NgbDropdown and related ng-bootstrap code, and I'm having trouble figuring out how to close all previous NgbDropdowns when a new one is opened using the enter key.
I've created a number of NgbDropdowns on a page in my Angular project, and I find that when I click from dropdown button to dropdown button, the previously opened dropdown closes; however, if I TAB from an open dropdown to another dropdown button and use the enter key to open the second dropdown, as may be needed for accessibility, the first dropdown does NOT close. I am left with two drop-downs overlaying each other.
This sequence can be duplicated on the main NgbDropdown example page at https://ng-bootstrap.github.io/#/components/dropdown/examples: on a different button (First example at the top.)
Sequence:
1. Tab to "Toggle Dropdown"
2. Hit the enter key to open the dropdown
3. Tab through the dropdown options, pluse one more time, so that you've
tabbed to the "toggle drop-up" button.
4. Hit the enter key
5. Both dropdowns will be open simultaneously.
Screen Shot
So apparently the code that closes previous dropdowns on click doesn't work when the enter key is pressed on a different button. Not finding much documentation about what can be done with the various Ngb objects in typescript, I am left not knowing how to close all previous dropdowns when a new dropdown is opened with the enter key. All I can think of doing is:
1) Loop over all dropdowns in the ts file, closing them prior to opening the latest. If this is the best solution, I do not see any way to loop over a collection of open drop-downs. Is there such an object/array available to me as part of the NgbSolution, or would I have to add each to an array on my own?
2) Trigger the click event when the enter key is pressed on a button. Again, I am unaware of how to have one event trigger another on an NgbDropdown object.
Any pointers would be appreciated. I've not posted my code here because it is the same as the basic example referenced above.
For your suggestion #1 (loop over all dropdowns and close them), you can implement it as follows:
In your HTML, declare each dropdown as a DOM variable using Template Reference Variables (the # syntax):
<div ngbDropdown class="d-inline-block" #dd1="ngbDropdown">
...
</div>
Add a click handler to the button as follows. This allows you to pass in a reference to the dropdown to the function called by the click handler:
<button class="btn btn-outline-primary" id="dropdownBasic1" ngbDropdownToggle (click)="closeOthers(dd1)">Toggle dropdown</button>
The complete HTML for a dropdown should look like this:
<div ngbDropdown class="d-inline-block" #dd1="ngbDropdown">
<button class="btn btn-outline-primary" id="dropdownBasic1" ngbDropdownToggle (click)="closeOthers(dd1)">
Toggle dropdown
</button>
<div ngbDropdownMenu aria-labelledby="dropdownBasic1">
<button ngbDropdownItem>Action - 1</button>
<button ngbDropdownItem>Another Action</button>
<button ngbDropdownItem>Something else is here</button>
</div>
</div>
Add the following as a class variable of your Typescript component. This allows you to reference all the dropdowns displayed in the component from the Typescript file:
#ViewChildren(NgbDropdown) dropdowns: QueryList<NgbDropdown>;
Add a click function to your Typescript component:
closeOthers(clickedDropdown: NgbDropdown) {
// Close all dropdowns
this.dropdowns.toArray().forEach(el => {
el.close();
});
// Open the dropdown that was clicked on
clickedDropdown.open();
}
Now whenever you click on a dropdown (or select it by tabbing and pressing return) it will call the closeOthers function which will:
Close all dropdowns
Open the dropdown that was clicked
Please see this StackBlitz for a working demo.
#Dordrecht - the approach I would take for the functionality mentioned in your comment below would be to create a service as follows:
export class ModalNotificationService {
private _closeModals: Subject<void> = new Subject<void>();
private _closeModals$: Observable<void> = this._closeModals.asObservable();
public emitCloseModalEvent() {
this._closeModals.next();
}
get closeModals$(): Observable<void> {
return this._closeModals$;
}
}
This service would be injected into any component that has modals and those components would subscribe to the observable closeModals$ and then close the modals in their own component. Modify the closeOthers method to call the new service's emitCloseModalEvent method to notify all the other components.

How to CSS style KendoUI-Dialog titlebar in 2 different ways when opening from the same Component?

I have a problem styling the KENDO UI dialog:
I have a component, lets call it WatComponent. Inside it,
If the user clicks the "Forbidden" button, I want a warning styled dialog to pop-up, with yellow/orange colored titlebar,
If the user clicks the "DANGER" button, I want an error styled dialog to pop-up, with red titlebar.
If I open the dialog via the DialogService:
const dialogRef = this.dialogService.open({
title: this.danger ? 'DANGER!' : 'Warning!',
content: WatDialogComponent
});
const userInfo = dialogRef.content.instance;
userInfo.danger = this.danger;
How can I apply two different CSS styles (in any way) to make the titlebar appear in different colors when opened from the same component?
I have tried
adding a class to the <div kendoDialogContainer class="myTitlebarClass"></div> but of course, I didnt expect it to work (it didnt).
giving the property danger some functionality to pass it to the titlebar, but unfortunately, it affects only titlebars inside WatDialogComponent, and I want to access the titlebar that is one step outside.
looking for a property in the DialogRef that would let me do that.
Is there a straightaway solution to this that I'm missing? If not, is there a workaround?
Looking at the DialogRef API
https://www.telerik.com/kendo-angular-ui/components/dialogs/api/DialogRef/
it includes the ComponentRef which can be used to get the host element and add a class:
this.dialog = this.dialogService.open({
title: 'Warning',
content: 'Warning'
});
this.dialog.dialog.location.nativeElement.classList.add('warning');
https://plnkr.co/edit/RkW3zHFbmMuQqk4UDYNY?p=preview

Todo App Example: Console doesn't show errors yet app doesn't work

Link to the git repo: https://github.com/todonoshow
I suspect the issue is related to the connection between the reducer and the container. In dir: src/containers/TodoList.js, I called:
mapStateToProps = state => ({ state: state.todos })
and
export default connect(mapStateToProps)(TodoList)
But I'm not sure if this is the right way to go about passing what the todos reducer returns to the container.
App's behavior: Layout shows up but nothing happens when I click 'Add Todo' after filling the input field.
Expected behavior: Input from the input field to show up as a list item part of an unordered list.
Your <button> must be wrapped within <form> as children, otherwise onSubmit will not even be called.
<form ...>
<button ... />
</form>
p.s. using ref and direct DOM manipulation is discouraged in React.

What would make a Meteor app stop scrolling?

Any browser. All other functions operate as normal. I can traverse routes, open modals, dropdowns, manipulate collections... everything works.
But at some point, the browser will no longer scroll. In any view. Unless I refresh the browser window. Then scrolling goes back to normal.
What might cause this? Why does refreshing the page fix the issue? Is there a method to smack reactivity upside the head?
This is likely due to navigating away from a (Bootstrap ?) modal without properly closing it.
When you show up a modal in HTML5 frameworks, they usualy display a backdrop (some kind of fullscreen grayed out component) and disable scrolling to simulate desktop experience.
You can solve this problem using a simple iron:router pattern, if you're not using this package, be sure to execute the corresponding code whenever you navigate away from a page where a modal might get shown.
client/views/modal/modal.html
<template name="modal">
<div id="modal" class="modal fade">
...
</div>
</template>
<template name="main">
{{> modal}}
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#modal">
Show Modal
</button>
</template>
client/config/router.js :
// define the plugin
Iron.Router.plugins.hideBootstrapModalOnStop=function(router,options){
router.onStop(function(){
// hide modal backdrop on route change
$(".modal-backdrop").remove();
// remove modal-open state on body
$("body").removeClass("modal-open");
});
};
// activate the plugin
Router.plugin("hideBootstrapModalOnStop");
The .modal-open class set on the HTML body tag is the one disabling scrolling when it is set, so by making sure that we remove that class whenever we navigate away from a page when a modal is possibly shown, we prevent this weird unexpected behavior to happen.

Resources