Skip to content

Commit

Permalink
Anchored region (#530)
Browse files Browse the repository at this point in the history
# Pull Request

## 🀨 Rationale

Create nimble-anchored-region component.

## πŸ‘©β€πŸ’» Implementation

Created a light-weight wrapper around the FAST anchored-region.

## πŸ§ͺ Testing

- Added unit tests for the nimble-anchored-region being returned from `tagFor(FoundationAnchoredRegion)` and the element being able to be constructed
- Added matrix tests for the component
    - Note: for technical reasons and because nothing in the anchored region is theme aware, I did not create matrix stories for each theme
- Added storybook story for the component

## βœ… Checklist

- [x] I have updated the project documentation to reflect my changes or determined no changes are needed.
  • Loading branch information
mollykreis authored May 4, 2022
1 parent 1bd7275 commit 653341a
Show file tree
Hide file tree
Showing 7 changed files with 278 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "Create nimble-anchored-region",
"packageName": "@ni/nimble-components",
"email": "[email protected]",
"dependentChangeType": "patch"
}
1 change: 1 addition & 0 deletions packages/nimble-components/src/all-components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* that are required instead of leveraging this file.
*/

import './anchored-region';
import './breadcrumb';
import './breadcrumb-item';
import './button';
Expand Down
36 changes: 36 additions & 0 deletions packages/nimble-components/src/anchored-region/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import {
DesignSystem,
AnchoredRegion as FoundationAnchoredRegion,
anchoredRegionTemplate as template
} from '@microsoft/fast-foundation';
import { styles } from './styles';

declare global {
interface HTMLElementTagNameMap {
'nimble-anchored-region': AnchoredRegion;
}
}

// When the anchor element changes position on the page, it is the client's responsibility to update the position
// of the anchored region by calling update() on the anchored region.
//
// When the anchor element is recreated on the page, it is the client's responsibility to reset the reference the
// anchored region has to the anchor element. This can be done by either recreating the anchor element with a new
// ID that is also set as the \`anchor\` attribute on the anchored region or by explicitly setting the value of
// anchorElement on the anchored region to the new anchor element.

/**
* A nimble-styled anchored region control.
*/
export class AnchoredRegion extends FoundationAnchoredRegion {}

const nimbleAnchoredRegion = AnchoredRegion.compose({
baseName: 'anchored-region',
baseClass: FoundationAnchoredRegion,
template,
styles
});

DesignSystem.getOrCreate()
.withPrefix('nimble')
.register(nimbleAnchoredRegion());
9 changes: 9 additions & 0 deletions packages/nimble-components/src/anchored-region/styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { css } from '@microsoft/fast-element';

export const styles = css`
:host {
contain: layout;
display: block;
z-index: 1000;
}
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import type { Meta, Story } from '@storybook/html';
import { html, ViewTemplate } from '@microsoft/fast-element';
import {
createMatrix,
sharedMatrixParameters
} from '../../utilities/tests/matrix';
import { createStory } from '../../utilities/tests/storybook';
import { hiddenWrapper } from '../../utilities/tests/hidden';
import '../../all-components';
import {
bodyFont,
bodyFontColor,
borderHoverColor,
applicationBackgroundColor
} from '../../theme-provider/design-tokens';

const metadata: Meta = {
title: 'Tests/Anchored Region',
parameters: {
...sharedMatrixParameters()
}
};

export default metadata;

const horizontalPositionStates = [
['Start', 'start'],
['End', 'end'],
['Left', 'left'],
['Right', 'right'],
['Center', 'center']
] as const;
type HorizontalPositionState = typeof horizontalPositionStates[number];

const verticalPositionStates = [
['Top', 'top'],
['Bottom', 'bottom'],
['Center', 'center']
] as const;
type VerticalPositionState = typeof verticalPositionStates[number];

const component = (
[horizontalPositionName, horizontalPosition]: HorizontalPositionState,
[verticalPositionName, verticalPosition]: VerticalPositionState
): ViewTemplate => html`<style>
.container {
display: inline-flex;
margin: 55px;
height: 75px;
width: 75px;
}
.anchor {
top: 25px;
left: 25px;
width: 75px;
height: 75px;
font: var(${bodyFont.cssCustomProperty});
color: var(${bodyFontColor.cssCustomProperty});
border: 1px solid var(${borderHoverColor.cssCustomProperty});
background: var(${applicationBackgroundColor.cssCustomProperty});
}
.anchoredRegion {
width: 50px;
height: 50px;
font: var(${bodyFont.cssCustomProperty});
color: var(${bodyFontColor.cssCustomProperty});
border: 1px solid var(${borderHoverColor.cssCustomProperty});
background: var(${applicationBackgroundColor.cssCustomProperty});
}
</style>
<div class="container">
<div
id="${() => `${verticalPosition}_${horizontalPosition}`}"
class="anchor"
>
Anchor element
</div>
<nimble-anchored-region
anchor="${() => `${verticalPosition}_${horizontalPosition}`}"
fixed-placement="true"
auto-update-mode="auto"
vertical-default-position="${() => verticalPosition}"
vertical-positioning-mode="locktodefault"
horizontal-default-position="${() => horizontalPosition}"
horizontal-positioning-mode="locktodefault"
>
<div class="anchoredRegion">
${horizontalPositionName} ${verticalPositionName}
</div>
</nimble-anchored-region>
<div></div>
</div>`;

export const anchoredRegionThemeMatrix: Story = createStory(
createMatrix(component, [horizontalPositionStates, verticalPositionStates])
);

export const hiddenAnchoredRegion: Story = createStory(
hiddenWrapper(
html`<nimble-anchored-region hidden>Hidden Anchored Region</nimble-anchored-regionx>`
)
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import {
DesignSystem,
AnchoredRegion as FoundationAnchoredRegion
} from '@microsoft/fast-foundation';
import { AnchoredRegion } from '..';

describe('Anchored Region', () => {
it('should have its tag returned by tagFor(FoundationAnchoredRegion)', () => {
expect(DesignSystem.tagFor(FoundationAnchoredRegion)).toBe(
'nimble-anchored-region'
);
});

it('can construct an element instance', () => {
expect(document.createElement('nimble-anchored-region')).toBeInstanceOf(
AnchoredRegion
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import type { Meta, StoryObj } from '@storybook/html';
import { html } from '@microsoft/fast-element';
import { createUserSelectedThemeStory } from '../../utilities/tests/storybook';
import '../../all-components';
import {
applicationBackgroundColor,
bodyFont,
bodyFontColor,
borderHoverColor
} from '../../theme-provider/design-tokens';

interface AnchoredRegionArgs {
horizontalPosition: string;
verticalPosition: string;
}

const overviewText = `The anchored region should not generally be used directly by nimble clients. Instead, it is intended to be used within other nimble
components where one part of the component needs to be dynamically positioned based on another element within the component. For example, the popup menu
within a menu button or a tooltip.
An anchored region is a container component which enables authors to create layouts where the contents of the anchored region can be positioned relative
to another "anchor" element. Additionally, the anchored region can react to the available space between the anchor and a parent
["viewport"](https://developer.mozilla.org/en-US/docs/Glossary/viewport) element such that the region is placed on the side of the anchor with the most
available space, or even resize itself based on that space.`;

const metadata: Meta<AnchoredRegionArgs> = {
title: 'Tests/Anchored Region',
parameters: {
docs: {
description: {
component: overviewText
}
}
},
// prettier-ignore
render: createUserSelectedThemeStory(html<AnchoredRegionArgs>`
<style>
.container {
height: 300px;
}
.anchor {
position: absolute;
top: 100px;
left: 300px;
width: 150px;
height: 150px;
font: var(${bodyFont.cssCustomProperty});
color: var(${bodyFontColor.cssCustomProperty});
border: 2px solid var(${borderHoverColor.cssCustomProperty});
background: var(${applicationBackgroundColor.cssCustomProperty});
}
.anchoredRegion {
width: 75px;
height: 75px;
font: var(${bodyFont.cssCustomProperty});
color: var(${bodyFontColor.cssCustomProperty});
border: 2px solid var(${borderHoverColor.cssCustomProperty});
background: var(${applicationBackgroundColor.cssCustomProperty});
}
</style>
<div class="container">
<div id="${x => `${x.verticalPosition}_${x.horizontalPosition}`}" class="anchor">
Anchor element
</div>
<nimble-anchored-region
anchor="${x => `${x.verticalPosition}_${x.horizontalPosition}`}"
fixed-placement="true"
auto-update-mode="auto"
vertical-default-position="${x => x.verticalPosition}"
vertical-positioning-mode="${x => (x.verticalPosition === 'unset' ? 'dynamic' : 'locktodefault')}"
horizontal-default-position="${x => x.horizontalPosition}"
horizontal-positioning-mode="${x => (x.horizontalPosition === 'unset' ? 'dynamic' : 'locktodefault')}"
>
<div class="anchoredRegion">
Anchored region
</div>
</nimble-anchored-region>
</div>
`),
argTypes: {
horizontalPosition: {
options: ['start', 'end', 'left', 'right', 'center', 'unset'],
control: { type: 'select' }
},
verticalPosition: {
options: ['top', 'bottom', 'center', 'unset'],
control: { type: 'select' }
}
},
args: {}
};

export default metadata;

export const anchoredRegion: StoryObj<AnchoredRegionArgs> = {
args: {
horizontalPosition: 'start',
verticalPosition: 'top'
}
};

0 comments on commit 653341a

Please sign in to comment.