@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.
Files changed (147) hide show
  1. package/Component/Component.js +598 -0
  2. package/Component/Component.scenarios.js +88 -0
  3. package/Component/Component.test.js +717 -0
  4. package/Component/README.md +455 -0
  5. package/Component/index.js +3 -0
  6. package/Component/observeElementConnection.js +52 -0
  7. package/Component/observeElementConnection.test.js +121 -0
  8. package/Elem/Elem.js +304 -0
  9. package/Elem/Elem.test.js +679 -0
  10. package/Elem/README.md +373 -0
  11. package/Elem/index.js +1 -0
  12. package/FontWithASyntaxHighlighter-Regular.woff2 +0 -0
  13. package/LICENSE +21 -0
  14. package/README.md +413 -0
  15. package/components/BottomSheet/BottomSheet.js +192 -0
  16. package/components/BottomSheet/BottomSheet.lld.md +25 -0
  17. package/components/BottomSheet/README.md +66 -0
  18. package/components/BottomSheet/index.js +1 -0
  19. package/components/Button/Button.js +53 -0
  20. package/components/Button/Button.lld.md +21 -0
  21. package/components/Button/index.js +1 -0
  22. package/components/Calendar/Calendar.js +720 -0
  23. package/components/Calendar/Calendar.lld.md +22 -0
  24. package/components/Calendar/CalendarEvent.js +102 -0
  25. package/components/Calendar/Toolbar.js +78 -0
  26. package/components/Calendar/index.js +2 -0
  27. package/components/Calendar/utils.js +56 -0
  28. package/components/Code/Code.js +84 -0
  29. package/components/Code/Code.lld.md +21 -0
  30. package/components/Code/index.js +1 -0
  31. package/components/ColorPicker/ColorPicker.js +445 -0
  32. package/components/ColorPicker/ColorPicker.lld.md +21 -0
  33. package/components/ColorPicker/index.js +1 -0
  34. package/components/ColorPicker/svg.js +5 -0
  35. package/components/Dialog/Dialog.js +278 -0
  36. package/components/Dialog/Dialog.lld.md +20 -0
  37. package/components/Dialog/README.md +96 -0
  38. package/components/Dialog/index.js +1 -0
  39. package/components/Form/Form.js +257 -0
  40. package/components/Form/Form.lld.md +21 -0
  41. package/components/Form/README.md +87 -0
  42. package/components/Form/index.js +1 -0
  43. package/components/Icon/Icon.js +54 -0
  44. package/components/Icon/Icon.lld.md +21 -0
  45. package/components/Icon/index.js +1 -0
  46. package/components/Input/Input.js +173 -0
  47. package/components/Input/Input.lld.md +28 -0
  48. package/components/Input/README.md +97 -0
  49. package/components/Input/index.js +2 -0
  50. package/components/Input/utils.js +122 -0
  51. package/components/Keyboard/Key.js +38 -0
  52. package/components/Keyboard/Keyboard.js +173 -0
  53. package/components/Keyboard/Keyboard.lld.md +21 -0
  54. package/components/Keyboard/index.js +1 -0
  55. package/components/Label/Label.js +214 -0
  56. package/components/Label/Label.lld.md +20 -0
  57. package/components/Label/index.js +1 -0
  58. package/components/Link/Link.js +43 -0
  59. package/components/Link/Link.lld.md +15 -0
  60. package/components/Link/index.js +1 -0
  61. package/components/List/List.js +82 -0
  62. package/components/List/List.lld.md +19 -0
  63. package/components/List/index.js +1 -0
  64. package/components/Menu/Menu.js +93 -0
  65. package/components/Menu/Menu.lld.md +15 -0
  66. package/components/Menu/index.js +1 -0
  67. package/components/Notify/Notify.js +96 -0
  68. package/components/Notify/Notify.lld.md +20 -0
  69. package/components/Notify/index.js +1 -0
  70. package/components/Page/Page.js +67 -0
  71. package/components/Page/Page.lld.md +20 -0
  72. package/components/Page/index.js +1 -0
  73. package/components/Popover/Popover.js +175 -0
  74. package/components/Popover/Popover.lld.md +19 -0
  75. package/components/Popover/index.js +1 -0
  76. package/components/RadioButton/RadioButton.js +108 -0
  77. package/components/RadioButton/RadioButton.lld.md +15 -0
  78. package/components/RadioButton/index.js +1 -0
  79. package/components/Router/README.md +160 -0
  80. package/components/Router/Router.js +150 -0
  81. package/components/Router/Router.lld.md +31 -0
  82. package/components/Router/View.js +15 -0
  83. package/components/Router/index.js +2 -0
  84. package/components/Router/utils.js +17 -0
  85. package/components/Select/README.md +88 -0
  86. package/components/Select/Select.js +74 -0
  87. package/components/Select/Select.lld.md +20 -0
  88. package/components/Select/index.js +1 -0
  89. package/components/Table/README.md +94 -0
  90. package/components/Table/Table.js +171 -0
  91. package/components/Table/Table.lld.md +21 -0
  92. package/components/Table/index.js +1 -0
  93. package/components/TagList/Tag.js +84 -0
  94. package/components/TagList/TagList.js +118 -0
  95. package/components/TagList/TagList.lld.md +30 -0
  96. package/components/TagList/design.excalidraw.png +0 -0
  97. package/components/TagList/index.js +2 -0
  98. package/components/Tooltip/Tooltip.js +139 -0
  99. package/components/Tooltip/Tooltip.lld.md +22 -0
  100. package/components/Tooltip/index.js +1 -0
  101. package/components/TooltipWrapper/TooltipWrapper.js +89 -0
  102. package/components/TooltipWrapper/TooltipWrapper.lld.md +21 -0
  103. package/components/TooltipWrapper/index.js +1 -0
  104. package/components/Whiteboard/Whiteboard.js +198 -0
  105. package/components/Whiteboard/Whiteboard.lld.md +35 -0
  106. package/components/Whiteboard/index.js +1 -0
  107. package/components/index.js +27 -0
  108. package/eslint.config.cjs +118 -0
  109. package/index.d.ts +635 -0
  110. package/index.js +19 -0
  111. package/package.json +123 -0
  112. package/plugins/asText.js +38 -0
  113. package/plugins/loadPlugins.js +5 -0
  114. package/plugins/markdownLoader.js +121 -0
  115. package/prettier.config.cjs +7 -0
  116. package/spellcheck.config.cjs +227 -0
  117. package/styled/README.md +329 -0
  118. package/styled/appendStyles.js +26 -0
  119. package/styled/appendStyles.test.js +45 -0
  120. package/styled/index.js +4 -0
  121. package/styled/shimCSS.js +31 -0
  122. package/styled/shimCSS.test.js +103 -0
  123. package/styled/styled.js +91 -0
  124. package/styled/styled.test.js +586 -0
  125. package/styled/themeStyles.js +36 -0
  126. package/styled/themeStyles.test.js +135 -0
  127. package/test-setup.js +123 -0
  128. package/theme/.test.js +69 -0
  129. package/theme/README.md +607 -0
  130. package/theme/button.js +100 -0
  131. package/theme/code.js +123 -0
  132. package/theme/colors.js +42 -0
  133. package/theme/fonts.js +42 -0
  134. package/theme/index.js +33 -0
  135. package/theme/input.js +64 -0
  136. package/theme/page.js +208 -0
  137. package/theme/scrollbar.js +24 -0
  138. package/theme/table.js +53 -0
  139. package/utils/README.md +176 -0
  140. package/utils/browser.js +92 -0
  141. package/utils/class.js +30 -0
  142. package/utils/color.js +81 -0
  143. package/utils/data.js +164 -0
  144. package/utils/element.js +55 -0
  145. package/utils/index.js +7 -0
  146. package/utils/rand.js +12 -0
  147. 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,2 @@
1
+ export { default as Calendar, DAYS, MONTHS } from './Calendar';
2
+ export * from './utils';
@@ -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';