@codecademy/styleguide 79.1.4-alpha.7f2ebb.0 → 79.1.4-alpha.d3a516.0
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/CHANGELOG.md +1 -1
- package/package.json +2 -2
- package/src/lib/Molecules/DatePicker/Calendar.mdx +26 -0
- package/src/lib/Molecules/DatePicker/Calendar.stories.tsx +53 -0
- package/src/lib/Molecules/DatePicker/DatePicker.stories.tsx +124 -0
- package/src/lib/UX Writing/Component guidelines/Confirmation dialogs.mdx +26 -44
- package/src/static/ux writing/clear_chat.png +0 -0
- package/src/static/ux writing/delete_study_plan.png +0 -0
- package/src/static/ux writing/delete_this_course.png +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
-
### [79.1.4-alpha.
|
|
6
|
+
### [79.1.4-alpha.d3a516.0](https://github.com/Codecademy/gamut/compare/@codecademy/styleguide@79.1.3...@codecademy/styleguide@79.1.4-alpha.d3a516.0) (2026-03-17)
|
|
7
7
|
|
|
8
8
|
**Note:** Version bump only for package @codecademy/styleguide
|
|
9
9
|
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codecademy/styleguide",
|
|
3
3
|
"description": "Styleguide & Component library for codecademy.com",
|
|
4
|
-
"version": "79.1.4-alpha.
|
|
4
|
+
"version": "79.1.4-alpha.d3a516.0",
|
|
5
5
|
"author": "Codecademy Engineering",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"publishConfig": {
|
|
8
8
|
"access": "public"
|
|
9
9
|
},
|
|
10
10
|
"repository": "git@github.com:Codecademy/gamut.git",
|
|
11
|
-
"gitHead": "
|
|
11
|
+
"gitHead": "0b2a5f824b8f2ce009e7171b3865bfd2621f60cb"
|
|
12
12
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Canvas, Controls, Meta } from '@storybook/blocks';
|
|
2
|
+
|
|
3
|
+
import { ComponentHeader } from '~styleguide/blocks';
|
|
4
|
+
|
|
5
|
+
import * as CalendarStories from './Calendar.stories';
|
|
6
|
+
|
|
7
|
+
export const parameters = {
|
|
8
|
+
title: 'DatePicker/Calendar',
|
|
9
|
+
subtitle: `Calendar grid with header (month/year + prev/next), body (day grid), and footer (Clear, Today, quick actions). Used inside DatePickerCalendar.`,
|
|
10
|
+
status: 'current',
|
|
11
|
+
source: {
|
|
12
|
+
repo: 'gamut',
|
|
13
|
+
githubLink:
|
|
14
|
+
'https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/DatePicker/Calendar',
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
<Meta of={CalendarStories} />
|
|
19
|
+
|
|
20
|
+
<ComponentHeader {...parameters} />
|
|
21
|
+
|
|
22
|
+
## Playground
|
|
23
|
+
|
|
24
|
+
<Canvas sourceState="shown" of={CalendarStories.Default} />
|
|
25
|
+
|
|
26
|
+
<Controls />
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Calendar,
|
|
3
|
+
CalendarBody,
|
|
4
|
+
CalendarFooter,
|
|
5
|
+
CalendarHeader,
|
|
6
|
+
} from '@codecademy/gamut';
|
|
7
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
8
|
+
import { useId, useState } from 'react';
|
|
9
|
+
|
|
10
|
+
const meta: Meta<typeof Calendar> = {
|
|
11
|
+
component: Calendar,
|
|
12
|
+
title: 'Molecules/DatePicker/Calendar',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default meta;
|
|
16
|
+
|
|
17
|
+
type Story = StoryObj<typeof Calendar>;
|
|
18
|
+
|
|
19
|
+
export const Default: Story = {
|
|
20
|
+
render: function CalendarStory() {
|
|
21
|
+
const headingId = useId();
|
|
22
|
+
const [visibleDate, setVisibleDate] = useState(() => new Date());
|
|
23
|
+
const [selectedDate, setSelectedDate] = useState<Date | null>(null);
|
|
24
|
+
const [focusedDate, setFocusedDate] = useState<Date | null>(
|
|
25
|
+
() => new Date()
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<Calendar>
|
|
30
|
+
<CalendarHeader
|
|
31
|
+
currentMonthYear={visibleDate}
|
|
32
|
+
headingId={headingId}
|
|
33
|
+
locale="en-US"
|
|
34
|
+
onCurrentMonthYearChange={setVisibleDate}
|
|
35
|
+
/>
|
|
36
|
+
<CalendarBody
|
|
37
|
+
focusedDate={focusedDate}
|
|
38
|
+
labelledById={headingId}
|
|
39
|
+
locale="en-US"
|
|
40
|
+
selectedDate={selectedDate}
|
|
41
|
+
visibleDate={visibleDate}
|
|
42
|
+
onDateSelect={setSelectedDate}
|
|
43
|
+
onFocusedDateChange={setFocusedDate}
|
|
44
|
+
onVisibleDateChange={setVisibleDate}
|
|
45
|
+
/>
|
|
46
|
+
<CalendarFooter
|
|
47
|
+
onCurrentMonthYearChange={setVisibleDate}
|
|
48
|
+
onSelectedDateChange={setSelectedDate}
|
|
49
|
+
/>
|
|
50
|
+
</Calendar>
|
|
51
|
+
);
|
|
52
|
+
},
|
|
53
|
+
};
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Box,
|
|
3
|
+
DatePicker,
|
|
4
|
+
DatePickerCalendar,
|
|
5
|
+
DatePickerInput,
|
|
6
|
+
PopoverContainer,
|
|
7
|
+
useDatePicker,
|
|
8
|
+
} from '@codecademy/gamut';
|
|
9
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
10
|
+
import { useRef, useState } from 'react';
|
|
11
|
+
|
|
12
|
+
const meta: Meta<typeof DatePicker> = {
|
|
13
|
+
component: DatePicker,
|
|
14
|
+
title: 'Molecules/DatePicker/DatePicker',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export default meta;
|
|
18
|
+
|
|
19
|
+
type Story = StoryObj<typeof DatePicker>;
|
|
20
|
+
|
|
21
|
+
export const Default: Story = {
|
|
22
|
+
render: function DatePickerStory() {
|
|
23
|
+
const [selectedDate, setSelectedDate] = useState<Date | null>(null);
|
|
24
|
+
return (
|
|
25
|
+
<Box p={32}>
|
|
26
|
+
<DatePicker
|
|
27
|
+
label="Date"
|
|
28
|
+
locale="de-DE"
|
|
29
|
+
placeholder="MM/DD/YYYY"
|
|
30
|
+
selectedDate={selectedDate}
|
|
31
|
+
setSelectedDate={setSelectedDate}
|
|
32
|
+
/>
|
|
33
|
+
</Box>
|
|
34
|
+
);
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const WithInitialDate: Story = {
|
|
39
|
+
render: function DatePickerStory() {
|
|
40
|
+
const [selectedDate, setSelectedDate] = useState<Date | null>(
|
|
41
|
+
() => new Date(2026, 1, 15)
|
|
42
|
+
);
|
|
43
|
+
return (
|
|
44
|
+
<DatePicker
|
|
45
|
+
label="Date"
|
|
46
|
+
placeholder="MM/DD/YYYY"
|
|
47
|
+
selectedDate={selectedDate}
|
|
48
|
+
setSelectedDate={setSelectedDate}
|
|
49
|
+
/>
|
|
50
|
+
);
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/** Range mode: two inputs for start and end date. First calendar click sets start, second sets end. */
|
|
55
|
+
export const Range: Story = {
|
|
56
|
+
render: function DatePickerStory() {
|
|
57
|
+
const [startDate, setStartDate] = useState<Date | null>(null);
|
|
58
|
+
const [endDate, setEndDate] = useState<Date | null>(null);
|
|
59
|
+
return (
|
|
60
|
+
<Box p={32}>
|
|
61
|
+
<DatePicker
|
|
62
|
+
endDate={endDate}
|
|
63
|
+
endLabel="End date"
|
|
64
|
+
mode="range"
|
|
65
|
+
setEndDate={setEndDate}
|
|
66
|
+
setStartDate={setStartDate}
|
|
67
|
+
startDate={startDate}
|
|
68
|
+
startLabel="Start date"
|
|
69
|
+
/>
|
|
70
|
+
</Box>
|
|
71
|
+
);
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Composed usage: DatePicker with children provides shared state via context.
|
|
77
|
+
* The child uses useDatePicker() to get open/close and inputRef, then composes
|
|
78
|
+
* DatePickerInput and DatePickerCalendar with a custom PopoverContainer layout.
|
|
79
|
+
*/
|
|
80
|
+
export const ComposedWithContext: Story = {
|
|
81
|
+
render: function DatePickerStory() {
|
|
82
|
+
const [selectedDate, setSelectedDate] = useState<Date | null>(null);
|
|
83
|
+
return (
|
|
84
|
+
<Box p={32}>
|
|
85
|
+
<DatePicker
|
|
86
|
+
label="Start date"
|
|
87
|
+
placeholder="MM/DD/YYYY"
|
|
88
|
+
selectedDate={selectedDate}
|
|
89
|
+
setSelectedDate={setSelectedDate}
|
|
90
|
+
>
|
|
91
|
+
<ComposedDatePickerLayout />
|
|
92
|
+
</DatePicker>
|
|
93
|
+
</Box>
|
|
94
|
+
);
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
function ComposedDatePickerLayout() {
|
|
99
|
+
const { isCalendarOpen, openCalendar, closeCalendar, calendarDialogId } =
|
|
100
|
+
useDatePicker();
|
|
101
|
+
const inputRef = useRef<HTMLInputElement | null>(null);
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<>
|
|
105
|
+
<Box width="fit-content" onClick={openCalendar}>
|
|
106
|
+
<DatePickerInput ref={inputRef} />
|
|
107
|
+
</Box>
|
|
108
|
+
<PopoverContainer
|
|
109
|
+
alignment="bottom-left"
|
|
110
|
+
allowPageInteraction
|
|
111
|
+
focusOnProps={{ autoFocus: false, focusLock: false }}
|
|
112
|
+
invertAxis="x"
|
|
113
|
+
isOpen={isCalendarOpen}
|
|
114
|
+
offset={10}
|
|
115
|
+
targetRef={inputRef}
|
|
116
|
+
onRequestClose={closeCalendar}
|
|
117
|
+
>
|
|
118
|
+
<div aria-label="Choose date" id={calendarDialogId} role="dialog">
|
|
119
|
+
<DatePickerCalendar dialogId={calendarDialogId} />
|
|
120
|
+
</div>
|
|
121
|
+
</PopoverContainer>
|
|
122
|
+
</>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { Meta } from '@storybook/blocks';
|
|
2
2
|
|
|
3
|
-
import { AboutHeader,
|
|
3
|
+
import { AboutHeader, LinkTo } from '~styleguide/blocks';
|
|
4
4
|
|
|
5
5
|
export const parameters = {
|
|
6
6
|
title: 'Confirmation dialogs',
|
|
7
7
|
subtitle:
|
|
8
|
-
'
|
|
8
|
+
'Simplify the language, prioritize the message, and make sure the implication of what learners are saying "Yes" (or "No") to is crystal clear.',
|
|
9
9
|
status: 'static',
|
|
10
10
|
design: {
|
|
11
11
|
type: 'figma',
|
|
@@ -17,57 +17,39 @@ export const parameters = {
|
|
|
17
17
|
|
|
18
18
|
<AboutHeader {...parameters} />
|
|
19
19
|
|
|
20
|
-
Confirmation
|
|
20
|
+
Confirmation dialog boxes are used to verify that a learner wants to take a specific action. They are generally used for actions that are irreversible, may result in critical consequences or loss of data, have other severe consequences, or happen infrequently.
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
- Loss of data, time, or work (e.g., deleting a course)
|
|
24
|
-
- Unexpected consequences (e.g., losing learning history on an existing prototype when when generating a new prototype)
|
|
25
|
-
|
|
26
|
-
Adding friction for these purposes helps improve trust and avoid unintentional actions by making sure learners clearly understand the consequences before continuing. It also lets us offer alternatives or undo options when needed.
|
|
22
|
+
They use the <LinkTo id="Molecules/Modals/Dialog">Dialog component in Gamut</LinkTo> and, for actions with serious or irreversible consequences, the `Danger` variant should be used.
|
|
27
23
|
|
|
28
24
|
## Best practices
|
|
29
25
|
|
|
30
|
-
###
|
|
31
|
-
|
|
32
|
-
- **Ask or inform about one main action**, mirroring the button that triggered the confirmation dialog.
|
|
33
|
-
- **Frame your headline as a binary question**, when possible, with 2 unambiguous answers.
|
|
34
|
-
- **Avoid generic “Are you sure?” headings and body text.** This phrasing takes up space, increases cognitive load, and may undermine users' confidence or be interpreted as patronizing.
|
|
35
|
-
|
|
36
|
-
### Body (optional)
|
|
37
|
-
|
|
38
|
-
- **Add essential information about the contextual consequences.** State what will happen, what will be lost/changed, and any critical conditions.
|
|
39
|
-
- **Avoid redundancy.** If the heading is already self-explanatory, the body is not needed.
|
|
40
|
-
- **Keep to 1–2 lines, unless more is required to get all the information across.**
|
|
41
|
-
|
|
42
|
-
### Buttons (CTA1 and CTA2)
|
|
26
|
+
### Headline
|
|
43
27
|
|
|
44
|
-
- **
|
|
45
|
-
- **
|
|
46
|
-
- **
|
|
47
|
-
- Whenever possible, be specific about the alternative. However, when space is limited, 'Cancel' can be used.
|
|
28
|
+
- **Ask or inform about one main action**, clearly and simply.
|
|
29
|
+
- **Frame your headline as a binary question**, when possible, with 2 unambiguous answers (i.e. Yes/No, Stay/Leave).
|
|
30
|
+
- **Be specific.** Instead of "Are you sure?" focus on what you want to ensure they're sure about (i.e. "Reset your progress?" or "Delete the file?").
|
|
48
31
|
|
|
49
|
-
###
|
|
32
|
+
### Explanation
|
|
50
33
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
34
|
+
- **Share only relevant information** that may help the learner make their decision.
|
|
35
|
+
- **Avoid redundancy.** If you've already set the stage in your headline, there's no need to re-ask the same question in your explanation. If the explanation doesn't add anything new, leave it out (i.e. "Permanently delete this item? Yes/No").
|
|
36
|
+
- **Avoid filler.** Questions like "Are you sure you want to \_\_\_?" take up space, increase cognitive load, and may undermine users' confidence or be interpreted as patronizing.
|
|
37
|
+
- **Keep to 1-2 lines**, unless more is required to get all the information across.
|
|
55
38
|
|
|
56
|
-
|
|
57
|
-
src="./ux writing/delete_study_plan.png"
|
|
58
|
-
alt="Delete study plan confirmation dialog"
|
|
59
|
-
/>
|
|
39
|
+
### Button copy
|
|
60
40
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
/>
|
|
41
|
+
- **Options should be clear and distinct.** Each option should be distinctly different and there should be no opportunity for learners to mix them up (i.e. "Delete" and "Cancel" are ambiguous choices whereas "Yes, remove" and "Cancel" clear up the confusion.
|
|
42
|
+
- **Add context to reaffirm the action.** Instead of "Yes," use "Yes, reset progress."
|
|
43
|
+
- **Match the verb in your headline.** If you use "Save" in your headline, use "Save" in your button copy, rather than keep. Consistency helps keep the message clear. All of this should also match whatever the learner clicked on that triggered the confirmation dialog.
|
|
65
44
|
|
|
66
45
|
## Checklist
|
|
67
46
|
|
|
68
|
-
- Is the
|
|
69
|
-
-
|
|
70
|
-
-
|
|
71
|
-
- Does the
|
|
72
|
-
-
|
|
73
|
-
-
|
|
47
|
+
- Is the language consistent from the wording on the button that opened the confirmation box, to the headline,
|
|
48
|
+
- Does the headline make the action clear?
|
|
49
|
+
- Is the headline framed as a question, if possible?
|
|
50
|
+
- Does the explanation provide relevant details and consequences of the action?
|
|
51
|
+
- Is the explanation 1-2 lines long?
|
|
52
|
+
- Are the words on the buttons clear and distinct?
|
|
53
|
+
- Do the buttons include context to reaffirm the action?
|
|
54
|
+
- Is your copy at a reading level of grade 7 or below? Test with [Hemingway App](https://hemingwayapp.com).
|
|
55
|
+
- Have you asked someone unrelated to the project to read the message and did they understand it?
|
|
Binary file
|
|
Binary file
|
|
Binary file
|