jasmincss 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 (76) hide show
  1. package/README.md +524 -0
  2. package/bin/jasmin.js +45 -0
  3. package/dist/index.d.ts +62 -0
  4. package/dist/index.js +14568 -0
  5. package/dist/index.mjs +14524 -0
  6. package/dist/jasmin.css +63308 -0
  7. package/dist/jasmin.min.css +1 -0
  8. package/dist/plugins/nextjs.js +14777 -0
  9. package/dist/plugins/nextjs.mjs +14747 -0
  10. package/dist/plugins/vite.js +14889 -0
  11. package/dist/plugins/vite.mjs +14860 -0
  12. package/package.json +101 -0
  13. package/src/cli/add.js +83 -0
  14. package/src/cli/init.js +210 -0
  15. package/src/cli/run.js +142 -0
  16. package/src/components/accordion.js +309 -0
  17. package/src/components/alerts.js +357 -0
  18. package/src/components/avatars.js +331 -0
  19. package/src/components/badges.js +353 -0
  20. package/src/components/buttons.js +412 -0
  21. package/src/components/cards.js +342 -0
  22. package/src/components/carousel.js +495 -0
  23. package/src/components/chips.js +440 -0
  24. package/src/components/command-palette.js +434 -0
  25. package/src/components/datepicker.js +517 -0
  26. package/src/components/dropdown.js +411 -0
  27. package/src/components/forms.js +516 -0
  28. package/src/components/index.js +81 -0
  29. package/src/components/modals.js +349 -0
  30. package/src/components/navigation.js +463 -0
  31. package/src/components/offcanvas.js +390 -0
  32. package/src/components/popover.js +427 -0
  33. package/src/components/progress.js +396 -0
  34. package/src/components/rating.js +394 -0
  35. package/src/components/skeleton.js +408 -0
  36. package/src/components/spinner.js +453 -0
  37. package/src/components/stepper.js +389 -0
  38. package/src/components/tables.js +304 -0
  39. package/src/components/timeline.js +452 -0
  40. package/src/components/timepicker.js +529 -0
  41. package/src/components/tooltips.js +345 -0
  42. package/src/components/upload.js +490 -0
  43. package/src/config/defaults.js +263 -0
  44. package/src/config/loader.js +109 -0
  45. package/src/core/base.js +241 -0
  46. package/src/core/compiler.js +135 -0
  47. package/src/core/utilities/accessibility.js +290 -0
  48. package/src/core/utilities/animations.js +205 -0
  49. package/src/core/utilities/background.js +109 -0
  50. package/src/core/utilities/colors.js +234 -0
  51. package/src/core/utilities/columns.js +161 -0
  52. package/src/core/utilities/effects.js +261 -0
  53. package/src/core/utilities/filters.js +135 -0
  54. package/src/core/utilities/icons.js +806 -0
  55. package/src/core/utilities/index.js +239 -0
  56. package/src/core/utilities/layout.js +321 -0
  57. package/src/core/utilities/scroll.js +205 -0
  58. package/src/core/utilities/spacing.js +120 -0
  59. package/src/core/utilities/svg.js +191 -0
  60. package/src/core/utilities/transforms.js +116 -0
  61. package/src/core/utilities/typography.js +188 -0
  62. package/src/index.js +7 -0
  63. package/src/js/components/accordion.js +154 -0
  64. package/src/js/components/alert.js +198 -0
  65. package/src/js/components/carousel.js +226 -0
  66. package/src/js/components/dropdown.js +166 -0
  67. package/src/js/components/modal.js +169 -0
  68. package/src/js/components/offcanvas.js +175 -0
  69. package/src/js/components/popover.js +221 -0
  70. package/src/js/components/tabs.js +163 -0
  71. package/src/js/components/tooltip.js +200 -0
  72. package/src/js/index.js +79 -0
  73. package/src/js/types/config.d.ts +228 -0
  74. package/src/js/types/index.d.ts +439 -0
  75. package/src/plugins/nextjs.js +100 -0
  76. package/src/plugins/vite.js +133 -0
@@ -0,0 +1,175 @@
1
+ /**
2
+ * JasminCSS Offcanvas Component
3
+ * Handles slide-out panels/drawers with focus trapping
4
+ */
5
+
6
+ class Offcanvas {
7
+ constructor(element, options = {}) {
8
+ this.element = element;
9
+ this.isOpen = false;
10
+ this.previousActiveElement = null;
11
+ this.options = {
12
+ backdrop: true,
13
+ keyboard: true,
14
+ scroll: false, // Allow body scroll when open
15
+ ...options
16
+ };
17
+
18
+ this.backdrop = null;
19
+ this.init();
20
+ }
21
+
22
+ init() {
23
+ // Set ARIA attributes
24
+ this.element.setAttribute('role', 'dialog');
25
+ this.element.setAttribute('aria-modal', 'true');
26
+ this.element.setAttribute('tabindex', '-1');
27
+
28
+ // Close button handlers
29
+ this.element.querySelectorAll('[data-offcanvas-close], .offcanvas-close').forEach(btn => {
30
+ btn.addEventListener('click', () => this.close());
31
+ });
32
+
33
+ // Keyboard handling
34
+ this.element.addEventListener('keydown', (e) => {
35
+ if (e.key === 'Escape' && this.options.keyboard) {
36
+ this.close();
37
+ }
38
+ if (e.key === 'Tab') {
39
+ this.trapFocus(e);
40
+ }
41
+ });
42
+ }
43
+
44
+ open() {
45
+ if (this.isOpen) return;
46
+
47
+ this.previousActiveElement = document.activeElement;
48
+ this.isOpen = true;
49
+
50
+ // Create backdrop
51
+ if (this.options.backdrop) {
52
+ this.backdrop = document.createElement('div');
53
+ this.backdrop.className = 'offcanvas-backdrop';
54
+ document.body.appendChild(this.backdrop);
55
+
56
+ // Trigger reflow for animation
57
+ this.backdrop.offsetHeight;
58
+ this.backdrop.classList.add('show');
59
+
60
+ if (this.options.backdrop !== 'static') {
61
+ this.backdrop.addEventListener('click', () => this.close());
62
+ }
63
+ }
64
+
65
+ // Show offcanvas
66
+ this.element.classList.add('show');
67
+
68
+ // Body scroll
69
+ if (!this.options.scroll) {
70
+ document.body.classList.add('offcanvas-open');
71
+ document.body.style.overflow = 'hidden';
72
+ }
73
+
74
+ // Focus first focusable element
75
+ requestAnimationFrame(() => {
76
+ const focusable = this.getFocusableElements()[0];
77
+ (focusable || this.element).focus();
78
+ });
79
+
80
+ // Dispatch event
81
+ this.element.dispatchEvent(new CustomEvent('offcanvas:open', { detail: { offcanvas: this } }));
82
+ }
83
+
84
+ close() {
85
+ if (!this.isOpen) return;
86
+
87
+ this.isOpen = false;
88
+
89
+ // Remove backdrop
90
+ if (this.backdrop) {
91
+ this.backdrop.classList.remove('show');
92
+ setTimeout(() => {
93
+ this.backdrop?.remove();
94
+ this.backdrop = null;
95
+ }, 300);
96
+ }
97
+
98
+ // Hide offcanvas
99
+ this.element.classList.remove('show');
100
+
101
+ // Restore body scroll
102
+ document.body.classList.remove('offcanvas-open');
103
+ document.body.style.overflow = '';
104
+
105
+ // Restore focus
106
+ if (this.previousActiveElement) {
107
+ this.previousActiveElement.focus();
108
+ }
109
+
110
+ // Dispatch event
111
+ this.element.dispatchEvent(new CustomEvent('offcanvas:close', { detail: { offcanvas: this } }));
112
+ }
113
+
114
+ toggle() {
115
+ this.isOpen ? this.close() : this.open();
116
+ }
117
+
118
+ getFocusableElements() {
119
+ return Array.from(this.element.querySelectorAll(
120
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
121
+ )).filter(el => !el.disabled && el.offsetParent !== null);
122
+ }
123
+
124
+ trapFocus(e) {
125
+ const focusable = this.getFocusableElements();
126
+ const firstFocusable = focusable[0];
127
+ const lastFocusable = focusable[focusable.length - 1];
128
+
129
+ if (e.shiftKey) {
130
+ if (document.activeElement === firstFocusable) {
131
+ e.preventDefault();
132
+ lastFocusable.focus();
133
+ }
134
+ } else {
135
+ if (document.activeElement === lastFocusable) {
136
+ e.preventDefault();
137
+ firstFocusable.focus();
138
+ }
139
+ }
140
+ }
141
+
142
+ destroy() {
143
+ this.close();
144
+ }
145
+
146
+ // Static methods
147
+ static initAll(selector = '[data-offcanvas]') {
148
+ document.querySelectorAll(selector).forEach(el => {
149
+ if (!el._jasminOffcanvas) {
150
+ el._jasminOffcanvas = new Offcanvas(el, {
151
+ backdrop: el.dataset.offcanvasBackdrop !== 'false',
152
+ keyboard: el.dataset.offcanvasKeyboard !== 'false',
153
+ scroll: el.dataset.offcanvasScroll === 'true'
154
+ });
155
+ }
156
+ });
157
+
158
+ // Init triggers
159
+ document.querySelectorAll('[data-offcanvas-target]').forEach(trigger => {
160
+ trigger.addEventListener('click', (e) => {
161
+ e.preventDefault();
162
+ const target = document.querySelector(trigger.dataset.offcanvasTarget);
163
+ if (target?._jasminOffcanvas) {
164
+ target._jasminOffcanvas.toggle();
165
+ }
166
+ });
167
+ });
168
+ }
169
+
170
+ static getInstance(element) {
171
+ return element._jasminOffcanvas;
172
+ }
173
+ }
174
+
175
+ export default Offcanvas;
@@ -0,0 +1,221 @@
1
+ /**
2
+ * JasminCSS Popover Component
3
+ * Handles popovers with rich content and positioning
4
+ */
5
+
6
+ class Popover {
7
+ constructor(element, options = {}) {
8
+ this.element = element;
9
+ this.options = {
10
+ placement: 'top',
11
+ trigger: 'click',
12
+ title: '',
13
+ content: '',
14
+ html: false,
15
+ ...options
16
+ };
17
+
18
+ this.popover = null;
19
+ this.isVisible = false;
20
+
21
+ this.init();
22
+ }
23
+
24
+ init() {
25
+ // Get content from data attributes or options
26
+ this.options.title = this.element.dataset.popoverTitle || this.options.title;
27
+ this.options.content = this.element.dataset.popoverContent || this.options.content;
28
+
29
+ // Bind events based on trigger
30
+ const triggers = this.options.trigger.split(' ');
31
+
32
+ if (triggers.includes('click')) {
33
+ this.element.addEventListener('click', (e) => {
34
+ e.preventDefault();
35
+ e.stopPropagation();
36
+ this.toggle();
37
+ });
38
+
39
+ // Close on outside click
40
+ document.addEventListener('click', (e) => {
41
+ if (this.isVisible && !this.popover?.contains(e.target)) {
42
+ this.hide();
43
+ }
44
+ });
45
+ }
46
+
47
+ if (triggers.includes('hover')) {
48
+ this.element.addEventListener('mouseenter', () => this.show());
49
+ this.element.addEventListener('mouseleave', () => this.hide());
50
+ }
51
+
52
+ if (triggers.includes('focus')) {
53
+ this.element.addEventListener('focus', () => this.show());
54
+ this.element.addEventListener('blur', () => this.hide());
55
+ }
56
+
57
+ // Close on escape
58
+ document.addEventListener('keydown', (e) => {
59
+ if (e.key === 'Escape' && this.isVisible) {
60
+ this.hide();
61
+ this.element.focus();
62
+ }
63
+ });
64
+ }
65
+
66
+ createPopover() {
67
+ this.popover = document.createElement('div');
68
+ this.popover.className = `popover popover-${this.options.placement}`;
69
+ this.popover.setAttribute('role', 'tooltip');
70
+ this.popover.id = `popover-${Date.now()}`;
71
+ this.popover.dataset.placement = this.options.placement;
72
+
73
+ // Header
74
+ if (this.options.title) {
75
+ const header = document.createElement('div');
76
+ header.className = 'popover-header';
77
+ if (this.options.html) {
78
+ header.innerHTML = this.options.title;
79
+ } else {
80
+ header.textContent = this.options.title;
81
+ }
82
+ this.popover.appendChild(header);
83
+ }
84
+
85
+ // Body
86
+ const body = document.createElement('div');
87
+ body.className = 'popover-body';
88
+ if (this.options.html) {
89
+ body.innerHTML = this.options.content;
90
+ } else {
91
+ body.textContent = this.options.content;
92
+ }
93
+ this.popover.appendChild(body);
94
+
95
+ // Arrow
96
+ const arrow = document.createElement('div');
97
+ arrow.className = 'popover-arrow';
98
+ this.popover.appendChild(arrow);
99
+
100
+ document.body.appendChild(this.popover);
101
+ this.element.setAttribute('aria-describedby', this.popover.id);
102
+
103
+ this.position();
104
+ }
105
+
106
+ position() {
107
+ if (!this.popover) return;
108
+
109
+ const rect = this.element.getBoundingClientRect();
110
+ const popoverRect = this.popover.getBoundingClientRect();
111
+ const scrollY = window.scrollY;
112
+ const scrollX = window.scrollX;
113
+
114
+ let top, left;
115
+
116
+ switch (this.options.placement) {
117
+ case 'top':
118
+ top = rect.top + scrollY - popoverRect.height - 10;
119
+ left = rect.left + scrollX + (rect.width - popoverRect.width) / 2;
120
+ break;
121
+ case 'bottom':
122
+ top = rect.bottom + scrollY + 10;
123
+ left = rect.left + scrollX + (rect.width - popoverRect.width) / 2;
124
+ break;
125
+ case 'left':
126
+ top = rect.top + scrollY + (rect.height - popoverRect.height) / 2;
127
+ left = rect.left + scrollX - popoverRect.width - 10;
128
+ break;
129
+ case 'right':
130
+ top = rect.top + scrollY + (rect.height - popoverRect.height) / 2;
131
+ left = rect.right + scrollX + 10;
132
+ break;
133
+ }
134
+
135
+ // Boundary checks
136
+ if (left < 8) left = 8;
137
+ if (left + popoverRect.width > window.innerWidth - 8) {
138
+ left = window.innerWidth - popoverRect.width - 8;
139
+ }
140
+ if (top < 8) top = 8;
141
+
142
+ this.popover.style.top = `${top}px`;
143
+ this.popover.style.left = `${left}px`;
144
+ }
145
+
146
+ show() {
147
+ if (!this.popover) {
148
+ this.createPopover();
149
+ }
150
+
151
+ // Small delay for positioning
152
+ requestAnimationFrame(() => {
153
+ this.popover.classList.add('show');
154
+ this.isVisible = true;
155
+ this.element.dispatchEvent(new CustomEvent('popover:show', { detail: { popover: this } }));
156
+ });
157
+ }
158
+
159
+ hide() {
160
+ if (this.popover) {
161
+ this.popover.classList.remove('show');
162
+
163
+ setTimeout(() => {
164
+ this.popover?.remove();
165
+ this.popover = null;
166
+ this.element.setAttribute('aria-describedby', '');
167
+ }, 150);
168
+ }
169
+
170
+ this.isVisible = false;
171
+ this.element.dispatchEvent(new CustomEvent('popover:hide', { detail: { popover: this } }));
172
+ }
173
+
174
+ toggle() {
175
+ this.isVisible ? this.hide() : this.show();
176
+ }
177
+
178
+ updateContent(title, content) {
179
+ this.options.title = title;
180
+ this.options.content = content;
181
+
182
+ if (this.popover) {
183
+ const header = this.popover.querySelector('.popover-header');
184
+ const body = this.popover.querySelector('.popover-body');
185
+
186
+ if (header) {
187
+ this.options.html ? (header.innerHTML = title) : (header.textContent = title);
188
+ }
189
+ if (body) {
190
+ this.options.html ? (body.innerHTML = content) : (body.textContent = content);
191
+ }
192
+
193
+ this.position();
194
+ }
195
+ }
196
+
197
+ destroy() {
198
+ this.hide();
199
+ }
200
+
201
+ // Static methods
202
+ static initAll(selector = '[data-popover]') {
203
+ document.querySelectorAll(selector).forEach(el => {
204
+ if (!el._jasminPopover) {
205
+ el._jasminPopover = new Popover(el, {
206
+ placement: el.dataset.popoverPlacement || 'top',
207
+ trigger: el.dataset.popoverTrigger || 'click',
208
+ title: el.dataset.popoverTitle || '',
209
+ content: el.dataset.popoverContent || '',
210
+ html: el.dataset.popoverHtml === 'true'
211
+ });
212
+ }
213
+ });
214
+ }
215
+
216
+ static getInstance(element) {
217
+ return element._jasminPopover;
218
+ }
219
+ }
220
+
221
+ export default Popover;
@@ -0,0 +1,163 @@
1
+ /**
2
+ * JasminCSS Tabs Component
3
+ * Handles tab navigation with keyboard support and accessibility
4
+ */
5
+
6
+ class Tabs {
7
+ constructor(element, options = {}) {
8
+ this.element = element;
9
+ this.options = {
10
+ orientation: 'horizontal', // 'horizontal' or 'vertical'
11
+ ...options
12
+ };
13
+ this.tabs = [];
14
+ this.panels = [];
15
+ this.activeIndex = 0;
16
+
17
+ this.init();
18
+ }
19
+
20
+ init() {
21
+ const tabList = this.element.querySelector('[role="tablist"], .tabs-list, .nav-tabs');
22
+ this.tabs = Array.from(this.element.querySelectorAll('[role="tab"], .tab, .nav-link'));
23
+ this.panels = Array.from(this.element.querySelectorAll('[role="tabpanel"], .tab-panel, .tab-content > *'));
24
+
25
+ if (tabList) {
26
+ tabList.setAttribute('role', 'tablist');
27
+ tabList.setAttribute('aria-orientation', this.options.orientation);
28
+ }
29
+
30
+ this.tabs.forEach((tab, index) => {
31
+ const panel = this.panels[index];
32
+ if (!panel) return;
33
+
34
+ // Generate IDs
35
+ const tabId = tab.id || `tab-${Date.now()}-${index}`;
36
+ const panelId = panel.id || `tabpanel-${Date.now()}-${index}`;
37
+
38
+ tab.id = tabId;
39
+ panel.id = panelId;
40
+
41
+ // Set ARIA attributes
42
+ tab.setAttribute('role', 'tab');
43
+ tab.setAttribute('aria-controls', panelId);
44
+ tab.setAttribute('tabindex', index === 0 ? '0' : '-1');
45
+
46
+ panel.setAttribute('role', 'tabpanel');
47
+ panel.setAttribute('aria-labelledby', tabId);
48
+ panel.setAttribute('tabindex', '0');
49
+
50
+ // Determine initial active tab
51
+ if (tab.classList.contains('active') || tab.getAttribute('aria-selected') === 'true') {
52
+ this.activeIndex = index;
53
+ }
54
+
55
+ // Click handler
56
+ tab.addEventListener('click', (e) => {
57
+ e.preventDefault();
58
+ this.selectTab(index);
59
+ });
60
+
61
+ // Keyboard handler
62
+ tab.addEventListener('keydown', (e) => this.handleKeydown(e, index));
63
+ });
64
+
65
+ // Set initial state
66
+ this.selectTab(this.activeIndex, false);
67
+ }
68
+
69
+ selectTab(index, focus = true) {
70
+ if (index < 0 || index >= this.tabs.length) return;
71
+
72
+ // Deactivate all
73
+ this.tabs.forEach((tab, i) => {
74
+ tab.setAttribute('aria-selected', 'false');
75
+ tab.setAttribute('tabindex', '-1');
76
+ tab.classList.remove('active');
77
+
78
+ if (this.panels[i]) {
79
+ this.panels[i].classList.remove('active');
80
+ this.panels[i].hidden = true;
81
+ }
82
+ });
83
+
84
+ // Activate selected
85
+ const selectedTab = this.tabs[index];
86
+ const selectedPanel = this.panels[index];
87
+
88
+ selectedTab.setAttribute('aria-selected', 'true');
89
+ selectedTab.setAttribute('tabindex', '0');
90
+ selectedTab.classList.add('active');
91
+
92
+ if (selectedPanel) {
93
+ selectedPanel.classList.add('active');
94
+ selectedPanel.hidden = false;
95
+ }
96
+
97
+ this.activeIndex = index;
98
+
99
+ if (focus) {
100
+ selectedTab.focus();
101
+ }
102
+
103
+ // Dispatch event
104
+ this.element.dispatchEvent(new CustomEvent('tabs:change', {
105
+ detail: { index, tab: selectedTab, panel: selectedPanel }
106
+ }));
107
+ }
108
+
109
+ handleKeydown(e, index) {
110
+ const isVertical = this.options.orientation === 'vertical';
111
+ const prevKey = isVertical ? 'ArrowUp' : 'ArrowLeft';
112
+ const nextKey = isVertical ? 'ArrowDown' : 'ArrowRight';
113
+
114
+ switch (e.key) {
115
+ case nextKey:
116
+ e.preventDefault();
117
+ const nextIndex = index < this.tabs.length - 1 ? index + 1 : 0;
118
+ this.selectTab(nextIndex);
119
+ break;
120
+
121
+ case prevKey:
122
+ e.preventDefault();
123
+ const prevIndex = index > 0 ? index - 1 : this.tabs.length - 1;
124
+ this.selectTab(prevIndex);
125
+ break;
126
+
127
+ case 'Home':
128
+ e.preventDefault();
129
+ this.selectTab(0);
130
+ break;
131
+
132
+ case 'End':
133
+ e.preventDefault();
134
+ this.selectTab(this.tabs.length - 1);
135
+ break;
136
+ }
137
+ }
138
+
139
+ destroy() {
140
+ this.tabs.forEach(tab => {
141
+ tab.removeAttribute('aria-selected');
142
+ tab.removeAttribute('aria-controls');
143
+ tab.removeAttribute('tabindex');
144
+ });
145
+ }
146
+
147
+ // Static methods
148
+ static initAll(selector = '[data-tabs]') {
149
+ document.querySelectorAll(selector).forEach(el => {
150
+ if (!el._jasminTabs) {
151
+ el._jasminTabs = new Tabs(el, {
152
+ orientation: el.dataset.tabsOrientation || 'horizontal'
153
+ });
154
+ }
155
+ });
156
+ }
157
+
158
+ static getInstance(element) {
159
+ return element._jasminTabs;
160
+ }
161
+ }
162
+
163
+ export default Tabs;