Skip to content

Commit

Permalink
Merge pull request #227 from Mezzanine-UI/feat/react-anchor
Browse files Browse the repository at this point in the history
'Anchor' component implemented
  • Loading branch information
travor20814 authored Aug 16, 2024
2 parents 6ddb58a + 6cbda2f commit 9e295d3
Show file tree
Hide file tree
Showing 22 changed files with 318 additions and 30 deletions.
1 change: 1 addition & 0 deletions .storybook/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const preview: Preview = {
'Data Entry',
'Data Display',
'Feedback',
'Others',
],
},
},
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@
"@storybook/react": "^8.2.8",
"@storybook/react-webpack5": "^8.2.8",
"@storybook/theming": "^8.2.8",
"@testing-library/dom": "^8.19.0",
"@types/jest": "^29.5.7",
"@testing-library/dom": "^10.4.0",
"@types/jest": "^29.5.12",
"@types/react-refresh": "^0",
"@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.18.0",
Expand Down Expand Up @@ -105,7 +105,7 @@
"stylelint-config-standard": "^36.0.1",
"stylelint-order": "^6.0.4",
"stylelint-scss": "^6.5.0",
"ts-jest": "^29.1.1",
"ts-jest": "^29.2.4",
"typescript": "^5.5.4",
"webpack": "^5.93.0"
},
Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/_styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,7 @@
@include _load-styles($options, popconfirm);
@include _load-styles($options, progress);
@include _load-styles($options, skeleton);

// Others
@include _load-styles($options, anchor);
}
48 changes: 48 additions & 0 deletions packages/core/src/anchor/_anchor-styles.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
@use '~@mezzanine-ui/system/palette';
@use '~@mezzanine-ui/system/transition';
@use '../button/utils' as btn-utils;
@use './anchor' as *;

$bar-width: 1px !default;
$anchor-left-padding: 12px !default;

.#{$prefix} {
width: 100%;
display: grid;
grid-template-columns: 1fr;
gap: 8px;
position: relative;

&__bar {
position: absolute;
left: 0;
top: 0;
z-index: 0;
width: $bar-width;
height: 100%;
background-color: palette.color(border);
}

&__anchor {
@include btn-utils.reset();

display: inline-grid;
position: relative;
z-index: 1;
width: 100%;
border-left: $bar-width solid palette.color(border);
padding-left: $anchor-left-padding;
text-align: left;
color: palette.color(action-inactive);
transition: transition.standard(border-left), transition.standard(color);

&:hover {
color: palette.color(primary);
}

&--active {
color: palette.color(primary);
border-left: $bar-width solid palette.color(primary);
}
}
}
1 change: 1 addition & 0 deletions packages/core/src/anchor/_anchor.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
$prefix: mzn-anchor;
1 change: 1 addition & 0 deletions packages/core/src/anchor/_index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@forward './anchor';
8 changes: 8 additions & 0 deletions packages/core/src/anchor/anchor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const anchorPrefix = 'mzn-anchor';

export const anchorClasses = {
host: anchorPrefix,
bar: `${anchorPrefix}__bar`,
anchor: `${anchorPrefix}__anchor`,
anchorActive: `${anchorPrefix}__anchor--active`,
} as const;
1 change: 1 addition & 0 deletions packages/core/src/anchor/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './anchor';
11 changes: 6 additions & 5 deletions packages/react/__test-utils__/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ export {
} from 'react-test-renderer';
export * from '@testing-library/react';
export {
act as actHook,
renderHook,
cleanup as cleanupHook,
} from '@testing-library/react-hooks';

export type RenderResult<Q extends Queries = typeof queries> = CoreRenderResult<Q> & {
getHostHTMLElement<E extends Element = HTMLElement>(): E;
};
export type RenderResult<Q extends Queries = typeof queries> =
CoreRenderResult<Q> & {
getHostHTMLElement<E extends Element = HTMLElement>(): E;
};

export function render(
ui: ReactElement,
Expand All @@ -35,7 +35,8 @@ export function render(ui: ReactElement, options?: RenderOptions): any {
const coreResult = coreRender(ui, options);
const result: RenderResult = {
...coreResult,
getHostHTMLElement: <E extends Element = HTMLElement>() => coreResult.container.firstElementChild as E,
getHostHTMLElement: <E extends Element = HTMLElement>() =>
coreResult.container.firstElementChild as E,
};

return result;
Expand Down
13 changes: 13 additions & 0 deletions packages/react/src/Anchor/Anchor.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Meta, ArgTypes, Description } from '@storybook/blocks';
import Anchor from '.';
import * as AnchorStories from './Anchor.stories';

<Meta of={AnchorStories} />

# Anchor

<Description of={Anchor} />

[Source Code](https://github.com/Mezzanine-UI/mezzanine/blob/main/packages/react/src/Anchor/Anchor.tsx)

<ArgTypes of={Anchor} />
29 changes: 29 additions & 0 deletions packages/react/src/Anchor/Anchor.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { cleanup, fireEvent, render } from '../../__test-utils__';
import {
describeForwardRefToHTMLElement,
describeHostElementClassNameAppendable,
} from '../../__test-utils__/common';
import Anchor from '.';

const mockList = [
{
id: 'foo',
name: 'foo',
},
{
id: 'bar',
name: 'bar',
},
];

describe('<Anchor />', () => {
afterEach(cleanup);

describeForwardRefToHTMLElement(HTMLDivElement, (ref) =>
render(<Anchor ref={ref} list={mockList} />),
);

describeHostElementClassNameAppendable('foo', (className) =>
render(<Anchor className={className} list={mockList} />),
);
});
67 changes: 67 additions & 0 deletions packages/react/src/Anchor/Anchor.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { Meta } from '@storybook/react';
import Anchor from './Anchor';
import { useState } from 'react';
import Typography from '../Typography';

export default {
title: 'Others/Anchor',
} as Meta;

const anchorList = [
{
id: 'Anchor1',
name: 'Anchor1',
},
{
id: 'Anchor2',
name: 'Anchor2',
},
{
id: 'lorem ipsum lorem ipsum lorem ipsum',
name: 'lorem ipsum lorem ipsum lorem ipsum',
},
{
id: 'Anchor4',
name: 'Anchor4',
},
{
id: 'Anchor5',
name: 'Anchor5',
},
];

export const Basics = () => {
const [currentActiveId, setCurrentActiveId] = useState<string>(
anchorList[0].id,
);

return (
<div
style={{
display: 'grid',
gridAutoFlow: 'row',
gap: '12px',
}}
>
<Typography variant="h4" color="secondary">
Non Ellipsis
</Typography>
<Anchor
activeAnchorId={currentActiveId}
list={anchorList}
maxWidth={180}
onClick={(nextAnchorId) => setCurrentActiveId(nextAnchorId)}
/>
<Typography variant="h4" color="secondary">
Ellipsis
</Typography>
<Anchor
activeAnchorId={currentActiveId}
ellipsis
list={anchorList}
maxWidth={200}
onClick={(nextAnchorId) => setCurrentActiveId(nextAnchorId)}
/>
</div>
);
};
83 changes: 83 additions & 0 deletions packages/react/src/Anchor/Anchor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { forwardRef } from 'react';
import { anchorClasses as classes } from '@mezzanine-ui/core/anchor';
import { cx } from '../utils/cx';
import { NativeElementPropsWithoutKeyAndRef } from '../utils/jsx-types';
import Typography from '../Typography';

export interface AnchorProps
extends Omit<NativeElementPropsWithoutKeyAndRef<'div'>, 'onClick'> {
/** The current active anchor ID */
activeAnchorId?: string;
/** Whether apply ellipsis or not
* @default false
*/
ellipsis?: boolean;
/**
* Anchor list.
*/
list: {
id: string;
name: string;
}[];
/**
* The maximum width for anchor container. This might be useful when you need to set `ellipsis: true`.
*/
maxWidth?: number;
/**
* Trigger when user click on any anchor.
*/
onClick?: (nextAnchorId: string) => void;
}

/**
* The react component for `mezzanine` anchor.
* This component should always be full width of its parent.
*/
const Anchor = forwardRef<HTMLDivElement, AnchorProps>(
function Anchor(props, ref) {
const {
activeAnchorId,
className,
children,
ellipsis = false,
list,
maxWidth,
onClick,
style,
...rest
} = props;

const resolvedStyle = {
...(style || {}),
...(maxWidth ? { maxWidth: `${maxWidth}px` } : {}),
};

return (
<div
ref={ref}
className={cx(classes.host, className)}
style={resolvedStyle}
{...rest}
>
<div className={classes.bar} />
{list.map((anchor) => (
<button
key={anchor.id}
type="button"
onClick={() => onClick?.(anchor.id)}
className={cx(
classes.anchor,
activeAnchorId === anchor.id && classes.anchorActive,
)}
>
<Typography variant="input3" color="inherit" ellipsis={ellipsis}>
{anchor.name}
</Typography>
</button>
))}
</div>
);
},
);

export default Anchor;
1 change: 1 addition & 0 deletions packages/react/src/Anchor/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { AnchorProps, default } from './Anchor';
3 changes: 1 addition & 2 deletions packages/react/src/DatePicker/DatePickerCalendar.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* global document */
import { createRef } from 'react';
import { createRef, act } from 'react';
import moment from 'moment';
import {
CalendarMode,
Expand All @@ -8,7 +8,6 @@ import {
getYearRange,
} from '@mezzanine-ui/core/calendar';
import CalendarMethodsMoment from '@mezzanine-ui/core/calendarMethodsMoment';
import { act } from 'react-dom/test-utils';
import { cleanup, fireEvent, render } from '../../__test-utils__';
import { describeForwardRefToHTMLElement } from '../../__test-utils__/common';
import { CalendarConfigProvider } from '../Calendar';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/* global document */
import moment from 'moment';
import { act } from 'react';
import { CalendarMode, DateType } from '@mezzanine-ui/core/calendar';
import CalendarMethodsMoment from '@mezzanine-ui/core/calendarMethodsMoment';
import { act } from 'react-dom/test-utils';
import { cleanup, fireEvent, render } from '../../__test-utils__';
import { describeForwardRefToHTMLElement } from '../../__test-utils__/common';
import { CalendarConfigProvider } from '../Calendar';
Expand Down
3 changes: 1 addition & 2 deletions packages/react/src/Notifier/createNotifier.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { act } from '@testing-library/react';
import { Key } from 'react';
import { Key, act } from 'react';
import { cleanup, cleanupHook } from '../../__test-utils__';
import { createNotifier, Notifier, NotifierData, RenderNotifier } from '.';

Expand Down
5 changes: 5 additions & 0 deletions packages/react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,11 @@ export {
} from './Progress';
export { SkeletonProps, default as Skeleton } from './Skeleton';

/**
* Others
*/
export { AnchorProps, default as Anchor } from './Anchor';

/**
* Utility
*/
Expand Down
5 changes: 2 additions & 3 deletions packages/react/src/utils/scroll-lock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@ import { getScrollbarWidth } from './get-scrollbar-width';
export function lockBodyScroll() {
const { scrollY } = window;

document.body.style.position = 'fixed';
document.body.style.top = `-${scrollY}px`;

// Calculate scroll bar width and use padding-right to remain layout width.
const scrollbarWidth = getScrollbarWidth();

document.body.style.position = 'fixed';
document.body.style.top = `-${scrollY}px`;
document.body.style.paddingRight = `${scrollbarWidth}px`;
document.body.style.overflow = 'hidden';
}
Expand Down
1 change: 0 additions & 1 deletion packages/react/tsconfig.dev.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"composite": true,
"importHelpers": false,
"noEmitHelpers": false
},
Expand Down
Loading

0 comments on commit 9e295d3

Please sign in to comment.