primefaces / primeng

The Most Complete Angular UI Component Library
https://primeng.org
Other
10.45k stars 4.6k forks source link

New Component: Sidebar #3134

Closed nsksaisaravana closed 7 years ago

nsksaisaravana commented 7 years ago

All components are very nice. I looking for a component like Office UI Fabric Panel, to check, please have a look at this url

https://dev.office.com/fabric#/components/panel

I'm submitting a ... (check one with "x")

[ ] bug report => Search github for a similar issue or PR before submitting
[ ] feature request => Please check if request is not on the roadmap already https://github.com/primefaces/primeng/wiki/Roadmap
[ ] support request => Please do not submit support request here, instead see http://forum.primefaces.org/viewforum.php?f=35

Plunkr Case (Bug Reports) Please fork the plunkr below and create a case demonstrating your bug report. Issues without a plunkr have much less possibility to be reviewed.

http://plnkr.co/edit/Wj39h1?p=preview

Current behavior

Expected behavior

Minimal reproduction of the problem with instructions

What is the motivation / use case for changing the behavior?

Please tell us about your environment:

ova2 commented 7 years ago

Cool component. We have a full page overlay variant in our project (called it lightbox).

nsksaisaravana commented 7 years ago

In future whether we can have such components in Primeng ?

Ketec commented 7 years ago

Would be nice to have - currently a lot of our app depends on a sidebar. We end up using ng-sidebar - but the quality is often spotty and updates unreliable.

IF you consider this - make sure to have a option for non fixed sidebar that could be placed inside a container (like relative parent and absolute sidebar).

A vertical scrollbar would be nice if contents do not fit - instead of stretching the whole page (or leave room to customize it with css). Another major issue with hiding overflows is that it ends up clipping dropdown menus etc - perhaps you can find a solution to that as well?

Separate template able header (where close button is - so we could add extra buttons like toggle for wider sidebar etc).

dhniels commented 7 years ago

We created our own component for something like this. basically just a menu that can slide in from the top/bottom/left/right of a container. Material has something similar called a bottom sheet.

I would consider this an essential component for PrimeNG

nsksaisaravana commented 7 years ago

This component will be very useful, instead of pop up window we can use this. Currently, we are using ng-office UI slide bar in AngularJS but in Angular 2 we don't have such component.

cagataycivici commented 7 years ago

Implemented for 4.2.2.

cagataycivici commented 7 years ago

ezgif-4-5fa4c98c33

nsksaisaravana commented 7 years ago

Thanks a lot for your hard work and looks awesome.

Can we have properties like small, medium, large and according to that we can have a size of the panel?

ova2 commented 7 years ago

Cool. The next topic for PrimeNG's book, second edition -:).

Can we do the sidebar responsive? I mean a "full screen" mode on small screens, like Angular Material Dialog.

Ketec commented 7 years ago

Since it has no documentation yet - does it use "fixed" display or relative-absolute? For example could i put it inside the "Documentation" tab in the demo above? Does it close when clicked outside? Or provides a attribute to toggle and implement the open/close ourselves?

cagataycivici commented 7 years ago

Initial features are;

dhniels commented 7 years ago

Will we be able to attach this to a certain element? it would be cool to use these as a sort of slide over menu inside of a dialog for example.

nsksaisaravana commented 7 years ago

@cagataycivici I am after those features mentioned above !!! Thanks, guys.

pieterlouw1974 commented 7 years ago

I dont see this in the demo https://www.primefaces.org/primeng/#/setup

Serranom4 commented 7 years ago

Can't find SidebarModule in 4.2.2... the component is there but the module is not exported.

pantonis commented 7 years ago

@cagataycivici How do I prevent closing of the sidebar when outside is clicked?

Thanks

bjareczek commented 7 years ago

Will be great when ready! Looks like export not included in primeng.d.ts. Also, can't tell for sure by think sidebar.d.ts needs to implement ngOnDestroy in class declaration. In the demo page source code, there are a bunch of closing curly braces that make for unusable code if someone copies/pastes from there. Hope that helps!

Ah, and the export is missing from primeng.js, I believe. Yes -- that does the trick. On to testing. Thank you!

pantonis commented 7 years ago

@bjareczek can you share code fix

bjareczek commented 7 years ago

@pantonis , PR is pending: https://github.com/primefaces/primeng/pull/4116

ova2 commented 7 years ago

Sidebar gets not closed on ESC key. @cagataycivici: is it possible to implement this behavior like in Dialog?

ralph-wermke commented 7 years ago

Is it possible to have different modes like here? https://material.angular.io/components/sidenav/overview#sidenav-mode

Is there an example how to use appendTo. Can't get it work.

nop1984 commented 6 years ago

Thanks for work. Can sidebar have options to: 1) Not to display overlay mask over rest of area 2) Have a pin button to make sidebar "sticky" and get a part of parent area as "position: relative" ?

nop1984 commented 6 years ago

Something like this ... (@ralph-wermke take a look) This component has

import {NgModule,Component,AfterViewInit,AfterViewChecked,OnDestroy,Input,Output,EventEmitter,ViewChild,ElementRef,Renderer2} from '@angular/core';
import {trigger, state, style, transition, animate, AnimationMetadata} from '@angular/animations';
import {CommonModule} from '@angular/common';
import {DomHandler} from 'primeng/primeng';

import {AnimationBuilder, AnimationPlayer, AnimationFactory} from "@angular/animations";

import {Directive,  HostBinding,  HostListener } from '@angular/core';

@Component({
    selector: 'p-sidebarex',
    template: `
        <div #sidebar_tassel *ngIf="useTassel" class="sidebar-ex-tassel" (click)="tasselClick($event)"></div>
        <div #container [ngClass]="{'ui-sidebar ui-widget ui-widget-content ui-shadow':true, 'ui-sidebar-active': visible, 
            'ui-sidebar-left': (position === 'left'), 'ui-sidebar-right': (position === 'right'),
            'ui-sidebar-top': (position === 'top'), 'ui-sidebar-bottom': (position === 'bottom'), 
            'ui-sidebar-full': fullScreen}"
            [@panelState]="visible ? 'visible' : 'hidden'" [ngStyle]="style" [class]="styleClass">
            <a *ngIf="pinable" [@pinbtnState]="pinned ? 'pinned' : 'unpinned'" [ngClass]="{'ui-sidebar-close ui-corner-all':true}" href="#" role="button" (click)="pin($event)">
                <span class="fa fa-fw fa-thumb-tack"></span>
            </a>
            <a [ngClass]="{'ui-sidebar-close ui-corner-all':true}" href="#" role="button" (click)="close($event)">
                <span class="fa fa-fw fa-close"></span>
            </a>
            <ng-content></ng-content>
        </div>
        <div #sidebar_workarea_wrapper class="sidebar-ex-workarea-wrapper"></div>
    `,
    styleUrls: ['./sidebar-ex.component.scss'],
    styles: ['.sidebar-ex-tassel {background: linear-gradient(to right,darkgrey, white); position:absolute;} '],
    animations: [
        trigger('panelState', [
            state('hidden', style({
                opacity: 0
            })),
            state('visible', style({
                opacity: 1
            })),
            transition('visible => hidden', animate('300ms ease-in')),
            transition('hidden => visible', animate('300ms ease-out'))
        ]),
        trigger('pinbtnState', [
            state('pinned', style({
                transform: 'rotate(0deg)',
                opacity: 1
            })),
            state('unpinned', style({
                transform: 'rotate(45deg)',
                opacity: 0.7
            })),
            transition('pinned => unpinned', animate('300ms ease-in')),
            transition('unpinned => pinned', animate('300ms ease-out'))
        ]),
    ],
    providers: [DomHandler]
})
export class SidebarEx implements AfterViewInit, AfterViewChecked, OnDestroy {

    _useMask: boolean = true;

    _useTassel: boolean = true;

    _tasselVisible: boolean = false;

    @Input() position: string = 'left';

    @Input() fullScreen: boolean;

    @Input() appendTo: string;

    @Input() blockScroll: boolean = false;

    @Input() style: any;

    @Input() styleClass: string;

    @Input() autoZIndex: boolean = true;

    @Input() baseZIndex: number = 0;

    @ViewChild('container') containerViewChild: ElementRef;

    @Output() onShow: EventEmitter<any> = new EventEmitter();

    @Output() onHide: EventEmitter<any> = new EventEmitter();

    @Output() visibleChange:EventEmitter<any> = new EventEmitter();

    @Output() pinChange:EventEmitter<any> = new EventEmitter();

    initialized: boolean;

    _visible: boolean;

    _pinned: boolean;

    _pinable: boolean;

    preventVisibleChangePropagation: boolean;

    mask: HTMLDivElement;

    @ViewChild('sidebar_workarea_wrapper') workareaWrapper: ElementRef;

    @ViewChild('sidebar_tassel') tassel: ElementRef;

    maskClickListener: Function;

    executePostDisplayActions: boolean;

    constructor(public el: ElementRef, public domHandler: DomHandler, public renderer: Renderer2, private _builder: AnimationBuilder) {}

    //or use ElementRef.nativeElement.addEventListener(...)
    @HostListener('document:mousemove', ['$event'])
    coordinates(event: MouseEvent): void {
        if (this._useTassel && !this.visible) {
            let tasselDelta = 20;
            const brect = this.containerViewChild.nativeElement.getBoundingClientRect(); //x, y, width, height, left, right, top, bottom

            switch (this.position) {
                case 'left': {
                    this.tasselVisible = event.clientX - brect.right <= tasselDelta;
                    break;
                }
                case 'right': {
                    this.tasselVisible = brect.left - event.clientX <= tasselDelta;
                    break;
                }
                case 'top': {
                    this.tasselVisible = event.clientY - brect.bottom <= tasselDelta;
                    break;
                }
                case 'bottom': {
                    this.tasselVisible = brect.top - event.clientY <= tasselDelta;
                    break;
                }
            }
        }
    }

    ngAfterViewInit() { 
        this.initialized = true;

        if(this.appendTo) {
            if(this.appendTo === 'body') {
                document.body.appendChild(this.containerViewChild.nativeElement);
            }
            else {
                this.domHandler.appendChild(this.containerViewChild.nativeElement, this.appendTo);

            }
        }

        this.renderer.appendChild(this.containerViewChild.nativeElement.parentElement.parentElement, this.workareaWrapper.nativeElement)

        let i = 0;
        while(i < this.containerViewChild.nativeElement.parentElement.parentElement.children.length) {
            let child = this.containerViewChild.nativeElement.parentElement.parentElement.children[i];
            if (child !== this.containerViewChild.nativeElement.parentElement && child !== this.workareaWrapper.nativeElement) {
                this.renderer.appendChild(this.workareaWrapper.nativeElement, child);
                continue;
            }

            i++;
        }

        if(this.visible) {
            this.show();
        }
    }

    @Input() get tasselVisible(): boolean {
        return this._tasselVisible;
    }

    set tasselVisible(val: boolean) {
        if (this._tasselVisible === val)
            return;
        let anim = <AnimationMetadata>{};
        let hide_style = style({width:0,height:0, ['background-color']:'black', position: 'absolute'});
        let show_style = style({});
        const tasselsize = 20;
        switch (this.position) {
            case 'left': {
                show_style = style({width: tasselsize, height: '100%', position: 'absolute', left:0, top:0});
                hide_style = style({width:0,height:"100%", position: 'absolute', left:0, top:0});
                break;
            }
            case 'right': {
                show_style = style({width: tasselsize, height: '100%', position: 'absolute', right:0, top:0});
                hide_style = style({width: 0, height: '100%', position: 'absolute', right:0, top:0});
                break;
            }
            case 'top': {
                show_style = style({width: '100%', height: tasselsize, position: 'absolute', left:0, top:0});
                hide_style = style({width: '100%', height: 0, position: 'absolute', left:0, top:0});
                break;
            }
            case 'bottom': {
                show_style = style({width: '100%', height: tasselsize, position: 'absolute', left:0, bottom:0});
                hide_style = style({width: '100%', height: 0, position: 'absolute', left:0, bottom:0});
                break;
            }
        }

        let factory = <AnimationFactory>{};
        if(val) {
            factory = this._builder.build([hide_style, animate('300ms ease-in', show_style)]);

        } else {
            factory = this._builder.build([show_style, animate('300ms ease-out', hide_style)]);
        }

        const player = factory.create(this.tassel.nativeElement, {});
        player.play();

        this._tasselVisible = val;
    }

    @Input() get visible(): boolean {
        return this._visible;
    }

    set visible(val:boolean) {
        this._visible = val;
        if(this.initialized && this.containerViewChild && this.containerViewChild.nativeElement) {
            if(this._visible) {
                this.show();
                this.tasselVisible = false;
            }
            else {
                if(this.preventVisibleChangePropagation)
                    this.preventVisibleChangePropagation = false;
                else
                    this.hide();
            }
        }
    }

    @Input() get pinned(): boolean {
        return this._pinned;
    }

    set pinned(val: boolean) {
        this._pinned = val;
        this.workareaAnimate(val);
    }

    @Input() get useMask(): boolean {
        return this._useMask;
    }

    set useMask(val: boolean) {
        this._useMask = val;
        if(val)
            this.enableModality();
        else
            this.disableModality();
    }

    @Input() get useTassel(): boolean {
        return this._useTassel;
    }

    set useTassel(val: boolean) {
        this._useTassel = val;
    }

    private workareaAnimate(like_pinned: boolean) {
        console.log('workareaAnimate ' + like_pinned);
        let anim = <AnimationMetadata>{};
        //let unpinned_style = style('*');
        let unpinned_style = style({['margin-' + this.position]: 0});
        let pinned_style = style({['margin-' + this.position]: 
            this.position == 'left' || this.position == 'right' ? 
                this.domHandler.getOuterWidth(this.containerViewChild.nativeElement) + 'px' : 
                this.domHandler.getOuterHeight(this.containerViewChild.nativeElement)+ 'px'
            });

        let factory = <AnimationFactory>{};
        if(like_pinned) {
            factory = this._builder.build([unpinned_style, animate('300ms ease-in', pinned_style)]);

        } else {
            factory = this._builder.build([pinned_style, animate('300ms ease-out', unpinned_style)]);
        }

        const player = factory.create(this.workareaWrapper.nativeElement, {});
        player.play();
    }

    @Input() get pinable(): boolean {
        return this._pinable;
    }

    set pinable(val: boolean) {
        this._pinable = val;
    }

    ngAfterViewChecked() {
        if(this.executePostDisplayActions) {
            this.onShow.emit({});
            this.executePostDisplayActions = false;
        }
    }

    show() {
        this.executePostDisplayActions = true;
        if(this.autoZIndex) {
            this.containerViewChild.nativeElement.style.zIndex = String(this.baseZIndex + (++DomHandler.zindex));
        }
        this.enableModality();
        if (this._pinned)
            this.workareaAnimate(true);

    }

    hide() {
        if (this._pinned)
            this.workareaAnimate(false);
        this.onHide.emit({});
        this.disableModality();
    }

    pin() {
        this.pinned = !this.pinned;
        this.pinChange.emit({});
    }

    close(event: Event) {
        this.preventVisibleChangePropagation = true;
        this.hide();
        this.visibleChange.emit(false);
        this.visible = false;
        event.preventDefault();
    }

    tasselClick(event: Event) {
        this.preventVisibleChangePropagation = false;
        if (this.visible) {

            //this.close(event);
            this.preventVisibleChangePropagation = true;
            this.visible = false;
        }
        else {
            this.visible = true;
        }
        this.preventVisibleChangePropagation = false;
    }

    enableModality() {
        if(!this.mask && this._useMask) {
            this.mask = document.createElement('div');
            this.mask.style.zIndex = String(parseInt(this.containerViewChild.nativeElement.style.zIndex) - 1);
            this.domHandler.addMultipleClasses(this.mask, 'ui-widget-overlay ui-sidebar-mask');
            this.maskClickListener = this.renderer.listen(this.mask, 'click', (event: any) => {
               this.close(event);
            });
            document.body.appendChild(this.mask);
            if(this.blockScroll) {
                this.domHandler.addClass(document.body, 'ui-overflow-hidden');
            }
        }
    }

    disableModality() {
        if(this.mask) {
            this.unbindMaskClickListener();
            document.body.removeChild(this.mask);
            if(this.blockScroll) {
                this.domHandler.removeClass(document.body, 'ui-overflow-hidden');
            }
            this.mask = null;
        }
    }

    unbindMaskClickListener() {
        if(this.maskClickListener) {
            this.maskClickListener();
            this.maskClickListener = null;
        }
    }

    ngOnDestroy() {
        this.initialized = false;

        if(this.visible) {
            this.hide();
        }

        if(this.appendTo) {
            this.el.nativeElement.appendChild(this.containerViewChild.nativeElement);
        }

        this.unbindMaskClickListener();
    }
}

//@NgModule({
//    imports: [CommonModule],
//    exports: [SidebarEx],
//    declarations: [SidebarEx]
//})
//export class SidebarModuleEx { }
ramukodandapuram commented 6 years ago

I am using primeng sidebar for named routing outlets. I need to get alert when we click on either outside sidebar or cross ( X ) on top of sidebar. I have no idea! help me!

pantonis commented 6 years ago

Use the onHide event which is fired when sidebar is closing Sidebar

LoveenDyall commented 6 years ago

Why is the .ui-sidebar-lg class style so small on small screens? It should be over 60% of the screen width IMHO

satheeshnair commented 6 years ago

Any working example of appendTo property in sidebar? It is always showing up for the entire browser window. Any help would be great.

tonymporter commented 6 years ago

The appendTo working for me when pointing to the variable attached to a @ViewChild(). Not found much use for it yet though.

MariemBADIS commented 5 years ago

Initial features are;

  • Fixed position relative to the document itself, not to another element
  • 4 positions, top, bottom, left, right
  • Closes when outside is clicked
  • Has a close icon at top right corner to close manually
  • Can be managed from model like a dialog using visible property
  • Has sm, md and lg as built-in sizes
  • Responsive
  • Full screen mode

how to let the sidebar open when I clicked outside the screen?

balayevkerim commented 4 years ago

How to adjust the content of sidebar when it s small device? and also scrollable may be?

lucasdesiderio commented 4 years ago

Thanks for work. Can sidebar have options to:

  1. Not to display overlay mask over rest of area
  2. Have a pin button to make sidebar "sticky" and get a part of parent area as "position: relative" ?

Is this item 2 (pin button) being developed?

cyraid commented 3 years ago

A sticky/lock button would be nice, which would take away the shadow and make it fixed.

mdsardar commented 1 year ago

Initial features are;

  • Fixed position relative to the document itself, not to another element
  • 4 positions, top, bottom, left, right
  • Closes when outside is clicked
  • Has a close icon at top right corner to close manually
  • Can be managed from model like a dialog using visible property
  • Has sm, md and lg as built-in sizes
  • Responsive
  • Full screen mode

Thanks for this feature. For some reason we've to hide or disable this close button. Can you give me an working example of this? I've recently started using primefaces. I tried to closable="false" but didn't work. After searching through found this is available here https://github.com/primefaces/primeng/issues/5757. Can you share me detailed link on how to use this? Thank you.