Skip to content

Commit 8cbb7c5

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

File tree

10 files changed

+405
-11
lines changed

10 files changed

+405
-11
lines changed

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ export class DrawerComponent extends BaseWidgetDirective<DrawerWidget> {
219219
/**
220220
* CSS classes to be applied on the widget main container
221221
*
222-
* @defaultValue `'w-full'`
222+
* @defaultValue `''`
223223
*/
224224
readonly className = input<string>(undefined, {alias: 'auClassName'});
225225

@@ -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: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
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+
<button class="btn btn-primary mb-3" (click)="drawer.api.open()">Toggle Inline Drawer</button>
9+
<div class="d-flex align-items-center mb-3">
10+
<label for="drawerPlacement" class="me-3">Placement:</label>
11+
<select id="drawerPlacement" [(ngModel)]="drawerPlacement" class="w-auto form-select">
12+
<option value="inline-start">Left</option>
13+
<option value="inline-end">Right</option>
14+
<option value="block-start">Top</option>
15+
<option value="block-end">Bottom</option>
16+
</select>
17+
</div>
18+
19+
<div class="d-flex demo-inline-drawer" [class.flex-column]="drawerPlacement().includes('block')">
20+
<div [style.order]="drawerPlacement().endsWith('end') ? 2 : 1">
21+
<au-component #drawer auDrawer [auClassName]="drawerPlacement()" auResizable auInline auVisible>
22+
<ng-template auDrawerHeader>
23+
<h2>Inline Drawer</h2>
24+
<button class="btn-close ms-auto" (click)="drawer.api.close()" aria-label="Close"></button>
25+
</ng-template>
26+
<div class="p-3">
27+
<h6>Drawer Content</h6>
28+
<ul>
29+
<li>No backdrop overlay</li>
30+
<li>Stays in document flow</li>
31+
<li>Pushes page content</li>
32+
<li>Page remains scrollable</li>
33+
<li>Fully interactable</li>
34+
</ul>
35+
</div>
36+
</au-component>
37+
</div>
38+
39+
<div class="flex-grow-1 p-3" [style.order]="drawerPlacement().endsWith('end') ? 1 : 2">
40+
<h6>Main Page Content</h6>
41+
<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>
42+
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
43+
<button class="btn btn-secondary">Clickable Button</button>
44+
<p>Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
45+
<input type="text" class="form-control mt-2" placeholder="Type here..." />
46+
<p class="mt-2">
47+
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
48+
proident.
49+
</p>
50+
</div>
51+
</div>
52+
`,
53+
styles: `
54+
.demo-inline-drawer {
55+
border: 1px solid #dee2e6;
56+
padding: 1rem;
57+
border-radius: 0.375rem;
58+
}
59+
`,
60+
imports: [DrawerComponent, DrawerHeaderDirective, FormsModule],
61+
})
62+
export default class InlineDrawerComponent {
63+
readonly drawerPlacement = signal<DrawerPositions>('inline-start');
64+
}

core-bootstrap/src/scss/drawer.scss

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,9 @@
77

88
display: inline-flex;
99
flex-direction: column;
10+
flex-shrink: 0;
1011
background: #fff;
11-
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.2);
1212
z-index: var(--#{$prefix}drawer-z-index);
13-
1413
--#{$prefix}drawer-size: max-content;
1514
--#{$prefix}drawer-min-size: 0;
1615
--#{$prefix}drawer-max-size: none;
@@ -49,6 +48,7 @@
4948
&.inline-start {
5049
inset-inline-start: 0;
5150
inset-block: 0;
51+
border-inline-end: 1px solid #e2e2e2;
5252
.au-splitter {
5353
inset-block: 0;
5454
inset-inline: auto calc(var(--#{$prefix}drawer-splitter-size) / -2);
@@ -58,6 +58,7 @@
5858
&.inline-end {
5959
inset-inline-end: 0;
6060
inset-block: 0;
61+
border-inline-start: 1px solid #e2e2e2;
6162
.au-splitter {
6263
inset-block: 0;
6364
inset-inline: calc(var(--#{$prefix}drawer-splitter-size) auto / -2);
@@ -68,6 +69,7 @@
6869
&.block-start {
6970
inset-inline: 0;
7071
inset-block: 0;
72+
border-block-end: 1px solid #e2e2e2;
7173
.au-splitter {
7274
inset-block: auto calc(var(--#{$prefix}drawer-splitter-size) / -2);
7375
inset-inline: 0;
@@ -77,13 +79,17 @@
7779
&.block-end {
7880
inset-inline: 0;
7981
inset-block-end: 0;
82+
border-block-start: 1px solid #e2e2e2;
8083
.au-splitter {
8184
inset-block: calc(var(--#{$prefix}drawer-splitter-size) auto / -2);
8285
inset-inline: 0;
8386
}
8487
}
8588

8689
.au-drawer-header {
90+
display: inline-flex;
91+
align-items: center;
92+
width: 100%;
8793
padding: 0.75rem 1rem;
8894
font-weight: 600;
8995
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">

e2e/drawer/drawer.e2e-spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,10 +136,10 @@ test.describe(`Drawer tests`, () => {
136136
.toStrictEqual(
137137
assign(expectedBoundingBox, {
138138
width: 1280,
139-
height: 500,
139+
height: 501,
140140
}),
141141
);
142-
expect(await drawerPO.statePosition()).toStrictEqual(assign(expectedVariables, {'--bs-drawer-size': '500px'}));
142+
expect(await drawerPO.statePosition()).toStrictEqual(assign(expectedVariables, {'--bs-drawer-size': '501px'}));
143143

144144
mousePos = await drawerPO.hoverOnSplitter();
145145
await page.mouse.down();
@@ -149,7 +149,7 @@ test.describe(`Drawer tests`, () => {
149149
await expect
150150
.poll(async () => await drawerPO.locatorRoot.boundingBox())
151151
.toStrictEqual(assign(expectedBoundingBox, {height: expectedBoundingBox.height + 300 - mousePos.y}));
152-
expect(await drawerPO.statePosition()).toStrictEqual(assign(expectedVariables, {'--bs-drawer-size': '300px'}));
152+
expect(await drawerPO.statePosition()).toStrictEqual(assign(expectedVariables, {'--bs-drawer-size': '301px'}));
153153

154154
await drawerPO.locatorBackdrop.click();
155155
await drawerDemoPO.locatorToggleDrawerButton.click();

0 commit comments

Comments
 (0)