@transferwise/components 0.0.0-experimental-693e956 → 0.0.0-experimental-d729abf
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/i18n/en.json +1 -0
- package/build/index.js +2320 -2234
- package/build/index.js.map +1 -1
- package/build/index.mjs +2322 -2237
- package/build/index.mjs.map +1 -1
- package/build/main.css +31 -10
- package/build/styles/main.css +31 -10
- package/build/styles/selectOption/SelectOption.css +31 -0
- package/build/styles/uploadInput/UploadInput.css +0 -10
- package/build/types/index.d.ts +2 -0
- package/build/types/index.d.ts.map +1 -1
- package/build/types/selectOption/SelectOption.d.ts +20 -0
- package/build/types/selectOption/SelectOption.d.ts.map +1 -0
- package/build/types/selectOption/SelectOption.messages.d.ts +8 -0
- package/build/types/selectOption/SelectOption.messages.d.ts.map +1 -0
- package/build/types/selectOption/index.d.ts +3 -0
- package/build/types/selectOption/index.d.ts.map +1 -0
- package/build/types/slidingPanel/SlidingPanel.d.ts +1 -0
- package/build/types/slidingPanel/SlidingPanel.d.ts.map +1 -1
- package/build/types/sticky/Sticky.d.ts +31 -3
- package/build/types/sticky/Sticky.d.ts.map +1 -1
- package/build/types/uploadInput/types.d.ts +1 -9
- package/build/types/uploadInput/types.d.ts.map +1 -1
- package/build/types/uploadInput/uploadItem/UploadItem.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/i18n/en.json +1 -0
- package/src/index.ts +2 -0
- package/src/main.css +31 -10
- package/src/main.less +1 -0
- package/src/selectOption/SelectOption.css +31 -0
- package/src/selectOption/SelectOption.less +27 -0
- package/src/selectOption/SelectOption.messages.ts +8 -0
- package/src/selectOption/SelectOption.story.tsx +167 -0
- package/src/selectOption/SelectOption.tsx +134 -0
- package/src/selectOption/index.ts +2 -0
- package/src/slidingPanel/SlidingPanel.spec.js +2 -1
- package/src/slidingPanel/SlidingPanel.tsx +3 -0
- package/src/sticky/Sticky.spec.tsx +71 -0
- package/src/sticky/Sticky.story.tsx +46 -13
- package/src/sticky/Sticky.tsx +48 -4
- package/src/uploadInput/UploadInput.css +0 -10
- package/src/uploadInput/UploadInput.less +0 -11
- package/src/uploadInput/UploadInput.story.tsx +7 -50
- package/src/uploadInput/types.ts +1 -11
- package/src/uploadInput/uploadItem/UploadItem.tsx +6 -25
- package/src/sticky/Sticky.spec.js +0 -34
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import type { StoryObj } from '@storybook/react';
|
|
2
|
+
import { Flag } from '@wise/art';
|
|
3
|
+
import SelectOption, { SelectOptionValue, SelectOptiopsGroup } from './SelectOption';
|
|
4
|
+
import { Bank, BankTransfer, Card, Plus } from '@transferwise/icons';
|
|
5
|
+
import { Field } from '../field/Field';
|
|
6
|
+
import { lorem10 } from '../test-utils';
|
|
7
|
+
|
|
8
|
+
export default {
|
|
9
|
+
title: 'Option/SelectOption',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
type Story = StoryObj<typeof SelectOption>;
|
|
13
|
+
|
|
14
|
+
type CustomData = { data?: string };
|
|
15
|
+
|
|
16
|
+
const cardPaymentMethod: SelectOptionValue<CustomData> = {
|
|
17
|
+
media: <Card />,
|
|
18
|
+
title: 'Credit card',
|
|
19
|
+
content: (
|
|
20
|
+
<>
|
|
21
|
+
<div>Transfer the money to Wise using your bank account.</div>
|
|
22
|
+
<div>0.32 GBP in fees, should arrive in seconds</div>
|
|
23
|
+
</>
|
|
24
|
+
),
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const recentPaymentMethods: SelectOptionValue<CustomData>[] = [cardPaymentMethod];
|
|
28
|
+
|
|
29
|
+
const allOtherPaymentMethods: SelectOptionValue<CustomData>[] = [
|
|
30
|
+
{
|
|
31
|
+
data: lorem10,
|
|
32
|
+
media: <Flag code="gbp" />,
|
|
33
|
+
title: 'Wise GBP balance',
|
|
34
|
+
content: (
|
|
35
|
+
<>
|
|
36
|
+
<span>300 GBP available</span>
|
|
37
|
+
<span>0 GBP in fees, should arrive in seconds</span>
|
|
38
|
+
</>
|
|
39
|
+
),
|
|
40
|
+
},
|
|
41
|
+
cardPaymentMethod,
|
|
42
|
+
{
|
|
43
|
+
media: <Card />,
|
|
44
|
+
title: 'Debit card',
|
|
45
|
+
content: (
|
|
46
|
+
<>
|
|
47
|
+
<span>Send from your Visa or Mastercard.</span>
|
|
48
|
+
<span>0.74 GBP in fees, should arrive in seconds</span>
|
|
49
|
+
</>
|
|
50
|
+
),
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
media: <Bank />,
|
|
54
|
+
title: 'Swift Transfer',
|
|
55
|
+
content: (
|
|
56
|
+
<>
|
|
57
|
+
<span>Send money internationally. Your bank will charge you extra fees.</span>
|
|
58
|
+
<span>0.32 GBP in fees, should arrive by Thursday</span>
|
|
59
|
+
</>
|
|
60
|
+
),
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
media: <BankTransfer />,
|
|
64
|
+
title: 'Bank Transfer',
|
|
65
|
+
content: (
|
|
66
|
+
<>
|
|
67
|
+
<span>Transfer the money to Wise using your bank account.</span>
|
|
68
|
+
<span>0.32 GBP in fees, should arrive in seconds</span>
|
|
69
|
+
</>
|
|
70
|
+
),
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
media: <BankTransfer />,
|
|
74
|
+
title: 'Withdraw from your U.S. bank account (ACH)',
|
|
75
|
+
content: (
|
|
76
|
+
<>
|
|
77
|
+
<span>1.63 USD in total fees.</span>
|
|
78
|
+
<span>
|
|
79
|
+
Pay with ACH using the bank account you’ve connected to Wise. Should arrive in seconds.
|
|
80
|
+
</span>
|
|
81
|
+
</>
|
|
82
|
+
),
|
|
83
|
+
},
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
const optionsGroup: SelectOptiopsGroup<CustomData>[] = [
|
|
87
|
+
{
|
|
88
|
+
title: 'Recently used',
|
|
89
|
+
options: recentPaymentMethods,
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
// title: 'Payment methods',
|
|
93
|
+
options: allOtherPaymentMethods,
|
|
94
|
+
},
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
export const Basic: Story = {
|
|
98
|
+
render: (args) => {
|
|
99
|
+
function onChange(value: SelectOptionValue<CustomData>): void {
|
|
100
|
+
console.log(value);
|
|
101
|
+
}
|
|
102
|
+
return (
|
|
103
|
+
<div className="row">
|
|
104
|
+
<div className="col-md-8 col-md-offset-2">
|
|
105
|
+
<Field label={<>Payment method</>}>
|
|
106
|
+
<SelectOption<CustomData>
|
|
107
|
+
media={<Plus size={24} />}
|
|
108
|
+
options={optionsGroup}
|
|
109
|
+
optionsTitle="Payment methods"
|
|
110
|
+
title="No method selected"
|
|
111
|
+
action={{ label: 'Select' }}
|
|
112
|
+
onChange={onChange}
|
|
113
|
+
/>
|
|
114
|
+
</Field>
|
|
115
|
+
</div>
|
|
116
|
+
|
|
117
|
+
<div className="col-md-6 col-md-offset-3">
|
|
118
|
+
<Field id="test-test" label={<>Payment method</>}>
|
|
119
|
+
<SelectOption<CustomData>
|
|
120
|
+
media={<Plus size={24} />}
|
|
121
|
+
title="No method selected"
|
|
122
|
+
options={optionsGroup}
|
|
123
|
+
onChange={onChange}
|
|
124
|
+
/>
|
|
125
|
+
</Field>
|
|
126
|
+
</div>
|
|
127
|
+
|
|
128
|
+
<div className="col-md-6 col-md-offset-3">
|
|
129
|
+
<Field id="test-test" label={<>Payment method</>} hint="Nice hint message">
|
|
130
|
+
<SelectOption<CustomData>
|
|
131
|
+
media={<Plus size={24} />}
|
|
132
|
+
title="No method selected"
|
|
133
|
+
optionsTitle="Payment method"
|
|
134
|
+
options={optionsGroup}
|
|
135
|
+
onChange={onChange}
|
|
136
|
+
/>
|
|
137
|
+
</Field>
|
|
138
|
+
</div>
|
|
139
|
+
|
|
140
|
+
<div className="col-md-6 col-md-offset-3">
|
|
141
|
+
<Field id="test-test" label={<>Payment method</>} error="Validation message">
|
|
142
|
+
<SelectOption<CustomData>
|
|
143
|
+
media={<Plus size={24} />}
|
|
144
|
+
title="No method selected"
|
|
145
|
+
optionsTitle="Payment method"
|
|
146
|
+
options={optionsGroup}
|
|
147
|
+
onChange={onChange}
|
|
148
|
+
/>
|
|
149
|
+
</Field>
|
|
150
|
+
</div>
|
|
151
|
+
|
|
152
|
+
<div className="col-md-6 col-md-offset-3">
|
|
153
|
+
<Field label={<>Payment method</>} error="Validation message">
|
|
154
|
+
<SelectOption<CustomData>
|
|
155
|
+
disabled
|
|
156
|
+
media={<Plus size={24} />}
|
|
157
|
+
title="No method selected"
|
|
158
|
+
optionsTitle="Payment method"
|
|
159
|
+
options={optionsGroup}
|
|
160
|
+
onChange={onChange}
|
|
161
|
+
/>
|
|
162
|
+
</Field>
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
);
|
|
166
|
+
},
|
|
167
|
+
};
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { useRef, useState, ReactNode } from 'react';
|
|
2
|
+
import ActionButton, { ActionButtonProps } from '../actionButton';
|
|
3
|
+
import Chevron from '../chevron';
|
|
4
|
+
import classNames from 'classnames';
|
|
5
|
+
import Option from '../common/Option';
|
|
6
|
+
import type { OptionProps } from '../common/Option/Option';
|
|
7
|
+
import Panel from '../common/panel/Panel';
|
|
8
|
+
import { Position } from '../common';
|
|
9
|
+
import Section from '../section';
|
|
10
|
+
import Header from '../header';
|
|
11
|
+
import { useLayout } from '../common/hooks';
|
|
12
|
+
import BottomSheet from '../common/bottomSheet';
|
|
13
|
+
import { HeaderProps } from '../header/Header';
|
|
14
|
+
import NavigationOption from '../navigationOption';
|
|
15
|
+
import NavigationOptionsList from '../navigationOptionsList';
|
|
16
|
+
import { useInputAttributes } from '../inputs/contexts';
|
|
17
|
+
import messages from './SelectOption.messages';
|
|
18
|
+
import { useIntl } from 'react-intl';
|
|
19
|
+
|
|
20
|
+
export type SelectOptiopsGroup<T = unknown> = {
|
|
21
|
+
title?: HeaderProps['title'];
|
|
22
|
+
options: SelectOptionValue<T>[];
|
|
23
|
+
};
|
|
24
|
+
export type SelectOptionValue<T = unknown> = T & Pick<OptionProps, 'media' | 'title' | 'content'>;
|
|
25
|
+
|
|
26
|
+
export type SelectOptionProps<T> = {
|
|
27
|
+
onChange: (selected: SelectOptionValue<T>) => void;
|
|
28
|
+
selected?: SelectOptionValue<T>;
|
|
29
|
+
optionsTitle?: HeaderProps['title'];
|
|
30
|
+
options: SelectOptiopsGroup<T>[];
|
|
31
|
+
action?: {
|
|
32
|
+
label?: ActionButtonProps['children'];
|
|
33
|
+
};
|
|
34
|
+
} & Omit<OptionProps, 'as' | 'onClick' | 'onChange' | 'showMediaAtAllSizes' | 'decision'>;
|
|
35
|
+
|
|
36
|
+
export default function SelectOption<T = unknown>({
|
|
37
|
+
selected: selectedValueProp = undefined,
|
|
38
|
+
optionsTitle,
|
|
39
|
+
options,
|
|
40
|
+
onChange,
|
|
41
|
+
action,
|
|
42
|
+
...props
|
|
43
|
+
}: SelectOptionProps<T>) {
|
|
44
|
+
const intl = useIntl();
|
|
45
|
+
const rootRef = useRef(null);
|
|
46
|
+
const [selected, setSelected] = useState(selectedValueProp);
|
|
47
|
+
const [showOptions, setShowOptions] = useState(false);
|
|
48
|
+
const inputAttributes = useInputAttributes();
|
|
49
|
+
function handleOnClick(showOptionsStatus: boolean) {
|
|
50
|
+
return () => {
|
|
51
|
+
setShowOptions(showOptionsStatus);
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function handleOnChange(data: SelectOptionValue<T>) {
|
|
55
|
+
return () => {
|
|
56
|
+
setShowOptions(false);
|
|
57
|
+
setSelected(data);
|
|
58
|
+
onChange(data);
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const isSelected = selected !== undefined;
|
|
62
|
+
const { isMobile } = useLayout();
|
|
63
|
+
function getOptionsContent(isMobileView = false) {
|
|
64
|
+
return (
|
|
65
|
+
<div className={classNames('np-select-option-list')}>
|
|
66
|
+
{options.map((optionsGroup) => (
|
|
67
|
+
// eslint-disable-next-line react/jsx-key
|
|
68
|
+
<Section className={classNames('np-select-option-group', { 'p-a-2': !isMobileView })}>
|
|
69
|
+
{optionsGroup.title && <Header title={optionsGroup.title} />}
|
|
70
|
+
<NavigationOptionsList>
|
|
71
|
+
{optionsGroup.options.map((option) => (
|
|
72
|
+
// eslint-disable-next-line react/jsx-key
|
|
73
|
+
<NavigationOption
|
|
74
|
+
isContainerAligned={isMobileView}
|
|
75
|
+
showMediaCircle
|
|
76
|
+
showMediaAtAllSizes
|
|
77
|
+
media={option.media}
|
|
78
|
+
title={option.title}
|
|
79
|
+
content={option.content}
|
|
80
|
+
onClick={handleOnChange(option)}
|
|
81
|
+
/>
|
|
82
|
+
))}
|
|
83
|
+
</NavigationOptionsList>
|
|
84
|
+
</Section>
|
|
85
|
+
))}
|
|
86
|
+
</div>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
return (
|
|
90
|
+
<>
|
|
91
|
+
<Option
|
|
92
|
+
ref={rootRef}
|
|
93
|
+
{...props}
|
|
94
|
+
{...inputAttributes}
|
|
95
|
+
showMediaAtAllSizes
|
|
96
|
+
media={(isSelected ? selected : props).media}
|
|
97
|
+
title={(isSelected ? selected : props).title}
|
|
98
|
+
content={(isSelected ? selected : props).content}
|
|
99
|
+
className={classNames(
|
|
100
|
+
'np-select-option',
|
|
101
|
+
{ 'np-select-option-selected': isSelected },
|
|
102
|
+
props.className,
|
|
103
|
+
)}
|
|
104
|
+
button={
|
|
105
|
+
isSelected ? (
|
|
106
|
+
<Chevron />
|
|
107
|
+
) : (
|
|
108
|
+
<ActionButton disabled={props.disabled} onClick={handleOnClick(true)}>
|
|
109
|
+
{action?.label || intl.formatMessage(messages.actionLabel)}
|
|
110
|
+
</ActionButton>
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
onClick={isSelected ? handleOnClick(true) : undefined}
|
|
114
|
+
/>
|
|
115
|
+
{isMobile ? (
|
|
116
|
+
<BottomSheet open={showOptions} onClose={handleOnClick(false)}>
|
|
117
|
+
{getOptionsContent(true)}
|
|
118
|
+
</BottomSheet>
|
|
119
|
+
) : (
|
|
120
|
+
<Panel
|
|
121
|
+
anchorWidth
|
|
122
|
+
open={showOptions}
|
|
123
|
+
flip={false}
|
|
124
|
+
altAxis={false}
|
|
125
|
+
anchorRef={rootRef}
|
|
126
|
+
position={Position.BOTTOM}
|
|
127
|
+
onClose={handleOnClick(false)}
|
|
128
|
+
>
|
|
129
|
+
{getOptionsContent(false)}
|
|
130
|
+
</Panel>
|
|
131
|
+
)}
|
|
132
|
+
</>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
@@ -11,6 +11,7 @@ describe('SlidingPanel', () => {
|
|
|
11
11
|
className: undefined,
|
|
12
12
|
slidingPanelPositionFixed: false,
|
|
13
13
|
showSlidingPanelBorder: false,
|
|
14
|
+
testId: undefined,
|
|
14
15
|
};
|
|
15
16
|
|
|
16
17
|
it('renders with right props', () => {
|
|
@@ -24,7 +25,7 @@ describe('SlidingPanel', () => {
|
|
|
24
25
|
|
|
25
26
|
expect(cssTransition()).toHaveLength(1);
|
|
26
27
|
|
|
27
|
-
expect(cssTransition().props()).
|
|
28
|
+
expect(cssTransition().props()).toEqual({
|
|
28
29
|
className: 'sliding-panel sliding-panel--open-left',
|
|
29
30
|
children: null,
|
|
30
31
|
});
|
|
@@ -12,6 +12,7 @@ export interface SlidingPanelProps
|
|
|
12
12
|
open: boolean;
|
|
13
13
|
showSlidingPanelBorder?: boolean;
|
|
14
14
|
slidingPanelPositionFixed?: boolean;
|
|
15
|
+
testId?: string;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
const SlidingPanel = forwardRef(
|
|
@@ -23,6 +24,7 @@ const SlidingPanel = forwardRef(
|
|
|
23
24
|
slidingPanelPositionFixed,
|
|
24
25
|
className,
|
|
25
26
|
children,
|
|
27
|
+
testId,
|
|
26
28
|
...rest
|
|
27
29
|
}: Omit<SlidingPanelProps, 'ref'>,
|
|
28
30
|
reference: React.ForwardedRef<HTMLDivElement | null>,
|
|
@@ -44,6 +46,7 @@ const SlidingPanel = forwardRef(
|
|
|
44
46
|
>
|
|
45
47
|
<div
|
|
46
48
|
ref={localReference}
|
|
49
|
+
data-testid={testId}
|
|
47
50
|
className={classNames(
|
|
48
51
|
'sliding-panel',
|
|
49
52
|
`sliding-panel--open-${position}`,
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
2
|
+
import Sticky, { StickyProps } from './Sticky';
|
|
3
|
+
|
|
4
|
+
describe('Sticky', () => {
|
|
5
|
+
const defaultProps = {
|
|
6
|
+
open: true,
|
|
7
|
+
children: <div>Test Content</div>,
|
|
8
|
+
position: 'bottom' as const,
|
|
9
|
+
testId: 'test-sticky-panel',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
let rerenderSticky: (props?: StickyProps) => void;
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
const { rerender } = render(<Sticky {...defaultProps} />);
|
|
16
|
+
|
|
17
|
+
rerenderSticky = (props?: StickyProps) => {
|
|
18
|
+
rerender(<Sticky {...defaultProps} {...props} />);
|
|
19
|
+
};
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('renders with default props', () => {
|
|
23
|
+
expect(screen.getByTestId('test-sticky-panel')).toBeVisible();
|
|
24
|
+
expect(screen.getByTestId('test-sticky-panel')).toHaveClass('sliding-panel--open-bottom');
|
|
25
|
+
expect(screen.getByText('Test Content')).toBeInTheDocument();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('renders with open `false`', async () => {
|
|
29
|
+
const props: StickyProps = {
|
|
30
|
+
...defaultProps,
|
|
31
|
+
open: false, // Ensure the Sticky is initially open
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
expect(screen.getByTestId('test-sticky-panel')).toBeVisible();
|
|
35
|
+
|
|
36
|
+
// Change props to close the Sticky
|
|
37
|
+
rerenderSticky(props);
|
|
38
|
+
|
|
39
|
+
await waitFor(() => {
|
|
40
|
+
// Use queryByText instead of getByText for conditional presence checks
|
|
41
|
+
expect(screen.queryByText('Test Content')).not.toBeInTheDocument();
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('renders new content', () => {
|
|
46
|
+
const props: StickyProps = {
|
|
47
|
+
...defaultProps,
|
|
48
|
+
children: <div>New Test Content</div>,
|
|
49
|
+
};
|
|
50
|
+
expect(screen.getByText('Test Content')).toBeInTheDocument();
|
|
51
|
+
|
|
52
|
+
// Change props
|
|
53
|
+
rerenderSticky(props);
|
|
54
|
+
|
|
55
|
+
expect(screen.getByText('New Test Content')).toBeInTheDocument();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('renders with position set to `top`', () => {
|
|
59
|
+
const props: StickyProps = {
|
|
60
|
+
...defaultProps,
|
|
61
|
+
position: 'top',
|
|
62
|
+
};
|
|
63
|
+
expect(screen.getByTestId('test-sticky-panel')).toBeInTheDocument();
|
|
64
|
+
expect(screen.getByTestId('test-sticky-panel')).toHaveClass('sliding-panel--open-bottom');
|
|
65
|
+
|
|
66
|
+
// Change props
|
|
67
|
+
rerenderSticky(props);
|
|
68
|
+
|
|
69
|
+
expect(screen.getByTestId('test-sticky-panel')).toHaveClass('sliding-panel--open-top');
|
|
70
|
+
});
|
|
71
|
+
});
|
|
@@ -1,22 +1,55 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
2
|
|
|
3
|
+
import React, { useState } from 'react';
|
|
3
4
|
import Sticky from './Sticky';
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
const meta: Meta<typeof Sticky> = {
|
|
6
7
|
component: Sticky,
|
|
7
8
|
title: 'Other/Sticky',
|
|
9
|
+
args: {
|
|
10
|
+
open: true,
|
|
11
|
+
position: 'bottom', // Default position
|
|
12
|
+
children: (
|
|
13
|
+
<div className="d-flex justify-content-start align-items-start flex-wrap p-x-4 p-y-4">
|
|
14
|
+
<p className="m-b-0">Default text for Sticky component.</p>
|
|
15
|
+
</div>
|
|
16
|
+
),
|
|
17
|
+
},
|
|
8
18
|
};
|
|
9
19
|
|
|
10
|
-
export
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
20
|
+
export default meta;
|
|
21
|
+
|
|
22
|
+
// Represents a story object for the `Sticky` component.
|
|
23
|
+
type Story = StoryObj<typeof Sticky>;
|
|
24
|
+
|
|
25
|
+
export const Default: Story = {}; // Default story with no additional props
|
|
26
|
+
|
|
27
|
+
export const PositionTop: Story = {
|
|
28
|
+
args: {
|
|
29
|
+
position: 'top', // Set the position prop to 'top'
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const WithLongContent: Story = {
|
|
34
|
+
args: {
|
|
35
|
+
children: (
|
|
36
|
+
<div className="p-x-4 p-y-4">
|
|
37
|
+
<p>
|
|
38
|
+
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Optio ipsa rem fugiat
|
|
39
|
+
repellendus nisi dolorum, recusandae aperiam repudiandae natus quaerat, explicabo fugit!
|
|
40
|
+
Odit illum vero eius quasi, error soluta veritatis.
|
|
41
|
+
</p>
|
|
42
|
+
<p>
|
|
43
|
+
Recusandae a animi tenetur magni itaque optio in aut enim odio. Minima, veritatis error
|
|
44
|
+
dicta expedita aliquam officiis deleniti repellat rerum nam ad quam dolorum reiciendis
|
|
45
|
+
voluptas doloremque quis eveniet.
|
|
46
|
+
</p>
|
|
47
|
+
<p>
|
|
48
|
+
Adipisci nulla quos magnam blanditiis minima nesciunt odio mollitia, fuga ullam aliquid
|
|
49
|
+
qui iure delectus dolores corporis officiis autem iste quia at ea sint tempore,
|
|
50
|
+
repudiandae amet? Error, praesentium eaque.
|
|
51
|
+
</p>
|
|
19
52
|
</div>
|
|
20
|
-
|
|
21
|
-
|
|
53
|
+
),
|
|
54
|
+
},
|
|
22
55
|
};
|
package/src/sticky/Sticky.tsx
CHANGED
|
@@ -1,16 +1,60 @@
|
|
|
1
1
|
import { Position } from '../common';
|
|
2
2
|
import SlidingPanel from '../slidingPanel';
|
|
3
|
+
import { FunctionComponent } from 'react';
|
|
3
4
|
|
|
4
5
|
export interface StickyProps {
|
|
6
|
+
/** Optional props that determines if the Sticky component is open or closed. Default value is `true`. */
|
|
5
7
|
open?: boolean;
|
|
6
|
-
|
|
8
|
+
|
|
9
|
+
/** Optional props that specifies the position of the Sticky component on the screen. Can be either `Position.TOP` or `Position.BOTTOM`. */
|
|
10
|
+
position?: `${Position.TOP}` | `${Position.BOTTOM}`;
|
|
11
|
+
|
|
12
|
+
/** Optional prop for children components to be rendered inside the Sticky component. */
|
|
7
13
|
children?: React.ReactNode;
|
|
14
|
+
|
|
15
|
+
/** Optional prop to specify the ID used for testing */
|
|
16
|
+
testId?: string;
|
|
8
17
|
}
|
|
9
18
|
|
|
10
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Sticky component.
|
|
21
|
+
*
|
|
22
|
+
* `Sticky` is a component that renders a `SlidingPanel` with specified props.
|
|
23
|
+
* It uses the `SlidingPanel` component to create a panel that can slide in and out of view,
|
|
24
|
+
* based on the `open` prop. It can be positioned at the top or bottom of the container,
|
|
25
|
+
* as specified by the `position` prop.
|
|
26
|
+
*
|
|
27
|
+
* @component
|
|
28
|
+
* @param {boolean} open - Determines if the Sticky component is open or closed.
|
|
29
|
+
* @param {('top'|'bottom')} position - Specifies the position.
|
|
30
|
+
* @param {ReactNode} children - Children content.
|
|
31
|
+
* @param {string} testId - ID used for testing.
|
|
32
|
+
* @returns {JSX.Element} The `SlidingPanel` component with applied props.
|
|
33
|
+
* @example
|
|
34
|
+
* <Button onClick={() => setOpen(!open)}>Open Sticky</Button>
|
|
35
|
+
* <Sticky open={open} position={'bottom'}>
|
|
36
|
+
* <div>
|
|
37
|
+
* <Button onClick={() => setOpen(!open)}>Close Sticky</Button>
|
|
38
|
+
* </div>
|
|
39
|
+
* </Sticky>
|
|
40
|
+
*/
|
|
41
|
+
const Sticky: FunctionComponent<StickyProps> = ({
|
|
42
|
+
open = true,
|
|
43
|
+
position = Position.BOTTOM,
|
|
44
|
+
children,
|
|
45
|
+
testId,
|
|
46
|
+
}: StickyProps) => {
|
|
11
47
|
return (
|
|
12
|
-
<SlidingPanel
|
|
48
|
+
<SlidingPanel
|
|
49
|
+
testId={testId}
|
|
50
|
+
open={open}
|
|
51
|
+
position={position}
|
|
52
|
+
slidingPanelPositionFixed
|
|
53
|
+
showSlidingPanelBorder
|
|
54
|
+
>
|
|
13
55
|
{children}
|
|
14
56
|
</SlidingPanel>
|
|
15
57
|
);
|
|
16
|
-
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export default Sticky;
|
|
@@ -26,9 +26,6 @@
|
|
|
26
26
|
padding: 16px;
|
|
27
27
|
padding: var(--padding-small);
|
|
28
28
|
}
|
|
29
|
-
.np-theme-personal .np-upload-input .np-upload-button .media {
|
|
30
|
-
align-items: start;
|
|
31
|
-
}
|
|
32
29
|
.np-theme-personal .np-upload-input .np-upload-input--square-top {
|
|
33
30
|
border-top-right-radius: 0px;
|
|
34
31
|
border-top-left-radius: 0px;
|
|
@@ -36,9 +33,6 @@
|
|
|
36
33
|
.np-theme-personal .np-upload-input .np-upload-item__remove-button {
|
|
37
34
|
border-radius: 0;
|
|
38
35
|
border-radius: initial;
|
|
39
|
-
display: inline-flex;
|
|
40
|
-
padding-top: 16px;
|
|
41
|
-
padding-top: var(--padding-small);
|
|
42
36
|
}
|
|
43
37
|
.np-theme-personal .np-upload-input .np-upload-icon {
|
|
44
38
|
color: var(--color-interactive-primary);
|
|
@@ -61,10 +55,6 @@
|
|
|
61
55
|
.np-theme-personal .np-upload-input .media-body .text-negative {
|
|
62
56
|
color: var(--color-sentiment-negative) !important;
|
|
63
57
|
}
|
|
64
|
-
.np-theme-personal .np-upload-input-errors {
|
|
65
|
-
padding-left: 0;
|
|
66
|
-
list-style-position: inside;
|
|
67
|
-
}
|
|
68
58
|
.np-theme-personal .np-upload-input .status-circle {
|
|
69
59
|
width: 24px;
|
|
70
60
|
width: var(--size-x-small);
|
|
@@ -27,10 +27,6 @@
|
|
|
27
27
|
.np-upload-button {
|
|
28
28
|
border-radius: @small-border-minus-one;
|
|
29
29
|
padding: var(--padding-small);
|
|
30
|
-
|
|
31
|
-
.media {
|
|
32
|
-
align-items: start;
|
|
33
|
-
}
|
|
34
30
|
}
|
|
35
31
|
|
|
36
32
|
.np-upload-input--square-top {
|
|
@@ -40,8 +36,6 @@
|
|
|
40
36
|
|
|
41
37
|
.np-upload-item__remove-button {
|
|
42
38
|
border-radius: unset;
|
|
43
|
-
display: inline-flex;
|
|
44
|
-
padding-top: var(--padding-small);
|
|
45
39
|
}
|
|
46
40
|
|
|
47
41
|
.np-upload-icon {
|
|
@@ -68,11 +62,6 @@
|
|
|
68
62
|
}
|
|
69
63
|
}
|
|
70
64
|
|
|
71
|
-
&-errors {
|
|
72
|
-
padding-left: 0;
|
|
73
|
-
list-style-position: inside;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
65
|
.status-circle {
|
|
77
66
|
width: var(--size-x-small);
|
|
78
67
|
height: var(--size-x-small);
|