@vanilla-bean/components 1.0.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/Component/Component.js +598 -0
- package/Component/Component.scenarios.js +88 -0
- package/Component/Component.test.js +717 -0
- package/Component/README.md +455 -0
- package/Component/index.js +3 -0
- package/Component/observeElementConnection.js +52 -0
- package/Component/observeElementConnection.test.js +121 -0
- package/Elem/Elem.js +304 -0
- package/Elem/Elem.test.js +679 -0
- package/Elem/README.md +373 -0
- package/Elem/index.js +1 -0
- package/FontWithASyntaxHighlighter-Regular.woff2 +0 -0
- package/LICENSE +21 -0
- package/README.md +413 -0
- package/components/BottomSheet/BottomSheet.js +192 -0
- package/components/BottomSheet/BottomSheet.lld.md +25 -0
- package/components/BottomSheet/README.md +66 -0
- package/components/BottomSheet/index.js +1 -0
- package/components/Button/Button.js +53 -0
- package/components/Button/Button.lld.md +21 -0
- package/components/Button/index.js +1 -0
- package/components/Calendar/Calendar.js +720 -0
- package/components/Calendar/Calendar.lld.md +22 -0
- package/components/Calendar/CalendarEvent.js +102 -0
- package/components/Calendar/Toolbar.js +78 -0
- package/components/Calendar/index.js +2 -0
- package/components/Calendar/utils.js +56 -0
- package/components/Code/Code.js +84 -0
- package/components/Code/Code.lld.md +21 -0
- package/components/Code/index.js +1 -0
- package/components/ColorPicker/ColorPicker.js +445 -0
- package/components/ColorPicker/ColorPicker.lld.md +21 -0
- package/components/ColorPicker/index.js +1 -0
- package/components/ColorPicker/svg.js +5 -0
- package/components/Dialog/Dialog.js +278 -0
- package/components/Dialog/Dialog.lld.md +20 -0
- package/components/Dialog/README.md +96 -0
- package/components/Dialog/index.js +1 -0
- package/components/Form/Form.js +257 -0
- package/components/Form/Form.lld.md +21 -0
- package/components/Form/README.md +87 -0
- package/components/Form/index.js +1 -0
- package/components/Icon/Icon.js +54 -0
- package/components/Icon/Icon.lld.md +21 -0
- package/components/Icon/index.js +1 -0
- package/components/Input/Input.js +173 -0
- package/components/Input/Input.lld.md +28 -0
- package/components/Input/README.md +97 -0
- package/components/Input/index.js +2 -0
- package/components/Input/utils.js +122 -0
- package/components/Keyboard/Key.js +38 -0
- package/components/Keyboard/Keyboard.js +173 -0
- package/components/Keyboard/Keyboard.lld.md +21 -0
- package/components/Keyboard/index.js +1 -0
- package/components/Label/Label.js +214 -0
- package/components/Label/Label.lld.md +20 -0
- package/components/Label/index.js +1 -0
- package/components/Link/Link.js +43 -0
- package/components/Link/Link.lld.md +15 -0
- package/components/Link/index.js +1 -0
- package/components/List/List.js +82 -0
- package/components/List/List.lld.md +19 -0
- package/components/List/index.js +1 -0
- package/components/Menu/Menu.js +93 -0
- package/components/Menu/Menu.lld.md +15 -0
- package/components/Menu/index.js +1 -0
- package/components/Notify/Notify.js +96 -0
- package/components/Notify/Notify.lld.md +20 -0
- package/components/Notify/index.js +1 -0
- package/components/Page/Page.js +67 -0
- package/components/Page/Page.lld.md +20 -0
- package/components/Page/index.js +1 -0
- package/components/Popover/Popover.js +175 -0
- package/components/Popover/Popover.lld.md +19 -0
- package/components/Popover/index.js +1 -0
- package/components/RadioButton/RadioButton.js +108 -0
- package/components/RadioButton/RadioButton.lld.md +15 -0
- package/components/RadioButton/index.js +1 -0
- package/components/Router/README.md +160 -0
- package/components/Router/Router.js +150 -0
- package/components/Router/Router.lld.md +31 -0
- package/components/Router/View.js +15 -0
- package/components/Router/index.js +2 -0
- package/components/Router/utils.js +17 -0
- package/components/Select/README.md +88 -0
- package/components/Select/Select.js +74 -0
- package/components/Select/Select.lld.md +20 -0
- package/components/Select/index.js +1 -0
- package/components/Table/README.md +94 -0
- package/components/Table/Table.js +171 -0
- package/components/Table/Table.lld.md +21 -0
- package/components/Table/index.js +1 -0
- package/components/TagList/Tag.js +84 -0
- package/components/TagList/TagList.js +118 -0
- package/components/TagList/TagList.lld.md +30 -0
- package/components/TagList/design.excalidraw.png +0 -0
- package/components/TagList/index.js +2 -0
- package/components/Tooltip/Tooltip.js +139 -0
- package/components/Tooltip/Tooltip.lld.md +22 -0
- package/components/Tooltip/index.js +1 -0
- package/components/TooltipWrapper/TooltipWrapper.js +89 -0
- package/components/TooltipWrapper/TooltipWrapper.lld.md +21 -0
- package/components/TooltipWrapper/index.js +1 -0
- package/components/Whiteboard/Whiteboard.js +198 -0
- package/components/Whiteboard/Whiteboard.lld.md +35 -0
- package/components/Whiteboard/index.js +1 -0
- package/components/index.js +27 -0
- package/eslint.config.cjs +118 -0
- package/index.d.ts +635 -0
- package/index.js +19 -0
- package/package.json +123 -0
- package/plugins/asText.js +38 -0
- package/plugins/loadPlugins.js +5 -0
- package/plugins/markdownLoader.js +121 -0
- package/prettier.config.cjs +7 -0
- package/spellcheck.config.cjs +227 -0
- package/styled/README.md +329 -0
- package/styled/appendStyles.js +26 -0
- package/styled/appendStyles.test.js +45 -0
- package/styled/index.js +4 -0
- package/styled/shimCSS.js +31 -0
- package/styled/shimCSS.test.js +103 -0
- package/styled/styled.js +91 -0
- package/styled/styled.test.js +586 -0
- package/styled/themeStyles.js +36 -0
- package/styled/themeStyles.test.js +135 -0
- package/test-setup.js +123 -0
- package/theme/.test.js +69 -0
- package/theme/README.md +607 -0
- package/theme/button.js +100 -0
- package/theme/code.js +123 -0
- package/theme/colors.js +42 -0
- package/theme/fonts.js +42 -0
- package/theme/index.js +33 -0
- package/theme/input.js +64 -0
- package/theme/page.js +208 -0
- package/theme/scrollbar.js +24 -0
- package/theme/table.js +53 -0
- package/utils/README.md +176 -0
- package/utils/browser.js +92 -0
- package/utils/class.js +30 -0
- package/utils/color.js +81 -0
- package/utils/data.js +164 -0
- package/utils/element.js +55 -0
- package/utils/index.js +7 -0
- package/utils/rand.js +12 -0
- package/utils/string.js +72 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Calendar
|
|
2
|
+
|
|
3
|
+
> ./Calendar.js
|
|
4
|
+
|
|
5
|
+
Multi-view calendar that renders the same event data differently depending on scale. The key design decision: `view` is an option like any other; changing it triggers a full re-render with the same events laid out for the new context. No view-specific state accumulates.
|
|
6
|
+
|
|
7
|
+
## Switching view re-renders layout without carrying forward view-specific state
|
|
8
|
+
|
|
9
|
+
- day, week, and month views have structurally different DOM; changing `view` produces a fresh render so residual state from the previous layout cannot persist
|
|
10
|
+
- does switching from month to day view reflect the events in the new layout?
|
|
11
|
+
|
|
12
|
+
## Day view resolves overlap so simultaneous events don't hide each other
|
|
13
|
+
|
|
14
|
+
- when events occupy the same time slot they share the horizontal space proportionally; events are never stacked invisibly
|
|
15
|
+
- do two events at the same time render side by side rather than overlapping?
|
|
16
|
+
|
|
17
|
+
## Date navigation handles year boundaries correctly
|
|
18
|
+
|
|
19
|
+
**method:** `navigateBackFromJanuary`
|
|
20
|
+
|
|
21
|
+
- navigating backward from January produces December of the prior year; the calendar does not produce invalid dates at month boundaries
|
|
22
|
+
- navigateBackFromJanuary() captures result → result.month === 11
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { Component } from '../../Component';
|
|
2
|
+
import { Elem } from '../../Elem';
|
|
3
|
+
import { styled } from '../../styled';
|
|
4
|
+
import { formatDuration, formatTime } from './utils';
|
|
5
|
+
|
|
6
|
+
const StyledEvent = styled(
|
|
7
|
+
Component,
|
|
8
|
+
({ colors }) => `
|
|
9
|
+
cursor: pointer;
|
|
10
|
+
color: ${colors.black};
|
|
11
|
+
background-color: ${colors.yellow};
|
|
12
|
+
|
|
13
|
+
& div {
|
|
14
|
+
pointer-events: none;
|
|
15
|
+
}
|
|
16
|
+
`,
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
export default class CalendarEvent {
|
|
20
|
+
constructor(data) {
|
|
21
|
+
if (typeof data !== 'object') throw new TypeError('CalendarEvent requires an object argument');
|
|
22
|
+
|
|
23
|
+
this.options = data;
|
|
24
|
+
|
|
25
|
+
this.at = new Date(data.at);
|
|
26
|
+
|
|
27
|
+
if (Number.isNaN(this.at.getTime())) throw new TypeError('CalendarEvent requires a valid date (options.at)');
|
|
28
|
+
|
|
29
|
+
this.label = data.label;
|
|
30
|
+
this.notes = data.notes;
|
|
31
|
+
this.color = data.color;
|
|
32
|
+
this.duration = data.duration;
|
|
33
|
+
this.recurring = data.recurring;
|
|
34
|
+
this.daily = data.daily;
|
|
35
|
+
this.whitelist = data.whitelist;
|
|
36
|
+
this.blacklist = data.blacklist;
|
|
37
|
+
this.weekdays = data.weekdays;
|
|
38
|
+
this.year = this.at.getFullYear();
|
|
39
|
+
this.month = this.at.getMonth() + 1;
|
|
40
|
+
this.day = this.at.getDate();
|
|
41
|
+
this.hour = this.at.getHours();
|
|
42
|
+
this.minute = this.at.getMinutes();
|
|
43
|
+
this.fullDate = `${this.month}/${this.day}/${this.year}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
render(type, container, calendar) {
|
|
47
|
+
const formattedTime = formatTime(this.at);
|
|
48
|
+
|
|
49
|
+
const handleSelectEvent = target => {
|
|
50
|
+
calendar.emit('selectEvent', { target, index: this.options.index });
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
this.eventElem = new StyledEvent({
|
|
54
|
+
data: { at: this.at },
|
|
55
|
+
className: 'event',
|
|
56
|
+
role: 'button',
|
|
57
|
+
tabindex: '0',
|
|
58
|
+
'aria-label': `${this.label}, ${formattedTime}${this.duration ? formatDuration(this.duration, ', ') : ''}`,
|
|
59
|
+
style: { backgroundColor: this.color },
|
|
60
|
+
append: [
|
|
61
|
+
new Elem({
|
|
62
|
+
className: 'time',
|
|
63
|
+
textContent: `${formattedTime}${this.duration ? formatDuration(this.duration, ', ') : ''}`,
|
|
64
|
+
}),
|
|
65
|
+
new Elem({ className: 'label', textContent: this.label }),
|
|
66
|
+
],
|
|
67
|
+
appendTo: container,
|
|
68
|
+
onPointerPress: ({ target }) => handleSelectEvent(target),
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
this.eventElem.on({
|
|
72
|
+
targetEvent: 'keydown',
|
|
73
|
+
callback: e => {
|
|
74
|
+
if (e.key === ' ') e.preventDefault();
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
this.eventElem.on({
|
|
79
|
+
targetEvent: 'keyup',
|
|
80
|
+
callback: e => {
|
|
81
|
+
if (e.key === ' ' || e.key === 'Enter') handleSelectEvent(e.target);
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
if (type === 'day') {
|
|
86
|
+
const gapPosition = this.at.getHours() * 2 + this.at.getMinutes() / 30;
|
|
87
|
+
const gapHeight = `${(this.duration ? Math.ceil(this.duration / 1000 / 60 / 30) : 1) * 3}em`;
|
|
88
|
+
const gapTop = `${gapPosition * 3}em`;
|
|
89
|
+
|
|
90
|
+
this.eventElem.elem.style.height = gapHeight;
|
|
91
|
+
this.eventElem.elem.style.top = gapTop;
|
|
92
|
+
this.eventElem.elem.style.left = '0';
|
|
93
|
+
this.eventElem.elem.style.zIndex = Math.floor(gapPosition);
|
|
94
|
+
|
|
95
|
+
if (this.duration) {
|
|
96
|
+
this.eventElem.elem.children[0].textContent = formattedTime + formatDuration(this.duration, ', ');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return this.eventElem.elem;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { capitalize } from '../../utils';
|
|
2
|
+
import { styled } from '../../styled';
|
|
3
|
+
import { Component } from '../../Component';
|
|
4
|
+
import { Button } from '../Button';
|
|
5
|
+
|
|
6
|
+
const Title = styled(
|
|
7
|
+
Component,
|
|
8
|
+
() => `
|
|
9
|
+
text-align: center;
|
|
10
|
+
`,
|
|
11
|
+
);
|
|
12
|
+
const Left = styled(
|
|
13
|
+
Component,
|
|
14
|
+
() => `
|
|
15
|
+
margin-left: 6px;
|
|
16
|
+
`,
|
|
17
|
+
);
|
|
18
|
+
const Right = styled(
|
|
19
|
+
Component,
|
|
20
|
+
() => `
|
|
21
|
+
margin-right: 6px;
|
|
22
|
+
`,
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
const VIEWS = ['day', 'week', 'month'];
|
|
26
|
+
|
|
27
|
+
class Toolbar extends Component {
|
|
28
|
+
constructor({ calendar, view, ...options } = {}, ...children) {
|
|
29
|
+
super(
|
|
30
|
+
{
|
|
31
|
+
...options,
|
|
32
|
+
style: {
|
|
33
|
+
display: 'flex',
|
|
34
|
+
justifyContent: 'space-between',
|
|
35
|
+
...options.style,
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
...children,
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
this.calendar = calendar;
|
|
42
|
+
this._currentView = view;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
build() {
|
|
46
|
+
new Left({
|
|
47
|
+
appendTo: this,
|
|
48
|
+
append: ['previous', 'today', 'next'].map(
|
|
49
|
+
action =>
|
|
50
|
+
new Button({
|
|
51
|
+
textContent: { next: '>', today: 'Today', previous: '<' }[action],
|
|
52
|
+
'aria-label': action,
|
|
53
|
+
onPointerPress: () => {
|
|
54
|
+
this.calendar[action]();
|
|
55
|
+
},
|
|
56
|
+
}),
|
|
57
|
+
),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
this.title = new Title({ appendTo: this });
|
|
61
|
+
|
|
62
|
+
new Right({
|
|
63
|
+
appendTo: this,
|
|
64
|
+
append: (this.options.views || VIEWS).map(
|
|
65
|
+
view =>
|
|
66
|
+
new Button({
|
|
67
|
+
addClass: [`set-${view}`, ...(view === this._currentView ? ['pressed'] : [])],
|
|
68
|
+
textContent: capitalize(view),
|
|
69
|
+
onPointerPress: () => {
|
|
70
|
+
this.calendar.setView(view);
|
|
71
|
+
},
|
|
72
|
+
}),
|
|
73
|
+
),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export default Toolbar;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export const getDaysInMonth = (year, month) => {
|
|
2
|
+
const isLeap = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
|
|
3
|
+
|
|
4
|
+
return {
|
|
5
|
+
numberOfDays: [31, isLeap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month],
|
|
6
|
+
firstDay: new Date(year, month).getDay(),
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const to12hour = hour => {
|
|
11
|
+
hour = hour < 12 ? hour : hour % 12;
|
|
12
|
+
|
|
13
|
+
if (hour === 0) hour = 12;
|
|
14
|
+
|
|
15
|
+
return hour;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// eslint-disable-next-line spellcheck/spell-checker
|
|
19
|
+
const nth = ['th', 'st', 'nd', 'rd'];
|
|
20
|
+
export const toNth = number => {
|
|
21
|
+
if (number > 3 && number < 21) return nth[0];
|
|
22
|
+
|
|
23
|
+
return nth[number.toString().slice(-1)] || nth[0];
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const formatTime = (date, options = {}) => {
|
|
27
|
+
let hours = date.getHours();
|
|
28
|
+
const minutes = date.getMinutes();
|
|
29
|
+
const seconds = date.getSeconds();
|
|
30
|
+
|
|
31
|
+
let am_pm = '';
|
|
32
|
+
|
|
33
|
+
if (!options.display24h) {
|
|
34
|
+
am_pm = hours < 12 ? 'AM' : 'PM';
|
|
35
|
+
hours = to12hour(hours);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
hours +
|
|
40
|
+
(minutes < 10 ? ':0' : ':') +
|
|
41
|
+
minutes +
|
|
42
|
+
(seconds && options.displaySeconds ? (seconds < 10 ? ':0' : ':') + seconds : '') +
|
|
43
|
+
am_pm
|
|
44
|
+
);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const formatDuration = (duration, append = '', prepend = '') => {
|
|
48
|
+
if (!duration) return '';
|
|
49
|
+
|
|
50
|
+
duration = Math.floor(duration / 1000 / 60);
|
|
51
|
+
|
|
52
|
+
const hours = Math.floor(duration / 60);
|
|
53
|
+
const minutes = duration % 60;
|
|
54
|
+
|
|
55
|
+
return append + (hours ? hours + 'h ' : '') + (minutes ? minutes + 'm' : '') + prepend;
|
|
56
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { copyToClipboard } from '../../utils';
|
|
2
|
+
import { Elem } from '../../Elem';
|
|
3
|
+
import { Component } from '../../Component';
|
|
4
|
+
import { Button } from '../Button';
|
|
5
|
+
import { Notify } from '../Notify';
|
|
6
|
+
|
|
7
|
+
const defaultOptions = { tag: 'code', language: 'javascript', multiline: 'auto' };
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Code display component with syntax highlighting and optional copy functionality.
|
|
11
|
+
*
|
|
12
|
+
* Renders code blocks with language-specific styling and automatic multiline detection.
|
|
13
|
+
* Supports inline code spans and multiline code blocks with optional copy-to-clipboard button.
|
|
14
|
+
* @param {object} [options={}] - Code component configuration options
|
|
15
|
+
* @param {string} [options.tag='code'] - HTML tag, automatically set to 'pre' for multiline code
|
|
16
|
+
* @param {string} [options.language='javascript'] - Programming language for syntax highlighting class
|
|
17
|
+
* @param {string|boolean} [options.multiline='auto'] - Whether to render as multiline block ('auto' detects newlines)
|
|
18
|
+
* @param {string} [options.code] - Code content to display
|
|
19
|
+
* @param {boolean} [options.copyButton] - Whether to show copy-to-clipboard button
|
|
20
|
+
* @param {...(Component|HTMLElement|string)} children - Child elements to append
|
|
21
|
+
* @returns {Code} Code component instance
|
|
22
|
+
*/
|
|
23
|
+
class Code extends Component {
|
|
24
|
+
defaultOptions = { ...super.defaultOptions, ...defaultOptions };
|
|
25
|
+
|
|
26
|
+
constructor(options = {}, ...children) {
|
|
27
|
+
if ((options.multiline || defaultOptions.multiline) === 'auto') {
|
|
28
|
+
options.multiline = (options.code || '').includes('\n');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
super(
|
|
32
|
+
{
|
|
33
|
+
...defaultOptions,
|
|
34
|
+
...options,
|
|
35
|
+
...(options.multiline ? { tag: 'pre' } : { tag: 'code' }),
|
|
36
|
+
},
|
|
37
|
+
...children,
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
build() {
|
|
42
|
+
if (this.options.multiline) {
|
|
43
|
+
this._code = new Elem({
|
|
44
|
+
tag: 'code',
|
|
45
|
+
textContent: this.options.code,
|
|
46
|
+
appendTo: this.elem,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (this.options.copyButton)
|
|
51
|
+
this.append(
|
|
52
|
+
new Button({
|
|
53
|
+
icon: 'copy',
|
|
54
|
+
tooltip: 'Copy to clipboard',
|
|
55
|
+
onPointerPress: () => {
|
|
56
|
+
const clipboardWriteSuccess = copyToClipboard(this.options.code);
|
|
57
|
+
|
|
58
|
+
if (clipboardWriteSuccess) {
|
|
59
|
+
const { right: x, top: y } = this.elem.getBoundingClientRect();
|
|
60
|
+
|
|
61
|
+
new Notify({ content: 'Copied text to clipboard!', timeout: 1000, x, y });
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
}),
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
static handlers = {
|
|
69
|
+
language(value) {
|
|
70
|
+
if (this.options.multiline && this._code) {
|
|
71
|
+
this._code.removeClass(/\blanguage-\S+\b/g);
|
|
72
|
+
this._code.addClass(`language-${value}`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
this.removeClass(/\blanguage-\S+\b/g);
|
|
76
|
+
this.addClass(`language-${value}`);
|
|
77
|
+
},
|
|
78
|
+
code(value) {
|
|
79
|
+
(this.options.multiline && this._code ? this._code : this).elem.textContent = value;
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export default Code;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Code
|
|
2
|
+
|
|
3
|
+
> ./Code.js
|
|
4
|
+
|
|
5
|
+
Code display that automatically promotes from inline to block when the content spans multiple lines. The decision: callers pass code and the component determines the appropriate element; callers never choose between `code` and `pre>code` themselves.
|
|
6
|
+
|
|
7
|
+
## Multiline content renders as a block element without the caller specifying it
|
|
8
|
+
|
|
9
|
+
- the component detects newlines at render time and promotes accordingly
|
|
10
|
+
- does code with newlines render as a block (pre) rather than an inline element?
|
|
11
|
+
- does single-line code render inline?
|
|
12
|
+
|
|
13
|
+
## Language class enables syntax highlighting libraries to identify the element
|
|
14
|
+
|
|
15
|
+
- the `language` option adds a `language-{name}` class; highlighting libraries target this class by convention
|
|
16
|
+
- does the language option add an identifiable class to the code element?
|
|
17
|
+
|
|
18
|
+
## Copy confirms success visually — clipboard writes can fail silently
|
|
19
|
+
|
|
20
|
+
- the copy button shows a notification on success so the user knows the copy worked rather than discovering it failed on paste
|
|
21
|
+
- does a successful copy show the user a confirmation?
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Code } from './Code';
|