Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Timeline): add isItemActive prop #3198

Merged
merged 1 commit into from
May 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/List/test/ListSpec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ describe('List', () => {
await waitFor(() => {
expect(onSortStart).to.have.been.calledOnce;
});

// FIXME-Doma
// This test case didn't cleanup the nodes it creates
});

it('should call onSortMove', async () => {
Expand All @@ -93,6 +96,8 @@ describe('List', () => {
await waitFor(() => {
expect(onSortMove).to.have.been.calledOnce;
});
// FIXME-Doma
// This test case didn't cleanup the nodes it creates
});

it('should call onSortEnd & onSort', async () => {
Expand Down
16 changes: 16 additions & 0 deletions src/Timeline/Timeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,21 @@ export interface TimelineProps extends WithAsProps {

/** Timeline endless **/
endless?: boolean;

/**
* Whether an item is active (with highlighted dot).
*
* @default
* The last item is marked active.
*/
isItemActive?: (index: number, totalItemsCount: number) => boolean;
}

interface TimelineComponent extends RsRefForwardingComponent<'div', TimelineProps> {
Item: typeof TimelineItem;

ACTIVE_FIRST: (index: number, totalItemsCount: number) => boolean;
ACTIVE_LAST: (index: number, totalItemsCount: number) => boolean;
}

const Timeline: TimelineComponent = React.forwardRef((props: TimelineProps, ref) => {
Expand All @@ -28,6 +39,7 @@ const Timeline: TimelineComponent = React.forwardRef((props: TimelineProps, ref)
className,
align = 'left',
endless,
isItemActive = Timeline.ACTIVE_LAST,
...rest
} = props;

Expand All @@ -44,12 +56,16 @@ const Timeline: TimelineComponent = React.forwardRef((props: TimelineProps, ref)
<Component {...rest} ref={ref} className={classes}>
{ReactChildren.mapCloneElement(children, (_child: any, index: number) => ({
last: index + 1 === count,
INTERNAL_active: isItemActive(index, count),
align
}))}
</Component>
);
}) as unknown as TimelineComponent;

Timeline.ACTIVE_FIRST = index => index === 0;
Timeline.ACTIVE_LAST = (index, totalItemsCount) => index === totalItemsCount - 1;

Timeline.Item = TimelineItem;

Timeline.displayName = 'Timeline';
Expand Down
29 changes: 25 additions & 4 deletions src/Timeline/TimelineItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,18 @@ import { useClassNames } from '../utils';
import { WithAsProps, RsRefForwardingComponent } from '../@types/common';

export interface TimelineItemProps extends WithAsProps {
/** Whether the last item */
/**
* Whether the last item
*
* @internal
* This props is supposed to be used only by Timeline component internally
* User should never rely on this prop
*
* @deprecated
* This prop was used to indicate whether an item is the last item so that it gets highlighted.
* Now we can specify whether an item should be highlighted individually.
* Use {@link INTERNAL_active} instead
*/
last?: boolean;

/** Customizing the Timeline item */
Expand All @@ -18,6 +29,13 @@ export interface TimelineItemProps extends WithAsProps {

/** Customized time of timeline **/
time?: React.ReactNode;

/**
* @internal
* This props is supposed to be used only by Timeline component internally
* User should never rely on this prop
*/
INTERNAL_active?: boolean;
}

const TimelineItem: RsRefForwardingComponent<'div', TimelineItemProps> = React.forwardRef(
Expand All @@ -26,15 +44,18 @@ const TimelineItem: RsRefForwardingComponent<'div', TimelineItemProps> = React.f
as: Component = 'li',
children,
classPrefix = 'timeline-item',
last,
last: DEPRECATED_last,
className,
dot,
time,
INTERNAL_active,
...rest
} = props;
const { merge, withClassPrefix, prefix } = useClassNames(classPrefix);
const classes = merge(className, withClassPrefix({ last }));

const classes = merge(
className,
withClassPrefix({ last: DEPRECATED_last, active: INTERNAL_active })
);
return (
<Component {...rest} ref={ref} className={classes}>
<span className={prefix('tail')} />
Expand Down
2 changes: 1 addition & 1 deletion src/Timeline/styles/index.less
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
}
}

&-item-last &-item-dot::before {
&-item-active &-item-dot::before {
background-color: var(--rs-timeline-indicator-active-bg);
}

Expand Down
13 changes: 13 additions & 0 deletions src/Timeline/test/TimelineItemSpec.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react';
import { render } from '@testing-library/react';
import { getDOMNode } from '@test/testUtils';
import { testStandardProps } from '@test/commonCases';
import TimelineItem from '../TimelineItem';
Expand All @@ -9,13 +10,16 @@ describe('TimelineItem', () => {
it('Should output a TimelineItem', () => {
const instance = getDOMNode(<TimelineItem />);
assert.equal(instance.className, 'rs-timeline-item');
// eslint-disable-next-line testing-library/no-node-access
assert.ok(instance.querySelector('.rs-timeline-item-dot'));
// eslint-disable-next-line testing-library/no-node-access
assert.ok(instance.querySelector('.rs-timeline-item-tail'));
});

it('Should render a dot', () => {
const instance = getDOMNode(<TimelineItem dot={<i>test</i>} />);
assert.equal(
// eslint-disable-next-line testing-library/no-node-access
(instance.querySelector('.rs-timeline-item-custom-dot') as HTMLElement).textContent,
'test'
);
Expand All @@ -25,6 +29,7 @@ describe('TimelineItem', () => {
const time = '2019-10-21';
const instance = getDOMNode(<TimelineItem time={time} />);
assert.equal(
// eslint-disable-next-line testing-library/no-node-access
(instance.querySelector('.rs-timeline-item-time') as HTMLElement).textContent,
time
);
Expand All @@ -34,4 +39,12 @@ describe('TimelineItem', () => {
const instance = getDOMNode(<TimelineItem last />);
assert.ok(instance.className.match(/\brs-timeline-item-last\b/));
});

describe('Internal', () => {
it('Should have "rs-timeline-item-active" className when `INTERNAL_active=true`', () => {
const { container } = render(<TimelineItem INTERNAL_active />);

expect(container.firstChild).to.have.class('rs-timeline-item-active');
});
});
});
48 changes: 48 additions & 0 deletions src/Timeline/test/TimelineSpec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react';
import { getDOMNode } from '@test/testUtils';
import { testStandardProps } from '@test/commonCases';
import Timeline from '../Timeline';
import { render, screen } from '@testing-library/react';

describe('Timeline', () => {
testStandardProps(<Timeline />);
Expand Down Expand Up @@ -34,4 +35,51 @@ describe('Timeline', () => {
const instance = getDOMNode(<Timeline align="left" />);
assert.ok(instance.className.match(/\brs-timeline-align-left\b/));
});

describe('Active item', () => {
it('Should mark the last item as active by default', () => {
render(
<Timeline>
<Timeline.Item>First item</Timeline.Item>
<Timeline.Item>Second item</Timeline.Item>
<Timeline.Item data-testid="last-item">Third item</Timeline.Item>
</Timeline>
);
expect(screen.getByTestId('last-item')).to.have.class('rs-timeline-item-active');
});

it('Should mark the item indicated by `isItemActive` as active', () => {
render(
<Timeline isItemActive={index => index === 1}>
<Timeline.Item>First item</Timeline.Item>
<Timeline.Item data-testid="second-item">Second item</Timeline.Item>
<Timeline.Item>Third item</Timeline.Item>
</Timeline>
);

expect(screen.getByTestId('second-item')).to.have.class('rs-timeline-item-active');
});

it('Should mark the first item as active with `Timeline.ACTIVE_FIRST`', () => {
render(
<Timeline isItemActive={Timeline.ACTIVE_FIRST}>
<Timeline.Item data-testid="first-item">First item</Timeline.Item>
<Timeline.Item>Second item</Timeline.Item>
<Timeline.Item>Third item</Timeline.Item>
</Timeline>
);
expect(screen.getByTestId('first-item')).to.have.class('rs-timeline-item-active');
});

it('Should mark the last item as active with `Timeline.ACTIVE_LAST`', () => {
render(
<Timeline isItemActive={Timeline.ACTIVE_LAST}>
<Timeline.Item>First item</Timeline.Item>
<Timeline.Item>Second item</Timeline.Item>
<Timeline.Item data-testid="last-item">Third item</Timeline.Item>
</Timeline>
);
expect(screen.getByTestId('last-item')).to.have.class('rs-timeline-item-active');
});
});
});
Loading