zenkit-css 1.0.3 → 1.0.5

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/README.md CHANGED
@@ -1,15 +1,18 @@
1
1
  <p align="center">
2
- <a href="https://sayedabdulkarim.github.io/-zenkit-css/docs/">
3
- <img src="https://raw.githubusercontent.com/sayedabdulkarim/-zenkit-css/main/docs/zenkit-logo.svg" alt="ZenKit" width="120" height="120">
2
+ <a href="https://sayedabdulkarim.github.io/-zenkit-css/">
3
+ <picture>
4
+ <source media="(prefers-color-scheme: dark)" srcset="https://cdn.jsdelivr.net/npm/zenkit-css@latest/docs/zenkit-logo.svg">
5
+ <img src="https://cdn.jsdelivr.net/npm/zenkit-css@latest/docs/zenkit-logo.svg" alt="ZenKit" width="120">
6
+ </picture>
4
7
  </a>
5
8
  </p>
6
9
 
7
10
  <h3 align="center">ZenKit</h3>
8
11
 
9
12
  <p align="center">
10
- A minimal, modern CSS framework for peaceful development.
13
+ Sleek, intuitive, and lightweight CSS framework for faster web development.
11
14
  <br>
12
- <a href="https://sayedabdulkarim.github.io/-zenkit-css/docs/"><strong>Explore ZenKit docs »</strong></a>
15
+ <a href="https://sayedabdulkarim.github.io/-zenkit-css/"><strong>Explore ZenKit docs »</strong></a>
13
16
  <br>
14
17
  <br>
15
18
  <a href="https://github.com/sayedabdulkarim/-zenkit-css/issues/new?labels=bug">Report bug</a>
@@ -0,0 +1,14 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" width="200" height="200">
2
+ <defs>
3
+ <linearGradient id="zenGradient" x1="0%" y1="0%" x2="100%" y2="100%">
4
+ <stop offset="0%" style="stop-color:#818CF8;stop-opacity:1" />
5
+ <stop offset="100%" style="stop-color:#6366F1;stop-opacity:1" />
6
+ </linearGradient>
7
+ </defs>
8
+
9
+ <!-- Background rounded square -->
10
+ <rect x="20" y="20" width="160" height="160" rx="32" fill="url(#zenGradient)"/>
11
+
12
+ <!-- Z letter -->
13
+ <path d="M60 65 L140 65 L140 80 L85 135 L140 135 L140 150 L60 150 L60 135 L115 80 L60 80 Z" fill="white"/>
14
+ </svg>
@@ -0,0 +1,119 @@
1
+ // ========================================
2
+ // ZenKit - Accordion Component
3
+ // ========================================
4
+
5
+ class Accordion {
6
+ constructor(element, options = {}) {
7
+ this.element = typeof element === 'string' ? document.querySelector(element) : element;
8
+ if (!this.element) return;
9
+
10
+ this.options = {
11
+ alwaysOpen: false,
12
+ ...options
13
+ };
14
+
15
+ this.items = this.element.querySelectorAll('.accordion-item');
16
+ this.init();
17
+ }
18
+
19
+ init() {
20
+ this.items.forEach(item => {
21
+ const button = item.querySelector('.accordion-button');
22
+ const collapse = item.querySelector('.accordion-collapse');
23
+
24
+ if (button && collapse) {
25
+ button.addEventListener('click', (e) => this.toggle(e, item));
26
+ }
27
+ });
28
+ }
29
+
30
+ toggle(event, item) {
31
+ event.preventDefault();
32
+ const button = item.querySelector('.accordion-button');
33
+ const collapse = item.querySelector('.accordion-collapse');
34
+ const isOpen = collapse.classList.contains('show');
35
+
36
+ if (!this.options.alwaysOpen) {
37
+ // Close all other items
38
+ this.items.forEach(otherItem => {
39
+ if (otherItem !== item) {
40
+ this.close(otherItem);
41
+ }
42
+ });
43
+ }
44
+
45
+ if (isOpen) {
46
+ this.close(item);
47
+ } else {
48
+ this.open(item);
49
+ }
50
+ }
51
+
52
+ open(item) {
53
+ const button = item.querySelector('.accordion-button');
54
+ const collapse = item.querySelector('.accordion-collapse');
55
+
56
+ button.classList.remove('collapsed');
57
+ button.setAttribute('aria-expanded', 'true');
58
+
59
+ collapse.classList.add('collapsing');
60
+ collapse.style.height = '0px';
61
+
62
+ requestAnimationFrame(() => {
63
+ collapse.style.height = collapse.scrollHeight + 'px';
64
+ });
65
+
66
+ const transitionEnd = () => {
67
+ collapse.classList.remove('collapsing');
68
+ collapse.classList.add('show', 'collapse');
69
+ collapse.style.height = '';
70
+ collapse.removeEventListener('transitionend', transitionEnd);
71
+
72
+ // Dispatch event
73
+ this.element.dispatchEvent(new CustomEvent('shown.zk.accordion', {
74
+ detail: { item }
75
+ }));
76
+ };
77
+
78
+ collapse.addEventListener('transitionend', transitionEnd);
79
+ }
80
+
81
+ close(item) {
82
+ const button = item.querySelector('.accordion-button');
83
+ const collapse = item.querySelector('.accordion-collapse');
84
+
85
+ if (!collapse.classList.contains('show')) return;
86
+
87
+ button.classList.add('collapsed');
88
+ button.setAttribute('aria-expanded', 'false');
89
+
90
+ collapse.style.height = collapse.scrollHeight + 'px';
91
+ collapse.classList.remove('show');
92
+
93
+ requestAnimationFrame(() => {
94
+ collapse.classList.add('collapsing');
95
+ collapse.style.height = '0px';
96
+ });
97
+
98
+ const transitionEnd = () => {
99
+ collapse.classList.remove('collapsing');
100
+ collapse.classList.add('collapse');
101
+ collapse.style.height = '';
102
+ collapse.removeEventListener('transitionend', transitionEnd);
103
+
104
+ // Dispatch event
105
+ this.element.dispatchEvent(new CustomEvent('hidden.zk.accordion', {
106
+ detail: { item }
107
+ }));
108
+ };
109
+
110
+ collapse.addEventListener('transitionend', transitionEnd);
111
+ }
112
+
113
+ // Static method to initialize all accordions
114
+ static init(selector = '.accordion') {
115
+ document.querySelectorAll(selector).forEach(el => new Accordion(el));
116
+ }
117
+ }
118
+
119
+ export default Accordion;
@@ -0,0 +1,281 @@
1
+ // ========================================
2
+ // ZenKit - Carousel Component
3
+ // ========================================
4
+
5
+ class Carousel {
6
+ constructor(element, options = {}) {
7
+ this.element = typeof element === 'string' ? document.querySelector(element) : element;
8
+ if (!this.element) return;
9
+
10
+ this.options = {
11
+ interval: 5000,
12
+ keyboard: true,
13
+ pause: 'hover',
14
+ ride: false,
15
+ wrap: true,
16
+ touch: true,
17
+ ...options
18
+ };
19
+
20
+ this.inner = this.element.querySelector('.carousel-inner');
21
+ this.items = this.element.querySelectorAll('.carousel-item');
22
+ this.indicators = this.element.querySelectorAll('.carousel-indicators [data-slide-to]');
23
+
24
+ this.activeIndex = 0;
25
+ this.isSliding = false;
26
+ this.interval = null;
27
+ this.touchStartX = 0;
28
+ this.touchStartY = 0;
29
+
30
+ this.init();
31
+ }
32
+
33
+ init() {
34
+ // Find initial active index
35
+ this.items.forEach((item, index) => {
36
+ if (item.classList.contains('active')) {
37
+ this.activeIndex = index;
38
+ }
39
+ });
40
+
41
+ // Controls
42
+ const prevBtn = this.element.querySelector('.carousel-control-prev');
43
+ const nextBtn = this.element.querySelector('.carousel-control-next');
44
+
45
+ if (prevBtn) {
46
+ prevBtn.addEventListener('click', (e) => {
47
+ e.preventDefault();
48
+ this.prev();
49
+ });
50
+ }
51
+
52
+ if (nextBtn) {
53
+ nextBtn.addEventListener('click', (e) => {
54
+ e.preventDefault();
55
+ this.next();
56
+ });
57
+ }
58
+
59
+ // Indicators
60
+ this.indicators.forEach((indicator, index) => {
61
+ indicator.addEventListener('click', (e) => {
62
+ e.preventDefault();
63
+ this.goTo(index);
64
+ });
65
+ });
66
+
67
+ // Keyboard
68
+ if (this.options.keyboard) {
69
+ this.element.addEventListener('keydown', (e) => this.handleKeydown(e));
70
+ }
71
+
72
+ // Pause on hover
73
+ if (this.options.pause === 'hover') {
74
+ this.element.addEventListener('mouseenter', () => this.pause());
75
+ this.element.addEventListener('mouseleave', () => this.cycle());
76
+ }
77
+
78
+ // Touch support
79
+ if (this.options.touch) {
80
+ this.element.addEventListener('touchstart', (e) => this.handleTouchStart(e), { passive: true });
81
+ this.element.addEventListener('touchend', (e) => this.handleTouchEnd(e), { passive: true });
82
+ }
83
+
84
+ // Auto-play
85
+ if (this.options.ride === 'carousel' || this.options.ride === true) {
86
+ this.cycle();
87
+ }
88
+ }
89
+
90
+ next() {
91
+ if (!this.isSliding) {
92
+ this.slide('next');
93
+ }
94
+ }
95
+
96
+ prev() {
97
+ if (!this.isSliding) {
98
+ this.slide('prev');
99
+ }
100
+ }
101
+
102
+ goTo(index) {
103
+ if (this.isSliding || index === this.activeIndex) return;
104
+
105
+ const direction = index > this.activeIndex ? 'next' : 'prev';
106
+ this.slide(direction, index);
107
+ }
108
+
109
+ slide(direction, targetIndex = null) {
110
+ const activeItem = this.items[this.activeIndex];
111
+ let nextIndex;
112
+
113
+ if (targetIndex !== null) {
114
+ nextIndex = targetIndex;
115
+ } else if (direction === 'next') {
116
+ nextIndex = this.activeIndex + 1;
117
+ if (nextIndex >= this.items.length) {
118
+ if (!this.options.wrap) return;
119
+ nextIndex = 0;
120
+ }
121
+ } else {
122
+ nextIndex = this.activeIndex - 1;
123
+ if (nextIndex < 0) {
124
+ if (!this.options.wrap) return;
125
+ nextIndex = this.items.length - 1;
126
+ }
127
+ }
128
+
129
+ const nextItem = this.items[nextIndex];
130
+ if (!nextItem) return;
131
+
132
+ const slideEvent = new CustomEvent('slide.zk.carousel', {
133
+ detail: { from: this.activeIndex, to: nextIndex, direction }
134
+ });
135
+ this.element.dispatchEvent(slideEvent);
136
+ if (slideEvent.defaultPrevented) return;
137
+
138
+ this.isSliding = true;
139
+
140
+ // Add positioning classes
141
+ const directionalClass = direction === 'next' ? 'carousel-item-start' : 'carousel-item-end';
142
+ const orderClass = direction === 'next' ? 'carousel-item-next' : 'carousel-item-prev';
143
+
144
+ nextItem.classList.add(orderClass);
145
+
146
+ // Force reflow
147
+ nextItem.offsetHeight;
148
+
149
+ activeItem.classList.add(directionalClass);
150
+ nextItem.classList.add(directionalClass);
151
+
152
+ const complete = () => {
153
+ nextItem.classList.remove(directionalClass, orderClass);
154
+ nextItem.classList.add('active');
155
+
156
+ activeItem.classList.remove('active', directionalClass, orderClass);
157
+
158
+ this.isSliding = false;
159
+ this.activeIndex = nextIndex;
160
+ this.updateIndicators();
161
+
162
+ this.element.dispatchEvent(new CustomEvent('slid.zk.carousel', {
163
+ detail: { from: this.activeIndex, to: nextIndex, direction }
164
+ }));
165
+ };
166
+
167
+ activeItem.addEventListener('transitionend', complete, { once: true });
168
+
169
+ // Fallback timeout
170
+ setTimeout(() => {
171
+ if (this.isSliding) {
172
+ complete();
173
+ }
174
+ }, 600);
175
+ }
176
+
177
+ updateIndicators() {
178
+ this.indicators.forEach((indicator, index) => {
179
+ indicator.classList.toggle('active', index === this.activeIndex);
180
+ });
181
+ }
182
+
183
+ cycle() {
184
+ if (this.interval) {
185
+ clearInterval(this.interval);
186
+ }
187
+
188
+ if (this.options.interval) {
189
+ this.interval = setInterval(() => this.next(), this.options.interval);
190
+ }
191
+ }
192
+
193
+ pause() {
194
+ if (this.interval) {
195
+ clearInterval(this.interval);
196
+ this.interval = null;
197
+ }
198
+ }
199
+
200
+ handleKeydown(event) {
201
+ if (/input|textarea/i.test(event.target.tagName)) return;
202
+
203
+ switch (event.key) {
204
+ case 'ArrowLeft':
205
+ event.preventDefault();
206
+ this.prev();
207
+ break;
208
+ case 'ArrowRight':
209
+ event.preventDefault();
210
+ this.next();
211
+ break;
212
+ }
213
+ }
214
+
215
+ handleTouchStart(event) {
216
+ this.touchStartX = event.touches[0].clientX;
217
+ this.touchStartY = event.touches[0].clientY;
218
+ }
219
+
220
+ handleTouchEnd(event) {
221
+ const touchEndX = event.changedTouches[0].clientX;
222
+ const touchEndY = event.changedTouches[0].clientY;
223
+
224
+ const deltaX = touchEndX - this.touchStartX;
225
+ const deltaY = touchEndY - this.touchStartY;
226
+
227
+ // Only handle horizontal swipes
228
+ if (Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) > 50) {
229
+ if (deltaX > 0) {
230
+ this.prev();
231
+ } else {
232
+ this.next();
233
+ }
234
+ }
235
+ }
236
+
237
+ dispose() {
238
+ this.pause();
239
+ Carousel.instances.delete(this.element);
240
+ }
241
+
242
+ nextWhenVisible() {
243
+ // Only cycle to next when the page is visible
244
+ if (!document.hidden && this.element.offsetWidth > 0 && this.element.offsetHeight > 0) {
245
+ this.next();
246
+ }
247
+ }
248
+
249
+ to(index) {
250
+ // Alias for goTo for Bootstrap compatibility
251
+ this.goTo(index);
252
+ }
253
+
254
+ // Static methods
255
+ static instances = new WeakMap();
256
+
257
+ static getInstance(element) {
258
+ return Carousel.instances.get(element);
259
+ }
260
+
261
+ static getOrCreateInstance(element, options) {
262
+ return Carousel.getInstance(element) || new Carousel(element, options);
263
+ }
264
+
265
+ static init(selector = '.carousel') {
266
+ document.querySelectorAll(selector).forEach(el => {
267
+ const options = {
268
+ interval: el.dataset.interval ? parseInt(el.dataset.interval) : 5000,
269
+ keyboard: el.dataset.keyboard !== 'false',
270
+ pause: el.dataset.pause || 'hover',
271
+ ride: el.dataset.ride || false,
272
+ wrap: el.dataset.wrap !== 'false',
273
+ touch: el.dataset.touch !== 'false'
274
+ };
275
+ const instance = new Carousel(el, options);
276
+ Carousel.instances.set(el, instance);
277
+ });
278
+ }
279
+ }
280
+
281
+ export default Carousel;
@@ -0,0 +1,145 @@
1
+ // ========================================
2
+ // ZenKit - Collapse Component
3
+ // ========================================
4
+
5
+ class Collapse {
6
+ constructor(element, options = {}) {
7
+ this.element = typeof element === 'string' ? document.querySelector(element) : element;
8
+ if (!this.element) return;
9
+
10
+ this.options = {
11
+ toggle: true,
12
+ parent: null,
13
+ ...options
14
+ };
15
+
16
+ this.isTransitioning = false;
17
+ this.triggerElements = document.querySelectorAll(
18
+ `[data-toggle="collapse"][data-target="#${this.element.id}"],` +
19
+ `[data-toggle="collapse"][href="#${this.element.id}"]`
20
+ );
21
+
22
+ this.init();
23
+ }
24
+
25
+ init() {
26
+ this.triggerElements.forEach(trigger => {
27
+ trigger.addEventListener('click', (e) => {
28
+ e.preventDefault();
29
+ this.toggle();
30
+ });
31
+ });
32
+
33
+ if (this.options.toggle) {
34
+ // Already handled by trigger elements
35
+ }
36
+ }
37
+
38
+ toggle() {
39
+ if (this.element.classList.contains('show')) {
40
+ this.hide();
41
+ } else {
42
+ this.show();
43
+ }
44
+ }
45
+
46
+ show() {
47
+ if (this.isTransitioning || this.element.classList.contains('show')) return;
48
+
49
+ // Close siblings if parent is set
50
+ if (this.options.parent) {
51
+ const parent = document.querySelector(this.options.parent);
52
+ if (parent) {
53
+ const siblings = parent.querySelectorAll('.collapse.show');
54
+ siblings.forEach(sibling => {
55
+ if (sibling !== this.element) {
56
+ const collapseInstance = Collapse.getInstance(sibling);
57
+ if (collapseInstance) collapseInstance.hide();
58
+ }
59
+ });
60
+ }
61
+ }
62
+
63
+ this.isTransitioning = true;
64
+ this.element.classList.remove('collapse');
65
+ this.element.classList.add('collapsing');
66
+ this.element.style.height = '0px';
67
+
68
+ this.updateTriggers(true);
69
+
70
+ requestAnimationFrame(() => {
71
+ this.element.style.height = this.element.scrollHeight + 'px';
72
+ });
73
+
74
+ const complete = () => {
75
+ this.isTransitioning = false;
76
+ this.element.classList.remove('collapsing');
77
+ this.element.classList.add('collapse', 'show');
78
+ this.element.style.height = '';
79
+ this.element.removeEventListener('transitionend', complete);
80
+ this.element.dispatchEvent(new CustomEvent('shown.zk.collapse'));
81
+ };
82
+
83
+ this.element.addEventListener('transitionend', complete);
84
+ this.element.dispatchEvent(new CustomEvent('show.zk.collapse'));
85
+ }
86
+
87
+ hide() {
88
+ if (this.isTransitioning || !this.element.classList.contains('show')) return;
89
+
90
+ this.isTransitioning = true;
91
+ this.element.style.height = this.element.scrollHeight + 'px';
92
+ this.element.classList.remove('show');
93
+
94
+ this.updateTriggers(false);
95
+
96
+ requestAnimationFrame(() => {
97
+ this.element.classList.add('collapsing');
98
+ this.element.style.height = '0px';
99
+ });
100
+
101
+ const complete = () => {
102
+ this.isTransitioning = false;
103
+ this.element.classList.remove('collapsing');
104
+ this.element.classList.add('collapse');
105
+ this.element.style.height = '';
106
+ this.element.removeEventListener('transitionend', complete);
107
+ this.element.dispatchEvent(new CustomEvent('hidden.zk.collapse'));
108
+ };
109
+
110
+ this.element.addEventListener('transitionend', complete);
111
+ this.element.dispatchEvent(new CustomEvent('hide.zk.collapse'));
112
+ }
113
+
114
+ updateTriggers(isOpen) {
115
+ this.triggerElements.forEach(trigger => {
116
+ trigger.classList.toggle('collapsed', !isOpen);
117
+ trigger.setAttribute('aria-expanded', isOpen);
118
+ });
119
+ }
120
+
121
+ // Store instance on element
122
+ static instances = new WeakMap();
123
+
124
+ static getInstance(element) {
125
+ return Collapse.instances.get(element);
126
+ }
127
+
128
+ static getOrCreateInstance(element, options) {
129
+ return Collapse.getInstance(element) || new Collapse(element, options);
130
+ }
131
+
132
+ // Static method to initialize all collapses
133
+ static init(selector = '[data-toggle="collapse"]') {
134
+ document.querySelectorAll(selector).forEach(trigger => {
135
+ const target = trigger.dataset.target || trigger.getAttribute('href');
136
+ const element = document.querySelector(target);
137
+ if (element && !Collapse.getInstance(element)) {
138
+ const instance = new Collapse(element, { toggle: false });
139
+ Collapse.instances.set(element, instance);
140
+ }
141
+ });
142
+ }
143
+ }
144
+
145
+ export default Collapse;