Skip to content

Commit a6f9074

Browse files
committed
Add DateTimePicker component
1 parent 18b335e commit a6f9074

File tree

10 files changed

+266
-4
lines changed

10 files changed

+266
-4
lines changed

packages/examples/packages/interactive-ui/snap.manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"url": "https://github.com/MetaMask/snaps.git"
88
},
99
"source": {
10-
"shasum": "Et0r8P+ORsedVAqQ9iF02gHUBC0zZjWg/xptu4v/DlU=",
10+
"shasum": "r1H34qFTVIm2D+PzqxIk+VTnYto8v9QNuEFtCZ4qxQU=",
1111
"location": {
1212
"npm": {
1313
"filePath": "dist/bundle.js",

packages/examples/packages/interactive-ui/src/components/InteractiveForm.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
Checkbox,
1717
Container,
1818
Footer,
19+
DateTimePicker,
1920
} from '@metamask/snaps-sdk/jsx';
2021

2122
/**
@@ -102,6 +103,9 @@ export const InteractiveForm: SnapComponent<{ disabled?: boolean }> = ({
102103
</SelectorOption>
103104
</Selector>
104105
</Field>
106+
<Field label="Example DateTimePicker">
107+
<DateTimePicker name="example-datetime" />
108+
</Field>
105109
</Form>
106110
</Box>
107111
<Footer>

packages/snaps-controllers/src/interface/utils.test.tsx

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
AssetSelector,
1919
AddressInput,
2020
AccountSelector,
21+
DateTimePicker,
2122
} from '@metamask/snaps-sdk/jsx';
2223
import type { CaipAccountId } from '@metamask/utils';
2324
import { parseCaipAccountId } from '@metamask/utils';
@@ -352,6 +353,66 @@ describe('constructState', () => {
352353
});
353354
});
354355

356+
it('sets default value for root level DateTimePicker', () => {
357+
const element = (
358+
<Box>
359+
<DateTimePicker name="foo" />
360+
</Box>
361+
);
362+
363+
const result = constructState({}, element, elementDataGetters);
364+
expect(result).toStrictEqual({
365+
foo: null,
366+
});
367+
});
368+
369+
it('supports root level DateTimePicker', () => {
370+
const element = (
371+
<Box>
372+
<DateTimePicker name="foo" value="2022-01-01T00:00:00Z" />
373+
</Box>
374+
);
375+
376+
const result = constructState({}, element, elementDataGetters);
377+
expect(result).toStrictEqual({
378+
foo: '2022-01-01T00:00:00Z',
379+
});
380+
});
381+
382+
it('sets default value for DateTimePicker in forms', () => {
383+
const element = (
384+
<Box>
385+
<Form name="form">
386+
<Field label="foo">
387+
<DateTimePicker name="foo" />
388+
</Field>
389+
</Form>
390+
</Box>
391+
);
392+
393+
const result = constructState({}, element, elementDataGetters);
394+
expect(result).toStrictEqual({
395+
form: { foo: null },
396+
});
397+
});
398+
399+
it('supports DateTimePicker in forms', () => {
400+
const element = (
401+
<Box>
402+
<Form name="form">
403+
<Field label="foo">
404+
<DateTimePicker name="foo" value="2022-01-01T00:00:00Z" />
405+
</Field>
406+
</Form>
407+
</Box>
408+
);
409+
410+
const result = constructState({}, element, elementDataGetters);
411+
expect(result).toStrictEqual({
412+
form: { foo: '2022-01-01T00:00:00Z' },
413+
});
414+
});
415+
355416
it('sets default value for root level dropdown', () => {
356417
const element = (
357418
<Box>

packages/snaps-controllers/src/interface/utils.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import type {
2323
AssetSelectorElement,
2424
AddressInputElement,
2525
AccountSelectorElement,
26+
DateTimePickerElement,
2627
} from '@metamask/snaps-sdk/jsx';
2728
import { isJSXElementUnsafe } from '@metamask/snaps-sdk/jsx';
2829
import type { InternalAccount } from '@metamask/snaps-utils';
@@ -57,6 +58,7 @@ const STATEFUL_COMPONENT_TYPES = [
5758
'AssetSelector',
5859
'AddressInput',
5960
'AccountSelector',
61+
'DateTimePicker',
6062
] as const;
6163

6264
/**
@@ -342,7 +344,8 @@ function constructComponentSpecificDefaultState(
342344
| SelectorElement
343345
| AssetSelectorElement
344346
| AddressInputElement
345-
| AccountSelectorElement,
347+
| AccountSelectorElement
348+
| DateTimePickerElement,
346349
elementDataGetters: ElementDataGetters,
347350
) {
348351
switch (element.type) {
@@ -459,7 +462,8 @@ function getComponentStateValue(
459462
| SelectorElement
460463
| AssetSelectorElement
461464
| AddressInputElement
462-
| AccountSelectorElement,
465+
| AccountSelectorElement
466+
| DateTimePickerElement,
463467
elementDataGetters: ElementDataGetters,
464468
) {
465469
switch (element.type) {
@@ -510,7 +514,8 @@ function constructInputState(
510514
| SelectorElement
511515
| AssetSelectorElement
512516
| AddressInputElement
513-
| AccountSelectorElement,
517+
| AccountSelectorElement
518+
| DateTimePickerElement,
514519
elementDataGetters: ElementDataGetters,
515520
form?: string,
516521
) {
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { DateTimePicker } from './DateTimePicker';
2+
3+
describe('DateTimePicker', () => {
4+
it('renders a date picker', () => {
5+
const element = <DateTimePicker name="test" type="date" />;
6+
7+
expect(element).toStrictEqual({
8+
type: 'DateTimePicker',
9+
props: {
10+
name: 'test',
11+
type: 'date',
12+
},
13+
key: null,
14+
});
15+
});
16+
17+
it('renders a time picker', () => {
18+
const element = <DateTimePicker name="test" type="time" />;
19+
20+
expect(element).toStrictEqual({
21+
type: 'DateTimePicker',
22+
props: {
23+
name: 'test',
24+
type: 'time',
25+
},
26+
key: null,
27+
});
28+
});
29+
30+
it('renders a date and time picker', () => {
31+
const element = <DateTimePicker name="test" type="datetime" />;
32+
33+
expect(element).toStrictEqual({
34+
type: 'DateTimePicker',
35+
props: {
36+
name: 'test',
37+
type: 'datetime',
38+
},
39+
key: null,
40+
});
41+
});
42+
43+
it('renders a date and time picker with optional props', () => {
44+
const element = (
45+
<DateTimePicker
46+
name="test"
47+
type="datetime"
48+
placeholder="Select date and time"
49+
disabled={true}
50+
value="2024-01-01T12:00"
51+
/>
52+
);
53+
54+
expect(element).toStrictEqual({
55+
type: 'DateTimePicker',
56+
props: {
57+
name: 'test',
58+
type: 'datetime',
59+
placeholder: 'Select date and time',
60+
disabled: true,
61+
value: '2024-01-01T12:00',
62+
},
63+
key: null,
64+
});
65+
});
66+
});
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { createSnapComponent } from '../../component';
2+
3+
/**
4+
* The props of the {@link DateTimePicker} component.
5+
*
6+
* @property name - The name of the date/time picker field. This is used to identify the
7+
* date/time picker field in the form data.
8+
* @property value - The value of the date/time picker field.
9+
* @property type - The type of the date/time picker field. Can be 'date', 'time', or 'datetime'.
10+
* Defaults to 'datetime'.
11+
* @property placeholder - The placeholder text of the date/time picker field.
12+
* @property disabled - Whether the date/time picker field is disabled.
13+
*/
14+
export type DateTimePickerProps = {
15+
name: string;
16+
value?: string | undefined;
17+
type?: 'date' | 'time' | 'datetime' | undefined;
18+
placeholder?: string | undefined;
19+
disabled?: boolean | undefined;
20+
};
21+
22+
const TYPE = 'DateTimePicker';
23+
24+
/**
25+
* A date/time picker component, which is used to create a date/time picker field.
26+
*
27+
* @param props - The props of the component.
28+
* @param props.name - The name of the date/time picker field. This is used to identify the
29+
* date/time picker field in the form data.
30+
* @param props.value - The value of the date/time picker field.
31+
* @param props.type - The type of the date/time picker field. Can be 'date', 'time', or 'datetime'.
32+
* Defaults to 'datetime'.
33+
* @param props.placeholder - The placeholder text of the date/time picker field.
34+
* @param props.disabled - Whether the date/time picker field is disabled.
35+
*/
36+
export const DateTimePicker = createSnapComponent<
37+
DateTimePickerProps,
38+
typeof TYPE
39+
>(TYPE);
40+
41+
/**
42+
* A DateTimePicker element.
43+
*
44+
* @see DateTimePicker
45+
*/
46+
export type DateTimePickerElement = ReturnType<typeof DateTimePicker>;

packages/snaps-sdk/src/jsx/components/form/Field.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { AccountSelectorElement } from './AccountSelector';
22
import type { AddressInputElement } from './AddressInput';
33
import type { AssetSelectorElement } from './AssetSelector';
44
import type { CheckboxElement } from './Checkbox';
5+
import type { DateTimePickerElement } from './DateTimePicker';
56
import type { DropdownElement } from './Dropdown';
67
import type { FileInputElement } from './FileInput';
78
import type { InputElement } from './Input';
@@ -25,6 +26,7 @@ export type FieldProps = {
2526
| [GenericSnapChildren, InputElement]
2627
| [GenericSnapChildren, InputElement, GenericSnapChildren]
2728
| DropdownElement
29+
| DateTimePickerElement
2830
| RadioGroupElement
2931
| FileInputElement
3032
| InputElement

packages/snaps-sdk/src/jsx/components/form/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { AddressInputElement } from './AddressInput';
33
import type { AssetSelectorElement } from './AssetSelector';
44
import type { ButtonElement } from './Button';
55
import type { CheckboxElement } from './Checkbox';
6+
import type { DateTimePickerElement } from './DateTimePicker';
67
import type { DropdownElement } from './Dropdown';
78
import type { FieldElement } from './Field';
89
import type { FileInputElement } from './FileInput';
@@ -19,6 +20,7 @@ export * from './AddressInput';
1920
export * from './AssetSelector';
2021
export * from './Button';
2122
export * from './Checkbox';
23+
export * from './DateTimePicker';
2224
export * from './Dropdown';
2325
export * from './Option';
2426
export * from './Radio';
@@ -36,6 +38,7 @@ export type StandardFormElement =
3638
| AssetSelectorElement
3739
| ButtonElement
3840
| CheckboxElement
41+
| DateTimePickerElement
3942
| FormElement
4043
| FieldElement
4144
| FileInputElement

packages/snaps-sdk/src/jsx/validation.test.tsx

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import {
3838
AssetSelector,
3939
AddressInput,
4040
AccountSelector,
41+
DateTimePicker,
4142
} from './components';
4243
import {
4344
AddressStruct,
@@ -80,6 +81,7 @@ import {
8081
AssetSelectorStruct,
8182
AddressInputStruct,
8283
AccountSelectorStruct,
84+
DateTimePickerStruct,
8385
} from './validation';
8486

8587
describe('KeyStruct', () => {
@@ -381,6 +383,9 @@ describe('FieldStruct', () => {
381383
<Field label="foo">
382384
<AccountSelector name="account" />
383385
</Field>,
386+
<Field label="foo">
387+
<DateTimePicker name="foo" />
388+
</Field>,
384389
])('validates a field element', (value) => {
385390
expect(is(value, FieldStruct)).toBe(true);
386391
});
@@ -1008,6 +1013,54 @@ describe('CopyableStruct', () => {
10081013
});
10091014
});
10101015

1016+
describe('DateTimePickerStruct', () => {
1017+
it.each([
1018+
<DateTimePicker name="foo" />,
1019+
<DateTimePicker name="foo" type="datetime" />,
1020+
<DateTimePicker name="foo" type="date" />,
1021+
<DateTimePicker name="foo" type="time" />,
1022+
<DateTimePicker name="foo" value={new Date().toISOString()} />,
1023+
<DateTimePicker name="foo" disabled={true} />,
1024+
<DateTimePicker
1025+
name="foo"
1026+
value={new Date().toISOString()}
1027+
placeholder="foobar"
1028+
type="datetime"
1029+
disabled={true}
1030+
/>,
1031+
])('validates a date time picker element', (value) => {
1032+
expect(is(value, DateTimePickerStruct)).toBe(true);
1033+
});
1034+
1035+
it.each([
1036+
'foo',
1037+
42,
1038+
null,
1039+
undefined,
1040+
{},
1041+
[],
1042+
// @ts-expect-error - Invalid props.
1043+
<DateTimePicker />,
1044+
// @ts-expect-error - Invalid props.
1045+
<DateTimePicker name={42} />,
1046+
// @ts-expect-error - Invalid props.
1047+
<DateTimePicker name="foo" type="foo" />,
1048+
// @ts-expect-error - Invalid props.
1049+
<DateTimePicker name="foo" value={42} />,
1050+
// @ts-expect-error - Invalid props.
1051+
<DateTimePicker name="foo" placeholder={32} />,
1052+
<Text>foo</Text>,
1053+
<Box>
1054+
<Text>foo</Text>
1055+
</Box>,
1056+
<Row label="label">
1057+
<Image src="src" alt="alt" />
1058+
</Row>,
1059+
])('does not validate "%p"', (value) => {
1060+
expect(is(value, DateTimePickerStruct)).toBe(false);
1061+
});
1062+
});
1063+
10111064
describe('DividerStruct', () => {
10121065
it.each([<Divider />])('validates a divider element', (value) => {
10131066
expect(is(value, DividerStruct)).toBe(true);

0 commit comments

Comments
 (0)