kempo-css 1.1.1 → 1.2.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.
@@ -806,6 +806,45 @@
806
806
  </div>
807
807
  </div>
808
808
 
809
+ <h2 id="drop-shadow">Drop Shadow</h2>
810
+ <p>Add the <code>drop-shadow</code> class to add a box shadow effect to any element. The shadow automatically adjusts for light and dark themes.</p>
811
+ <div class="row -mx">
812
+ <div class="col m-span-12 px">
813
+ <k-card label="HTML">
814
+ <pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"drop-shadow"</span>&gt;</span>Drop Shadow<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span><br /><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card drop-shadow"</span>&gt;</span>Card with Drop Shadow<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></code></pre>
815
+ </k-card>
816
+ </div>
817
+ <div class="col m-span-12 px">
818
+ <k-card label="Output">
819
+ <div class="d-ib p bg-default r drop-shadow mb">Drop Shadow</div>
820
+ <div class="card drop-shadow">Card with Drop Shadow</div>
821
+ </k-card>
822
+ </div>
823
+ </div>
824
+ <h3>Drop Shadow CSS Variables</h3>
825
+ <table>
826
+ <thead>
827
+ <tr>
828
+ <th>Variable</th>
829
+ <th>Description</th>
830
+ </tr>
831
+ </thead>
832
+ <tbody>
833
+ <tr>
834
+ <td><code>--drop_shadow</code></td>
835
+ <td>The active drop shadow (automatically set based on theme)</td>
836
+ </tr>
837
+ <tr>
838
+ <td><code>--drop_shadow__light</code></td>
839
+ <td>Drop shadow for light theme</td>
840
+ </tr>
841
+ <tr>
842
+ <td><code>--drop_shadow__dark</code></td>
843
+ <td>Drop shadow for dark theme (stronger for better visibility)</td>
844
+ </tr>
845
+ </tbody>
846
+ </table>
847
+
809
848
  <h2 id="noScroll">Disable Body Scrolling</h2>
810
849
  <p>Add the <code>no-scroll</code> class to the body to temparily disable body scrolling, this is used for things
811
850
  like dialogs and side menus that temporarily take over the page.</p>
package/docs/kempo.css CHANGED
@@ -1,3 +1,6 @@
1
+ /*
2
+ CSS Variables
3
+ */
1
4
  :root {
2
5
  color-scheme: light;
3
6
  --ff_body: "Helvetica Neue", Helvetica, Arial, sans-serif;
@@ -32,7 +35,7 @@
32
35
  --c_bg__alt: light-dark(rgb(238, 238, 238), rgb(34, 34, 34));
33
36
  --c_overscroll: light-dark(rgb(255, 255, 255), rgb(0, 0, 0));
34
37
  --c_border: light-dark(rgb(204, 204, 204), rgb(119, 119, 119));
35
- --c_border__inv: light-dark(var(--d_c_bg_border), rgb(204, 204, 204));
38
+ --c_border__inv: light-dark(rgb(119, 119, 119), rgb(204, 204, 204));
36
39
  --c_primary: rgb(51, 102, 255);
37
40
  --c_primary__hover: rgb(17, 68, 221);
38
41
  --c_secondary: rgb(153, 51, 255);
@@ -77,7 +80,9 @@
77
80
  --focus_shadow_on_primary: 0 0 2px 2px var(--tc_on_primary);
78
81
  --input_bg: light-dark(white, var(--c_bg__alt));
79
82
  --input_tc: light-dark(rgba(0, 0, 0, 0.93), var(--tc));
80
- --drop_shadow: light-dark(0 0.25rem 0.5rem rgba(0, 0, 0, 0.333), 0 0.25rem 0.5rem rgba(0, 0, 0, 0.25));
83
+ --drop_shadow__light: 0 0.25rem 0.5rem rgba(0, 0, 0, 0.333);
84
+ --drop_shadow__dark: 0 0.25rem 0.5rem rgba(0, 0, 0, 0.5);
85
+ --drop_shadow: var(--drop_shadow__light);
81
86
  --date_picker_icon_filter: light-dark(invert(0), invert(1));
82
87
  }
83
88
  [theme="light"] {
@@ -86,12 +91,21 @@
86
91
 
87
92
  [theme="dark"] {
88
93
  color-scheme: dark;
94
+ --drop_shadow: var(--drop_shadow__dark);
89
95
  }
90
96
 
91
97
  [theme="auto"] {
92
98
  color-scheme: light dark;
93
99
  }
100
+ @media (prefers-color-scheme: dark) {
101
+ [theme="auto"] {
102
+ --drop_shadow: var(--drop_shadow__dark);
103
+ }
104
+ }
94
105
 
106
+ /*
107
+ Base / Reset
108
+ */
95
109
  :root {
96
110
  interpolate-size: allow-keywords;
97
111
  }
@@ -600,6 +614,10 @@ li ul {
600
614
  .m-cols-9 { grid-template-columns: repeat(9, 1fr); }
601
615
  .m-cols-10 { grid-template-columns: repeat(10, 1fr); }
602
616
  }
617
+
618
+ /*
619
+ Buttons
620
+ */
603
621
  button:not(.no-btn):not(.no-style),
604
622
  .btn,
605
623
  input[type="button"],
@@ -772,6 +790,10 @@ input[type="reset"].link {
772
790
  display: block;
773
791
  width: 100%;
774
792
  }
793
+
794
+ /*
795
+ Forms
796
+ */
775
797
  input:not([type="button"]):not([type="submit"]):not([type="reset"]):not([type="radio"]):not([type="checkbox"]),
776
798
  select,
777
799
  textarea {
@@ -864,6 +886,10 @@ input[type="week"]::-webkit-calendar-picker-indicator,
864
886
  input[type="search"]::-webkit-search-cancel-button {
865
887
  filter: var(--date_picker_icon_filter);
866
888
  }
889
+
890
+ /*
891
+ Tables
892
+ */
867
893
  .table-wrapper {
868
894
  overflow-x: auto;
869
895
  }
@@ -898,6 +924,10 @@ tr:last-child td:first-child {
898
924
  tr:last-child td:last-child {
899
925
  border-bottom-right-radius: var(--radius);
900
926
  }
927
+
928
+ /*
929
+ Colors
930
+ */
901
931
  .bg-default {
902
932
  background-color: var(--c_bg);
903
933
  color: var(--tc);
@@ -1003,6 +1033,10 @@ tr:last-child td:last-child {
1003
1033
  .tc-muted {
1004
1034
  color: var(--tc_muted);
1005
1035
  }
1036
+
1037
+ /*
1038
+ Components
1039
+ */
1006
1040
  .card {
1007
1041
  border: 1px solid var(--c_border);
1008
1042
  border-radius: var(--radius);
@@ -199,7 +199,9 @@
199
199
  "--focus_shadow_on_primary": "0 0 2px 2px var(--tc_on_primary)",
200
200
  "--input_bg": { light: "white", dark: "var(--c_bg__alt)" },
201
201
  "--input_tc": { light: "rgba(0, 0, 0, 0.93)", dark: "var(--tc)" },
202
- "--drop_shadow": { light: "0 0.25rem 0.5rem rgba(0, 0, 0, 0.333)", dark: "0 0.25rem 0.5rem rgba(0, 0, 0, 0.25)" },
202
+ "--drop_shadow__light": "0 0.25rem 0.5rem rgba(0, 0, 0, 0.333)",
203
+ "--drop_shadow__dark": "0 0.25rem 0.5rem rgba(0, 0, 0, 0.5)",
204
+ "--drop_shadow": { light: "var(--drop_shadow__light)", dark: "var(--drop_shadow__dark)" },
203
205
  "--date_picker_icon_filter": { light: "invert(0)", dark: "invert(1)" }
204
206
  };
205
207
 
@@ -282,7 +284,8 @@
282
284
  title: 'Focus & Effects',
283
285
  props: [
284
286
  '--focus_shadow', '--focus_shadow_on_primary',
285
- '--drop_shadow', '--date_picker_icon_filter',
287
+ '--drop_shadow', '--drop_shadow__light', '--drop_shadow__dark',
288
+ '--date_picker_icon_filter',
286
289
  '--c_highlight', '--c_overlay'
287
290
  ]
288
291
  },
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "kempo-css",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "description": "",
5
+ "type": "module",
5
6
  "main": "scripts/build.js",
6
7
  "scripts": {
7
8
  "build": "node scripts/build.js",
@@ -21,6 +22,7 @@
21
22
  },
22
23
  "dependencies": {
23
24
  "kempo-server": "^1.9.1",
25
+ "kempo-testing-framework": "^1.4.0",
24
26
  "terser": "^5.44.1"
25
27
  }
26
28
  }
package/scripts/build.js CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const { execSync } = require('child_process');
4
- const fs = require('fs');
5
- const path = require('path');
6
- const { minify } = require('terser');
3
+ import { execSync } from 'child_process';
4
+ import fs from 'fs';
5
+ import path from 'path';
6
+ import { minify } from 'terser';
7
7
 
8
8
  // Check for watch flag
9
9
  const isWatchMode = process.argv.includes('--watch');
package/src/kempo.css CHANGED
@@ -1,3 +1,6 @@
1
+ /*
2
+ CSS Variables
3
+ */
1
4
  :root {
2
5
  color-scheme: light;
3
6
  --ff_body: "Helvetica Neue", Helvetica, Arial, sans-serif;
@@ -32,7 +35,7 @@
32
35
  --c_bg__alt: light-dark(rgb(238, 238, 238), rgb(34, 34, 34));
33
36
  --c_overscroll: light-dark(rgb(255, 255, 255), rgb(0, 0, 0));
34
37
  --c_border: light-dark(rgb(204, 204, 204), rgb(119, 119, 119));
35
- --c_border__inv: light-dark(var(--d_c_bg_border), rgb(204, 204, 204));
38
+ --c_border__inv: light-dark(rgb(119, 119, 119), rgb(204, 204, 204));
36
39
  --c_primary: rgb(51, 102, 255);
37
40
  --c_primary__hover: rgb(17, 68, 221);
38
41
  --c_secondary: rgb(153, 51, 255);
@@ -77,7 +80,9 @@
77
80
  --focus_shadow_on_primary: 0 0 2px 2px var(--tc_on_primary);
78
81
  --input_bg: light-dark(white, var(--c_bg__alt));
79
82
  --input_tc: light-dark(rgba(0, 0, 0, 0.93), var(--tc));
80
- --drop_shadow: light-dark(0 0.25rem 0.5rem rgba(0, 0, 0, 0.333), 0 0.25rem 0.5rem rgba(0, 0, 0, 0.25));
83
+ --drop_shadow__light: 0 0.25rem 0.5rem rgba(0, 0, 0, 0.333);
84
+ --drop_shadow__dark: 0 0.25rem 0.5rem rgba(0, 0, 0, 0.5);
85
+ --drop_shadow: var(--drop_shadow__light);
81
86
  --date_picker_icon_filter: light-dark(invert(0), invert(1));
82
87
  }
83
88
  [theme="light"] {
@@ -86,12 +91,21 @@
86
91
 
87
92
  [theme="dark"] {
88
93
  color-scheme: dark;
94
+ --drop_shadow: var(--drop_shadow__dark);
89
95
  }
90
96
 
91
97
  [theme="auto"] {
92
98
  color-scheme: light dark;
93
99
  }
100
+ @media (prefers-color-scheme: dark) {
101
+ [theme="auto"] {
102
+ --drop_shadow: var(--drop_shadow__dark);
103
+ }
104
+ }
94
105
 
106
+ /*
107
+ Base / Reset
108
+ */
95
109
  :root {
96
110
  interpolate-size: allow-keywords;
97
111
  }
@@ -600,6 +614,10 @@ li ul {
600
614
  .m-cols-9 { grid-template-columns: repeat(9, 1fr); }
601
615
  .m-cols-10 { grid-template-columns: repeat(10, 1fr); }
602
616
  }
617
+
618
+ /*
619
+ Buttons
620
+ */
603
621
  button:not(.no-btn):not(.no-style),
604
622
  .btn,
605
623
  input[type="button"],
@@ -772,6 +790,10 @@ input[type="reset"].link {
772
790
  display: block;
773
791
  width: 100%;
774
792
  }
793
+
794
+ /*
795
+ Forms
796
+ */
775
797
  input:not([type="button"]):not([type="submit"]):not([type="reset"]):not([type="radio"]):not([type="checkbox"]),
776
798
  select,
777
799
  textarea {
@@ -864,6 +886,10 @@ input[type="week"]::-webkit-calendar-picker-indicator,
864
886
  input[type="search"]::-webkit-search-cancel-button {
865
887
  filter: var(--date_picker_icon_filter);
866
888
  }
889
+
890
+ /*
891
+ Tables
892
+ */
867
893
  .table-wrapper {
868
894
  overflow-x: auto;
869
895
  }
@@ -898,6 +924,10 @@ tr:last-child td:first-child {
898
924
  tr:last-child td:last-child {
899
925
  border-bottom-right-radius: var(--radius);
900
926
  }
927
+
928
+ /*
929
+ Colors
930
+ */
901
931
  .bg-default {
902
932
  background-color: var(--c_bg);
903
933
  color: var(--tc);
@@ -1003,6 +1033,10 @@ tr:last-child td:last-child {
1003
1033
  .tc-muted {
1004
1034
  color: var(--tc_muted);
1005
1035
  }
1036
+
1037
+ /*
1038
+ Components
1039
+ */
1006
1040
  .card {
1007
1041
  border: 1px solid var(--c_border);
1008
1042
  border-radius: var(--radius);
@@ -0,0 +1,201 @@
1
+ const getStyle = (el, prop) => getComputedStyle(el)[prop];
2
+
3
+ export const beforeAll = async () => {
4
+ const link = document.createElement('link');
5
+ link.rel = 'stylesheet';
6
+ link.href = '/src/kempo.css';
7
+ document.head.appendChild(link);
8
+ await new Promise(resolve => link.onload = resolve);
9
+ };
10
+
11
+ export default {
12
+ 'should apply box-sizing border-box to all elements': ({pass, fail}) => {
13
+ const div = document.createElement('div');
14
+ document.body.appendChild(div);
15
+ const boxSizing = getStyle(div, 'boxSizing');
16
+ div.remove();
17
+ if(boxSizing === 'border-box'){
18
+ pass('box-sizing is border-box');
19
+ } else {
20
+ fail(`Expected border-box, got ${boxSizing}`);
21
+ }
22
+ },
23
+
24
+ 'should reset margin on body': ({pass, fail}) => {
25
+ const margin = getStyle(document.body, 'margin');
26
+ if(margin === '0px'){
27
+ pass('Body margin is reset to 0');
28
+ } else {
29
+ fail(`Expected 0px margin, got ${margin}`);
30
+ }
31
+ },
32
+
33
+ 'should set min-height 100vh on body': ({pass, fail}) => {
34
+ const minHeight = getStyle(document.body, 'minHeight');
35
+ const viewportHeight = window.innerHeight;
36
+ const minHeightValue = parseFloat(minHeight);
37
+ if(minHeightValue >= viewportHeight){
38
+ pass('Body min-height is at least 100vh');
39
+ } else {
40
+ fail(`Expected min-height >= ${viewportHeight}px, got ${minHeight}`);
41
+ }
42
+ },
43
+
44
+ 'should apply background color to body': ({pass, fail}) => {
45
+ const bg = getStyle(document.body, 'backgroundColor');
46
+ if(bg && bg !== 'transparent' && bg !== 'rgba(0, 0, 0, 0)'){
47
+ pass(`Body has background color: ${bg}`);
48
+ } else {
49
+ fail('Body should have a background color');
50
+ }
51
+ },
52
+
53
+ 'should apply text color to body': ({pass, fail}) => {
54
+ const color = getStyle(document.body, 'color');
55
+ if(color && color !== 'transparent' && color !== 'rgba(0, 0, 0, 0)'){
56
+ pass(`Body has text color: ${color}`);
57
+ } else {
58
+ fail('Body should have a text color');
59
+ }
60
+ },
61
+
62
+ 'should reset top margin on headings': ({pass, fail}) => {
63
+ const headings = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
64
+ const failed = [];
65
+ headings.forEach(tag => {
66
+ const el = document.createElement(tag);
67
+ document.body.appendChild(el);
68
+ const marginTop = getStyle(el, 'marginTop');
69
+ el.remove();
70
+ if(marginTop !== '0px'){
71
+ failed.push(`${tag}: ${marginTop}`);
72
+ }
73
+ });
74
+ if(failed.length === 0){
75
+ pass('All headings have top margin reset to 0');
76
+ } else {
77
+ fail(`Headings with non-zero top margin: ${failed.join(', ')}`);
78
+ }
79
+ },
80
+
81
+ 'should apply bottom margin to headings': ({pass, fail}) => {
82
+ const h1 = document.createElement('h1');
83
+ document.body.appendChild(h1);
84
+ const marginBottom = parseFloat(getStyle(h1, 'marginBottom'));
85
+ h1.remove();
86
+ if(marginBottom > 0){
87
+ pass(`Headings have bottom margin: ${marginBottom}px`);
88
+ } else {
89
+ fail('Headings should have bottom margin for spacing');
90
+ }
91
+ },
92
+
93
+ 'should apply bottom margin to paragraphs': ({pass, fail}) => {
94
+ const p = document.createElement('p');
95
+ document.body.appendChild(p);
96
+ const marginBottom = parseFloat(getStyle(p, 'marginBottom'));
97
+ const marginTop = getStyle(p, 'marginTop');
98
+ p.remove();
99
+ if(marginTop === '0px' && marginBottom > 0){
100
+ pass(`Paragraphs have 0 top margin and ${marginBottom}px bottom margin`);
101
+ } else {
102
+ fail(`Expected 0 top margin and positive bottom margin, got top: ${marginTop}, bottom: ${marginBottom}px`);
103
+ }
104
+ },
105
+
106
+ 'should apply container max-width': ({pass, fail}) => {
107
+ const container = document.createElement('div');
108
+ container.className = 'container';
109
+ document.body.appendChild(container);
110
+ const maxWidth = getStyle(container, 'maxWidth');
111
+ container.remove();
112
+ if(maxWidth && maxWidth !== 'none'){
113
+ pass(`Container has max-width: ${maxWidth}`);
114
+ } else {
115
+ fail('Container should have a max-width');
116
+ }
117
+ },
118
+
119
+ 'should center container horizontally': ({pass, fail}) => {
120
+ const container = document.createElement('div');
121
+ container.className = 'container';
122
+ document.body.appendChild(container);
123
+ const marginLeft = getStyle(container, 'marginLeft');
124
+ const marginRight = getStyle(container, 'marginRight');
125
+ container.remove();
126
+ // When container is narrower than viewport, auto margins compute to equal pixel values
127
+ // When container fills viewport, they're both 0 or equal
128
+ if(marginLeft === marginRight){
129
+ pass('Container is horizontally centered');
130
+ } else {
131
+ fail(`Expected equal margins, got left: ${marginLeft}, right: ${marginRight}`);
132
+ }
133
+ },
134
+
135
+ 'should apply padding to container': ({pass, fail}) => {
136
+ const container = document.createElement('div');
137
+ container.className = 'container';
138
+ document.body.appendChild(container);
139
+ const paddingLeft = parseFloat(getStyle(container, 'paddingLeft'));
140
+ const paddingRight = parseFloat(getStyle(container, 'paddingRight'));
141
+ container.remove();
142
+ if(paddingLeft > 0 && paddingRight > 0){
143
+ pass(`Container has padding: left ${paddingLeft}px, right ${paddingRight}px`);
144
+ } else {
145
+ fail('Container should have left and right padding');
146
+ }
147
+ },
148
+
149
+ 'should style main element like container': ({pass, fail}) => {
150
+ const main = document.createElement('main');
151
+ document.body.appendChild(main);
152
+ const maxWidth = getStyle(main, 'maxWidth');
153
+ const marginLeft = getStyle(main, 'marginLeft');
154
+ const marginRight = getStyle(main, 'marginRight');
155
+ main.remove();
156
+ if(maxWidth && maxWidth !== 'none' && marginLeft === marginRight){
157
+ pass('Main element is styled like container');
158
+ } else {
159
+ fail(`Main should have max-width and centered margins, got maxWidth: ${maxWidth}, margins: ${marginLeft}/${marginRight}`);
160
+ }
161
+ },
162
+
163
+ 'should hide overflow on body.no-scroll': ({pass, fail}) => {
164
+ document.body.classList.add('no-scroll');
165
+ const overflow = getStyle(document.body, 'overflow');
166
+ document.body.classList.remove('no-scroll');
167
+ if(overflow === 'hidden'){
168
+ pass('body.no-scroll hides overflow');
169
+ } else {
170
+ fail(`Expected overflow hidden, got ${overflow}`);
171
+ }
172
+ },
173
+
174
+ 'should style summary element with pointer cursor': ({pass, fail}) => {
175
+ const details = document.createElement('details');
176
+ const summary = document.createElement('summary');
177
+ summary.textContent = 'Test';
178
+ details.appendChild(summary);
179
+ document.body.appendChild(details);
180
+ const cursor = getStyle(summary, 'cursor');
181
+ details.remove();
182
+ if(cursor === 'pointer'){
183
+ pass('Summary has pointer cursor');
184
+ } else {
185
+ fail(`Expected pointer cursor, got ${cursor}`);
186
+ }
187
+ },
188
+
189
+ 'should reset menu margin and padding': ({pass, fail}) => {
190
+ const menu = document.createElement('menu');
191
+ document.body.appendChild(menu);
192
+ const margin = getStyle(menu, 'margin');
193
+ const padding = getStyle(menu, 'padding');
194
+ menu.remove();
195
+ if(margin === '0px' && padding === '0px'){
196
+ pass('Menu margin and padding reset');
197
+ } else {
198
+ fail(`Menu should have 0 margin/padding, got margin: ${margin}, padding: ${padding}`);
199
+ }
200
+ }
201
+ };