Skip to content

Commit b921ea1

Browse files
feat(drawer): Add inline version
1 parent 9dc8662 commit b921ea1

7 files changed

Lines changed: 255 additions & 5 deletions

File tree

angular/bootstrap/src/components/drawer/drawer.component.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,14 @@ export class DrawerComponent extends BaseWidgetDirective<DrawerWidget> {
230230
*/
231231
readonly resizable = input(undefined, {alias: 'auResizable', transform: auBooleanAttribute});
232232

233+
/**
234+
* If `true`, the drawer is inline.
235+
* When inline mode is enabled, the drawer stays in the document flow and moves content as it expands/resizes.
236+
*
237+
* @defaultValue `false`
238+
*/
239+
readonly inline = input(undefined, {alias: 'auInline', transform: auBooleanAttribute});
240+
233241
/**
234242
* An event emitted when the drawer size changes (width or height depending on the orientation).
235243
*
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import type {DrawerPositions} from '@agnos-ui/angular-bootstrap';
2+
import {DrawerComponent, DrawerHeaderDirective} from '@agnos-ui/angular-bootstrap';
3+
import {Component, signal} from '@angular/core';
4+
import {FormsModule} from '@angular/forms';
5+
6+
@Component({
7+
template: `
8+
<div class="demo-inline-drawer">
9+
<button class="btn btn-primary mb-3" (click)="drawer.api.open()">Toggle Inline Drawer</button>
10+
<div class="d-flex align-items-center mb-3">
11+
<label for="drawerPlacement" class="me-3">Placement:</label>
12+
<select id="drawerPlacement" [(ngModel)]="drawerPlacement" class="w-auto form-select">
13+
<option value="inline-start">Left</option>
14+
<option value="inline-end">Right</option>
15+
<option value="block-start">Top</option>
16+
<option value="block-end">Bottom</option>
17+
</select>
18+
</div>
19+
20+
<div class="d-flex" [class.flex-column]="drawerPlacement().includes('block')">
21+
<div [style.order]="drawerPlacement().endsWith('end') ? 2 : 1">
22+
<au-component #drawer auDrawer [auClassName]="drawerPlacement()" auResizable auInline>
23+
<ng-template auDrawerHeader>
24+
<h2>Inline Drawer</h2>
25+
<button class="btn-close ms-auto" (click)="drawer.api.close()" aria-label="Close"></button>
26+
</ng-template>
27+
<div class="p-3">
28+
<h6>Drawer Content</h6>
29+
<ul>
30+
<li>No backdrop overlay</li>
31+
<li>Stays in document flow</li>
32+
<li>Pushes page content</li>
33+
<li>Page remains scrollable</li>
34+
<li>Fully interactable</li>
35+
</ul>
36+
</div>
37+
</au-component>
38+
</div>
39+
40+
<div class="flex-grow-1 p-3" [style.order]="drawerPlacement().endsWith('end') ? 1 : 2">
41+
<h6>Main Page Content</h6>
42+
<p>This content is pushed aside by the inline drawer. You can interact with everything on this page even when the drawer is open.</p>
43+
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
44+
<button class="btn btn-secondary">Clickable Button</button>
45+
<p>Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
46+
<input type="text" class="form-control mt-2" placeholder="Type here..." />
47+
<p class="mt-2">
48+
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat
49+
non proident.
50+
</p>
51+
</div>
52+
</div>
53+
</div>
54+
`,
55+
styles: `
56+
.demo-inline-drawer {
57+
border: 1px solid #dee2e6;
58+
padding: 1rem;
59+
border-radius: 0.375rem;
60+
}
61+
`,
62+
imports: [DrawerComponent, DrawerHeaderDirective, FormsModule],
63+
})
64+
export default class InlineDrawerComponent {
65+
readonly drawerPlacement = signal<DrawerPositions>('inline-start');
66+
}

core-bootstrap/src/scss/drawer.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
display: inline-flex;
99
flex-direction: column;
10+
flex-shrink: 0;
1011
background: #fff;
1112
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.2);
1213
z-index: var(--#{$prefix}drawer-z-index);
@@ -84,6 +85,9 @@
8485
}
8586

8687
.au-drawer-header {
88+
display: inline-flex;
89+
align-items: center;
90+
width: 100%;
8791
padding: 0.75rem 1rem;
8892
font-weight: 600;
8993
border-bottom: 1px solid #e2e2e2;

core/src/components/drawer/drawer.ts

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ interface DrawerCommonPropsAndState extends WidgetsCommonPropsAndState {
3333
/**
3434
* CSS classes to be applied on the widget main container
3535
*
36-
* @defaultValue `'w-full'`
36+
* @defaultValue `''`
3737
*/
3838
className: string;
3939
/**
@@ -72,6 +72,13 @@ interface DrawerCommonPropsAndState extends WidgetsCommonPropsAndState {
7272
* @defaultValue `null`
7373
*/
7474
size: number | null;
75+
/**
76+
* If `true`, the drawer is inline.
77+
* When inline mode is enabled, the drawer stays in the document flow and moves content as it expands/resizes.
78+
*
79+
* @defaultValue `false`
80+
*/
81+
inline: boolean;
7582
}
7683

7784
/**
@@ -290,7 +297,7 @@ const defaultDrawerConfig: DrawerProps = {
290297
ariaLabelledBy: '',
291298
backdropClass: '',
292299
backdropTransition: noop,
293-
className: 'w-full',
300+
className: '',
294301
visible: false,
295302
container: typeof window !== 'undefined' ? document.body : null,
296303
transition: noop,
@@ -307,6 +314,7 @@ const defaultDrawerConfig: DrawerProps = {
307314
bodyScroll: false,
308315
size: null,
309316
focusOnInit: true,
317+
inline: false,
310318
};
311319

312320
const configValidator: ConfigValidator<DrawerProps> = {
@@ -332,6 +340,7 @@ const configValidator: ConfigValidator<DrawerProps> = {
332340
bodyScroll: typeBoolean,
333341
size: typeNumberOrNull,
334342
focusOnInit: typeBoolean,
343+
inline: typeBoolean,
335344
};
336345

337346
/**
@@ -342,14 +351,14 @@ const configValidator: ConfigValidator<DrawerProps> = {
342351
export const createDrawer: WidgetFactory<DrawerWidget> = createWidgetFactory('drawer', (config?: PropsConfig<DrawerProps>) => {
343352
const [
344353
{
345-
backdrop$,
354+
backdrop$: _backdrop$,
346355
backdropTransition$,
347356
backdropClass$,
348-
bodyScroll$,
357+
bodyScroll$: _bodyScroll$,
349358
transition$,
350359
verticalTransition$,
351360
visible$: requestedVisible$,
352-
container$,
361+
container$: _container$,
353362
className$,
354363
size$: _dirtySize$,
355364
animated$,
@@ -363,11 +372,17 @@ export const createDrawer: WidgetFactory<DrawerWidget> = createWidgetFactory('dr
363372
onMaximizedChange$,
364373
onResizingChange$,
365374
focusOnInit$,
375+
inline$,
366376
...stateProps
367377
},
368378
patch,
369379
] = writablesForProps(defaultDrawerConfig, config, configValidator);
370380

381+
// Override props when inline mode is enabled
382+
const backdrop$ = computed(() => (inline$() ? false : _backdrop$()));
383+
const bodyScroll$ = computed(() => (inline$() ? true : _bodyScroll$()));
384+
const container$ = computed(() => (inline$() ? null : _container$()));
385+
371386
const size$ = bindableProp(_dirtySize$, onSizeChange$, (value) => (value ? Math.round(value) : value));
372387

373388
const isVertical$ = computed(() => {
@@ -405,6 +420,9 @@ export const createDrawer: WidgetFactory<DrawerWidget> = createWidgetFactory('dr
405420
},
406421
styles: {
407422
position: computed(() => {
423+
if (inline$()) {
424+
return 'relative';
425+
}
408426
const container = container$();
409427
return container && isBrowserHTMLElement(container) && container !== document.body ? 'relative' : 'fixed';
410428
}),
@@ -578,6 +596,7 @@ export const createDrawer: WidgetFactory<DrawerWidget> = createWidgetFactory('dr
578596
backdropHidden$,
579597
hidden$,
580598
isVertical$,
599+
inline$,
581600
}),
582601
patch,
583602
api: {

demo/src/routes/docs/[framework]/components/drawer/examples/+page.svelte

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,21 @@
33
import Section from '$lib/layout/Section.svelte';
44
import sampleBasic from '@agnos-ui/samples/bootstrap/drawer/basic';
55
import sampleBody from '@agnos-ui/samples/bootstrap/drawer/body';
6+
import sampleInline from '@agnos-ui/samples/bootstrap/drawer/inline';
67
import samplePosition from '@agnos-ui/samples/bootstrap/drawer/position';
78
import sampleSizes from '@agnos-ui/samples/bootstrap/drawer/sizes';
89
</script>
910

1011
<Section label="Basic drawer" id="basic" level={2}>
1112
<Sample title="Basic example" sample={sampleBasic} height={190} noresize />
1213
</Section>
14+
<Section label="Inline drawer" id="inline" level={2}>
15+
<p>
16+
An inline drawer stays in the document flow and pushes the page content instead of overlaying it. The page remains fully scrollable and
17+
interactable.
18+
</p>
19+
<Sample title="Inline drawer example" sample={sampleInline} height={500} noresize />
20+
</Section>
1321
<Section label="Drawer size" id="positions" level={2}>
1422
<p>You can customize the drawer's width or height by adjusting the following CSS variables:</p>
1523
<p class="ps-5">
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import type {DrawerApi} from '@agnos-ui/react-bootstrap/components/drawer';
2+
import {Drawer} from '@agnos-ui/react-bootstrap/components/drawer';
3+
import {useRef, useState} from 'react';
4+
5+
type DrawerTypes = 'inline-start' | 'inline-end' | 'block-start' | 'block-end';
6+
7+
const InlineDemo = () => {
8+
const refDrawer = useRef<DrawerApi>(null);
9+
const [placement, setPlacement] = useState<DrawerTypes>('inline-start');
10+
11+
return (
12+
<>
13+
<div className="demo-inline-drawer">
14+
<button className="btn btn-primary mb-3" onClick={() => refDrawer.current?.open()}>
15+
Toggle Inline Drawer
16+
</button>
17+
<div className="d-flex align-items-center mb-3">
18+
<label htmlFor="drawerPlacement" className="me-3">
19+
Placement:
20+
</label>
21+
<select id="drawerPlacement" className="w-auto form-select" onChange={(e) => setPlacement(e.target.value as DrawerTypes)} value={placement}>
22+
<option value="inline-start">Left</option>
23+
<option value="inline-end">Right</option>
24+
<option value="block-start">Top</option>
25+
<option value="block-end">Bottom</option>
26+
</select>
27+
</div>
28+
29+
<div className={`d-flex ${placement.includes('block') ? 'flex-column' : ''}`}>
30+
<div style={{order: placement.endsWith('end') ? 2 : 1}}>
31+
<Drawer
32+
ref={refDrawer}
33+
header={
34+
<>
35+
<h2>Inline Drawer</h2>
36+
<button className="btn-close ms-auto" onClick={() => refDrawer.current?.close()} aria-label="Close"></button>
37+
</>
38+
}
39+
className={placement}
40+
resizable
41+
inline
42+
>
43+
<div className="p-3">
44+
<h6>Drawer Content</h6>
45+
<ul>
46+
<li>No backdrop overlay</li>
47+
<li>Stays in document flow</li>
48+
<li>Pushes page content</li>
49+
<li>Page remains scrollable</li>
50+
<li>Fully interactable</li>
51+
</ul>
52+
</div>
53+
</Drawer>
54+
</div>
55+
56+
<div className="flex-grow-1 p-3" style={{order: placement.endsWith('end') ? 1 : 2}}>
57+
<h6>Main Page Content</h6>
58+
<p>This content is pushed aside by the inline drawer. You can interact with everything on this page even when the drawer is open.</p>
59+
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
60+
<button className="btn btn-secondary">Clickable Button</button>
61+
<p>Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
62+
<input type="text" className="form-control mt-2" placeholder="Type here..." />
63+
<p className="mt-2">
64+
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat
65+
non proident.
66+
</p>
67+
</div>
68+
</div>
69+
</div>
70+
71+
<style>{`
72+
.demo-inline-drawer {
73+
border: 1px solid #dee2e6;
74+
padding: 1rem;
75+
border-radius: 0.375rem;
76+
}
77+
`}</style>
78+
</>
79+
);
80+
};
81+
82+
export default InlineDemo;
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<script lang="ts">
2+
import {Drawer} from '@agnos-ui/svelte-bootstrap/components/drawer';
3+
4+
type DrawerTypes = 'inline-start' | 'inline-end' | 'block-start' | 'block-end';
5+
let drawerPlacement = $state('inline-start' as DrawerTypes);
6+
// svelte-ignore non_reactive_update
7+
let drawer: Drawer;
8+
</script>
9+
10+
<div class="demo-inline-drawer">
11+
<button class="btn btn-primary mb-3" onclick={() => drawer.api.open()}>Toggle Inline Drawer</button>
12+
<div class="d-flex align-items-center mb-3">
13+
<label for="drawerPlacement" class="me-3">Placement:</label>
14+
<select id="drawerPlacement" bind:value={drawerPlacement} class="w-auto form-select">
15+
<option value="inline-start">Left</option>
16+
<option value="inline-end">Right</option>
17+
<option value="block-start">Top</option>
18+
<option value="block-end">Bottom</option>
19+
</select>
20+
</div>
21+
22+
<div class="d-flex" class:flex-column={drawerPlacement.includes('block')}>
23+
<div style="order: {drawerPlacement.endsWith('end') ? 2 : 1}">
24+
<Drawer bind:this={drawer} className={drawerPlacement} resizable inline visible>
25+
{#snippet header()}
26+
<h2>Inline Drawer</h2>
27+
<button class="btn-close ms-auto" onclick={() => drawer.api.close()} aria-label="Close"></button>
28+
{/snippet}
29+
<div class="p-3">
30+
<h6>Drawer Content</h6>
31+
<ul>
32+
<li>No backdrop overlay</li>
33+
<li>Stays in document flow</li>
34+
<li>Pushes page content</li>
35+
<li>Page remains scrollable</li>
36+
<li>Fully interactable</li>
37+
</ul>
38+
</div>
39+
</Drawer>
40+
</div>
41+
42+
<div class="flex-grow-1 p-3" style="order: {drawerPlacement.endsWith('end') ? 1 : 2}">
43+
<h6>Main Page Content</h6>
44+
<p>This content is pushed aside by the inline drawer. You can interact with everything on this page even when the drawer is open.</p>
45+
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
46+
<button class="btn btn-secondary">Clickable Button</button>
47+
<p>Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
48+
<input type="text" class="form-control mt-2" placeholder="Type here..." />
49+
<p class="mt-2">
50+
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
51+
proident.
52+
</p>
53+
</div>
54+
</div>
55+
</div>
56+
57+
<style>
58+
.demo-inline-drawer {
59+
border: 1px solid #dee2e6;
60+
padding: 1rem;
61+
border-radius: 0.375rem;
62+
}
63+
</style>

0 commit comments

Comments
 (0)