@thl-ui/styles 0.0.0-stage → 0.1.1

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 (45) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +42 -2
  3. package/dist/components/alert.css +1 -0
  4. package/dist/components/app.css +1 -0
  5. package/dist/components/avatar.css +1 -0
  6. package/dist/components/badge.css +1 -0
  7. package/dist/components/button.css +1 -0
  8. package/dist/components/card.css +1 -0
  9. package/dist/components/form.css +1 -0
  10. package/dist/components/nav.css +1 -0
  11. package/dist/components/overlay.css +1 -0
  12. package/dist/components/pages.css +1 -0
  13. package/dist/components/progress.css +1 -0
  14. package/dist/components/ribbon.css +1 -0
  15. package/dist/components/table.css +1 -0
  16. package/dist/components/widgets.css +1 -0
  17. package/dist/fonts/OFL.txt +93 -0
  18. package/dist/fonts/open-sans-cyrillic-ext-wght-normal.woff2 +0 -0
  19. package/dist/fonts/open-sans-cyrillic-wght-normal.woff2 +0 -0
  20. package/dist/fonts/open-sans-greek-ext-wght-normal.woff2 +0 -0
  21. package/dist/fonts/open-sans-greek-wght-normal.woff2 +0 -0
  22. package/dist/fonts/open-sans-latin-ext-wght-normal.woff2 +0 -0
  23. package/dist/fonts/open-sans-latin-wght-normal.woff2 +0 -0
  24. package/dist/fonts/open-sans-vietnamese-wght-normal.woff2 +0 -0
  25. package/dist/fonts.css +70 -0
  26. package/dist/thl-ui.css +447 -0
  27. package/dist/tokens.css +376 -0
  28. package/package.json +48 -4
  29. package/src/_base.scss +408 -0
  30. package/src/_mixins.scss +53 -0
  31. package/src/components/_alert.scss +183 -0
  32. package/src/components/_avatar.scss +75 -0
  33. package/src/components/_badge.scss +112 -0
  34. package/src/components/_button.scss +242 -0
  35. package/src/components/_card.scss +211 -0
  36. package/src/components/_form.scss +959 -0
  37. package/src/components/_nav.scss +738 -0
  38. package/src/components/_overlay.scss +382 -0
  39. package/src/components/_progress.scss +103 -0
  40. package/src/components/_ribbon.scss +136 -0
  41. package/src/components/_table.scss +215 -0
  42. package/src/components/_widgets.scss +1102 -0
  43. package/src/index.scss +16 -0
  44. package/src/layout/_app.scss +804 -0
  45. package/src/pages/_pages.scss +691 -0
package/src/_base.scss ADDED
@@ -0,0 +1,408 @@
1
+ @use 'mixins' as *;
2
+
3
+ *,
4
+ *::before,
5
+ *::after {
6
+ box-sizing: border-box;
7
+ }
8
+
9
+ .#{$p}-root,
10
+ body.#{$p}-body {
11
+ margin: 0;
12
+ font-family: v(font-family);
13
+ font-size: v(font-size);
14
+ line-height: v(line-height);
15
+ color: v(text);
16
+ background: v(body-bg);
17
+ -webkit-font-smoothing: antialiased;
18
+ }
19
+ /* default link colour, zero specificity so any component rule (even `color: inherit`) wins */
20
+ :where(.#{$p}-root, body.#{$p}-body) :where(a:not([class*='#{$p}-btn'])) {
21
+ color: v(link);
22
+ text-decoration: none;
23
+ &:hover {
24
+ color: v(theme-text);
25
+ text-decoration: underline;
26
+ }
27
+ }
28
+ .#{$p}-root,
29
+ body.#{$p}-body {
30
+ h1,
31
+ h2,
32
+ h3,
33
+ h4,
34
+ h5,
35
+ h6 {
36
+ color: v(heading);
37
+ font-weight: 400;
38
+ margin: 10px 0;
39
+ font-family: inherit;
40
+ }
41
+ h1 {
42
+ font-size: v(h1);
43
+ line-height: 48px;
44
+ }
45
+ h2 {
46
+ font-size: v(h2);
47
+ line-height: 36px;
48
+ }
49
+ h3 {
50
+ font-size: v(h3);
51
+ line-height: 30px;
52
+ }
53
+ h4 {
54
+ font-size: v(h4);
55
+ line-height: 22px;
56
+ }
57
+ h5 {
58
+ font-size: v(h5);
59
+ line-height: 18px;
60
+ }
61
+ h6 {
62
+ font-size: v(h6);
63
+ line-height: 16px;
64
+ }
65
+
66
+ hr {
67
+ border: 0;
68
+ border-top: 1px solid v(border-color);
69
+ margin: 20px 0;
70
+ }
71
+ img {
72
+ max-width: 100%;
73
+ }
74
+ }
75
+ :focus-visible {
76
+ @include focus-ring;
77
+ }
78
+
79
+ /* ---------- utilities ---------- */
80
+ .#{$p}-sr-only {
81
+ @include visually-hidden;
82
+ }
83
+ @each $c in $colors {
84
+ .#{$p}-text-#{$c} {
85
+ color: tx($c) !important;
86
+ }
87
+ .#{$p}-bg-#{$c} {
88
+ background: v(color-#{$c}) !important;
89
+ color: on($c);
90
+ }
91
+ }
92
+ .#{$p}-text-muted {
93
+ color: v(text) !important;
94
+ }
95
+ .#{$p}-text-strong {
96
+ color: v(text-strong) !important;
97
+ }
98
+ .#{$p}-text-theme {
99
+ color: v(theme-text) !important;
100
+ }
101
+ .#{$p}-bg-theme {
102
+ background: v(theme) !important;
103
+ color: v(on-theme);
104
+ }
105
+ .#{$p}-text-center {
106
+ text-align: center;
107
+ }
108
+ .#{$p}-text-end {
109
+ text-align: end;
110
+ }
111
+ .#{$p}-text-start {
112
+ text-align: start;
113
+ }
114
+ .#{$p}-fw-600 {
115
+ font-weight: 600;
116
+ }
117
+ /* visual heading sizes decoupled from semantic level (keep heading order valid, look unchanged) */
118
+ .#{$p}-root,
119
+ body.#{$p}-body {
120
+ .#{$p}-h3 {
121
+ font-size: v(h3);
122
+ line-height: 30px;
123
+ }
124
+ .#{$p}-h4 {
125
+ font-size: v(h4);
126
+ line-height: 22px;
127
+ }
128
+ .#{$p}-h5 {
129
+ font-size: v(h5);
130
+ line-height: 18px;
131
+ }
132
+ .#{$p}-h6 {
133
+ font-size: v(h6);
134
+ line-height: 16px;
135
+ }
136
+ }
137
+ .#{$p}-caption {
138
+ font-size: v(h6);
139
+ line-height: 16px;
140
+ font-weight: 400;
141
+ color: v(heading);
142
+ }
143
+ .#{$p}-fw-300 {
144
+ font-weight: 300;
145
+ }
146
+ .#{$p}-flex {
147
+ display: flex;
148
+ gap: v(space-3);
149
+ align-items: center;
150
+ flex-wrap: wrap;
151
+ }
152
+ .#{$p}-flex-between {
153
+ display: flex;
154
+ justify-content: space-between;
155
+ align-items: center;
156
+ gap: v(space-3);
157
+ flex-wrap: wrap;
158
+ }
159
+ .#{$p}-stack {
160
+ display: flex;
161
+ flex-direction: column;
162
+ gap: v(space-4);
163
+ }
164
+ .#{$p}-mt-0 {
165
+ margin-top: 0 !important;
166
+ }
167
+ .#{$p}-mb-0 {
168
+ margin-bottom: 0 !important;
169
+ }
170
+ @for $i from 1 through 7 {
171
+ .#{$p}-m-#{$i} {
172
+ margin: v(space-#{$i});
173
+ }
174
+ .#{$p}-p-#{$i} {
175
+ padding: v(space-#{$i});
176
+ }
177
+ .#{$p}-mt-#{$i} {
178
+ margin-top: v(space-#{$i});
179
+ }
180
+ .#{$p}-mb-#{$i} {
181
+ margin-bottom: v(space-#{$i});
182
+ }
183
+ .#{$p}-gap-#{$i} {
184
+ gap: v(space-#{$i});
185
+ }
186
+ }
187
+
188
+ /* ---------- grid (12 columns, flexbox) ---------- */
189
+ .#{$p}-row {
190
+ display: grid;
191
+ grid-template-columns: repeat(12, minmax(0, 1fr));
192
+ gap: v(space-7);
193
+ }
194
+ .#{$p}-row--gap-sm {
195
+ gap: v(space-4);
196
+ }
197
+ @for $i from 1 through 12 {
198
+ .#{$p}-col-#{$i} {
199
+ grid-column: span $i;
200
+ }
201
+ }
202
+ @each $bp in (sm, md, lg) {
203
+ @include up($bp) {
204
+ @for $i from 1 through 12 {
205
+ .#{$p}-col-#{$bp}-#{$i} {
206
+ grid-column: span $i;
207
+ }
208
+ }
209
+ }
210
+ }
211
+ .#{$p}-row > [class*='#{$p}-col'] {
212
+ min-width: 0;
213
+ }
214
+ .#{$p}-row > :not([class*='#{$p}-col']) {
215
+ grid-column: 1 / -1;
216
+ }
217
+ /* below 768px every column is full width: use a single track, otherwise the
218
+ 11 inter-column gaps alone (11 × 30px) are wider than a phone screen */
219
+ @include down(sm) {
220
+ .#{$p}-row {
221
+ grid-template-columns: minmax(0, 1fr);
222
+ }
223
+ .#{$p}-row > [class*='#{$p}-col'] {
224
+ grid-column: 1 / -1;
225
+ }
226
+ }
227
+ .#{$p}-grid-auto {
228
+ display: grid;
229
+ gap: v(space-6);
230
+ grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
231
+ }
232
+
233
+ /* ---------- animations ---------- */
234
+ @keyframes #{$p}-fade-in {
235
+ from {
236
+ opacity: 0;
237
+ }
238
+ to {
239
+ opacity: 1;
240
+ }
241
+ }
242
+ @keyframes #{$p}-slide-down {
243
+ from {
244
+ opacity: 0;
245
+ transform: translateY(-10px);
246
+ }
247
+ to {
248
+ opacity: 1;
249
+ transform: none;
250
+ }
251
+ }
252
+ @keyframes #{$p}-zoom-in {
253
+ from {
254
+ opacity: 0;
255
+ transform: scale(0.95);
256
+ }
257
+ to {
258
+ opacity: 1;
259
+ transform: none;
260
+ }
261
+ }
262
+ @keyframes #{$p}-bounce-in-down {
263
+ 0% {
264
+ opacity: 0;
265
+ transform: translateY(-40px);
266
+ }
267
+ 60% {
268
+ opacity: 1;
269
+ transform: translateY(6px);
270
+ }
271
+ 100% {
272
+ transform: none;
273
+ }
274
+ }
275
+ @keyframes #{$p}-spin {
276
+ to {
277
+ transform: rotate(360deg);
278
+ }
279
+ }
280
+ @keyframes #{$p}-stripes {
281
+ from {
282
+ background-position: 40px 0;
283
+ }
284
+ to {
285
+ background-position: 0 0;
286
+ }
287
+ }
288
+ @keyframes #{$p}-pulse {
289
+ 0%,
290
+ 100% {
291
+ opacity: 1;
292
+ }
293
+ 50% {
294
+ opacity: 0.4;
295
+ }
296
+ }
297
+ @keyframes #{$p}-flip-in-y {
298
+ from {
299
+ transform: perspective(400px) rotateY(90deg);
300
+ opacity: 0;
301
+ }
302
+ to {
303
+ transform: none;
304
+ opacity: 1;
305
+ }
306
+ }
307
+ .#{$p}-anim-fade-in {
308
+ animation: #{$p}-fade-in 0.3s both;
309
+ }
310
+ .#{$p}-anim-slide-down {
311
+ animation: #{$p}-slide-down 0.25s both;
312
+ }
313
+ .#{$p}-anim-zoom-in {
314
+ animation: #{$p}-zoom-in 0.2s both;
315
+ }
316
+ .#{$p}-anim-bounce-in-down {
317
+ animation: #{$p}-bounce-in-down 0.5s both;
318
+ }
319
+ .#{$p}-anim-flip-in-y {
320
+ animation: #{$p}-flip-in-y 0.4s both;
321
+ }
322
+ @media (prefers-reduced-motion: reduce) {
323
+ *,
324
+ *::before,
325
+ *::after {
326
+ animation-duration: 0.01ms !important;
327
+ transition-duration: 0.01ms !important;
328
+ }
329
+ }
330
+
331
+ /* ---------- host elements (Angular / custom-element adapters render real tags) ---------- */
332
+ /* Unknown elements default to display:inline, which breaks block components. */
333
+ :where(
334
+ thl-app-layout,
335
+ thl-card,
336
+ thl-panel,
337
+ thl-well,
338
+ thl-ribbon,
339
+ thl-alert,
340
+ thl-tabs,
341
+ thl-steps,
342
+ thl-timeline,
343
+ thl-activity-feed,
344
+ thl-horizontal-timeline,
345
+ thl-carousel,
346
+ thl-progress,
347
+ thl-data-table,
348
+ thl-list,
349
+ thl-gallery,
350
+ thl-info-box,
351
+ thl-stat-box,
352
+ thl-color-box,
353
+ thl-stats-row,
354
+ thl-pricing-table,
355
+ thl-user-card,
356
+ thl-profile-card,
357
+ thl-chat,
358
+ thl-todo-list,
359
+ thl-weather,
360
+ thl-line-chart,
361
+ thl-bar-chart,
362
+ thl-calendar,
363
+ thl-calendar-grid,
364
+ thl-field,
365
+ thl-select,
366
+ thl-number-input,
367
+ thl-slider,
368
+ thl-upload,
369
+ thl-date-picker,
370
+ thl-date-range-picker,
371
+ thl-time-picker,
372
+ thl-color-picker,
373
+ thl-input-group,
374
+ thl-checkbox-group,
375
+ thl-radio-group,
376
+ thl-side-menu,
377
+ thl-breadcrumb,
378
+ thl-pagination,
379
+ thl-auth-layout,
380
+ thl-error-layout
381
+ ) {
382
+ display: block;
383
+ }
384
+ :where(thl-page, thl-settings-panel, thl-confirm-host, thl-toaster, thl-drawer, thl-modal) {
385
+ display: contents;
386
+ }
387
+ :where(
388
+ thl-icon,
389
+ thl-badge,
390
+ thl-label,
391
+ thl-avatar,
392
+ thl-spinner,
393
+ thl-checkbox,
394
+ thl-switch,
395
+ thl-tooltip,
396
+ thl-popover,
397
+ thl-dropdown,
398
+ thl-sparkline,
399
+ thl-button-group,
400
+ thl-progress-circle,
401
+ thl-topbar-messages
402
+ ) {
403
+ display: inline-flex;
404
+ }
405
+ thl-topbar-messages {
406
+ align-items: center;
407
+ }
408
+ /* :where() has zero specificity, so a host that also carries a .thl-* class (e.g. thl-progress.thl-progress) keeps that class's display. */
@@ -0,0 +1,53 @@
1
+ @use 'sass:map';
2
+ $prefix: 'thl' !default;
3
+ $p: $prefix;
4
+
5
+ @function v($name) {
6
+ @return var(--#{$p}-#{$name});
7
+ }
8
+
9
+ $colors: ('default', 'primary', 'success', 'info', 'warning', 'danger', 'inverse', 'purple', 'dark', 'red');
10
+ $bps: (
11
+ xs: 480px,
12
+ sm: 768px,
13
+ md: 1024px,
14
+ lg: 1170px,
15
+ xl: 1367px,
16
+ );
17
+
18
+ @mixin down($bp) {
19
+ @media (max-width: map.get($bps, $bp) - 1px) {
20
+ @content;
21
+ }
22
+ }
23
+ @mixin up($bp) {
24
+ @media (min-width: map.get($bps, $bp)) {
25
+ @content;
26
+ }
27
+ }
28
+
29
+ /** text color that contrasts with a semantic color background */
30
+ @function on($c) {
31
+ @return v(on-#{$c});
32
+ }
33
+ /** semantic color used as text on surfaces (AA-safe tone) */
34
+ @function tx($c) {
35
+ @return v(color-#{$c}-text);
36
+ }
37
+
38
+ @mixin focus-ring {
39
+ outline: 2px solid color-mix(in srgb, #{v(theme)} 55%, transparent);
40
+ outline-offset: 2px;
41
+ }
42
+
43
+ @mixin visually-hidden {
44
+ position: absolute !important;
45
+ width: 1px;
46
+ height: 1px;
47
+ padding: 0;
48
+ margin: -1px;
49
+ overflow: hidden;
50
+ clip: rect(0, 0, 0, 0);
51
+ white-space: nowrap;
52
+ border: 0;
53
+ }
@@ -0,0 +1,183 @@
1
+ @use '../mixins' as *;
2
+
3
+ .#{$p}-alert {
4
+ --_c: #{v(color-info)};
5
+ display: flex;
6
+ gap: 12px;
7
+ align-items: flex-start;
8
+ position: relative;
9
+ padding: 15px 20px;
10
+ margin-bottom: 20px;
11
+ border-radius: v(radius);
12
+ color: var(--_on);
13
+ background: var(--_c);
14
+ border: 1px solid var(--_c);
15
+ --_on: #{on('info')};
16
+ --_t: #{tx('info')};
17
+ @each $c in $colors {
18
+ &--#{$c} {
19
+ --_c: #{v(color-#{$c})};
20
+ --_on: #{on($c)};
21
+ --_t: #{tx($c)};
22
+ }
23
+ }
24
+ &--soft {
25
+ color: var(--_t);
26
+ background: color-mix(in srgb, var(--_c) 12%, #{v(surface)});
27
+ border-color: color-mix(in srgb, var(--_c) 30%, transparent);
28
+ }
29
+ &__icon {
30
+ font-size: 20px;
31
+ line-height: 1.2;
32
+ flex-shrink: 0;
33
+ }
34
+ &__content {
35
+ flex: 1;
36
+ min-width: 0;
37
+ }
38
+ &__title {
39
+ font-weight: 600;
40
+ margin: 0 0 4px !important;
41
+ color: inherit !important;
42
+ font-size: 16px !important;
43
+ line-height: 1.3 !important;
44
+ }
45
+ &__close {
46
+ background: none;
47
+ border: 0;
48
+ color: inherit;
49
+ opacity: 0.7;
50
+ cursor: pointer;
51
+ font-size: 20px;
52
+ line-height: 1;
53
+ padding: 0;
54
+ &:hover {
55
+ opacity: 1;
56
+ }
57
+ }
58
+ &__img {
59
+ width: 40px;
60
+ height: 40px;
61
+ border-radius: 50%;
62
+ object-fit: cover;
63
+ }
64
+ }
65
+
66
+ /* toasts */
67
+ .#{$p}-toaster {
68
+ position: fixed;
69
+ z-index: v(z-toast);
70
+ display: flex;
71
+ flex-direction: column;
72
+ gap: 10px;
73
+ padding: 16px;
74
+ pointer-events: none;
75
+ max-width: 100%;
76
+ width: 380px;
77
+ &--top-right {
78
+ top: 0;
79
+ inset-inline-end: 0;
80
+ }
81
+ &--top-left {
82
+ top: 0;
83
+ inset-inline-start: 0;
84
+ }
85
+ &--bottom-right {
86
+ bottom: 0;
87
+ inset-inline-end: 0;
88
+ flex-direction: column-reverse;
89
+ }
90
+ &--bottom-left {
91
+ bottom: 0;
92
+ inset-inline-start: 0;
93
+ flex-direction: column-reverse;
94
+ }
95
+ &--top-center {
96
+ top: 0;
97
+ left: 50%;
98
+ transform: translateX(-50%);
99
+ }
100
+ &--bottom-center {
101
+ bottom: 0;
102
+ left: 50%;
103
+ transform: translateX(-50%);
104
+ flex-direction: column-reverse;
105
+ }
106
+ }
107
+ .#{$p}-toast {
108
+ --_c: #{v(color-inverse)};
109
+ pointer-events: auto;
110
+ display: flex;
111
+ gap: 12px;
112
+ align-items: flex-start;
113
+ padding: 14px 16px;
114
+ border-radius: v(radius);
115
+ background: v(surface);
116
+ color: v(text-strong);
117
+ box-shadow: v(shadow-lg);
118
+ border-inline-start: 4px solid var(--_c);
119
+ animation: #{$p}-slide-down 0.25s both;
120
+ position: relative;
121
+ overflow: hidden;
122
+ &--success {
123
+ --_c: #{v(color-success)};
124
+ }
125
+ &--info {
126
+ --_c: #{v(color-info)};
127
+ }
128
+ &--warning {
129
+ --_c: #{v(color-warning)};
130
+ }
131
+ &--error {
132
+ --_c: #{v(color-danger)};
133
+ }
134
+ &__icon {
135
+ color: var(--_c);
136
+ font-size: 20px;
137
+ line-height: 1.2;
138
+ flex-shrink: 0;
139
+ }
140
+ &__body {
141
+ flex: 1;
142
+ min-width: 0;
143
+ }
144
+ &__title {
145
+ font-weight: 600;
146
+ margin-bottom: 2px;
147
+ color: v(heading);
148
+ }
149
+ &__message {
150
+ font-size: 13px;
151
+ color: v(text);
152
+ word-break: break-word;
153
+ }
154
+ &__close {
155
+ background: none;
156
+ border: 0;
157
+ color: v(text);
158
+ cursor: pointer;
159
+ font-size: 18px;
160
+ line-height: 1;
161
+ padding: 0;
162
+ &:hover {
163
+ color: v(text-strong);
164
+ }
165
+ }
166
+ &__progress {
167
+ position: absolute;
168
+ bottom: 0;
169
+ inset-inline-start: 0;
170
+ height: 3px;
171
+ background: var(--_c);
172
+ opacity: 0.5;
173
+ animation: #{$p}-toast-progress linear forwards;
174
+ }
175
+ }
176
+ @keyframes #{$p}-toast-progress {
177
+ from {
178
+ width: 100%;
179
+ }
180
+ to {
181
+ width: 0;
182
+ }
183
+ }
@@ -0,0 +1,75 @@
1
+ @use '../mixins' as *;
2
+
3
+ .#{$p}-avatar {
4
+ --_size: 40px;
5
+ position: relative;
6
+ display: inline-flex;
7
+ align-items: center;
8
+ justify-content: center;
9
+ flex-shrink: 0;
10
+ width: var(--_size);
11
+ height: var(--_size);
12
+ border-radius: 50%;
13
+ background: v(color-light);
14
+ color: v(text-strong);
15
+ font-weight: 600;
16
+ font-size: calc(var(--_size) * 0.38);
17
+ text-transform: uppercase;
18
+ img {
19
+ width: 100%;
20
+ height: 100%;
21
+ object-fit: cover;
22
+ border-radius: inherit;
23
+ }
24
+ &--size-xs {
25
+ --_size: 24px;
26
+ }
27
+ &--size-sm {
28
+ --_size: 30px;
29
+ }
30
+ &--size-lg {
31
+ --_size: 60px;
32
+ }
33
+ &--size-xl {
34
+ --_size: 79px;
35
+ }
36
+ &--rounded {
37
+ border-radius: v(radius);
38
+ }
39
+ &--square {
40
+ border-radius: 0;
41
+ }
42
+ &__status {
43
+ position: absolute;
44
+ bottom: 2%;
45
+ inset-inline-end: 2%;
46
+ width: 28%;
47
+ height: 28%;
48
+ min-width: 8px;
49
+ min-height: 8px;
50
+ border-radius: 50%;
51
+ border: 2px solid v(surface);
52
+ &--online {
53
+ background: v(color-success);
54
+ }
55
+ &--busy {
56
+ background: v(color-danger);
57
+ }
58
+ &--away {
59
+ background: v(color-warning);
60
+ }
61
+ &--offline {
62
+ background: v(color-muted);
63
+ }
64
+ }
65
+ }
66
+ .#{$p}-avatar-group {
67
+ display: inline-flex;
68
+ > .#{$p}-avatar {
69
+ border: 2px solid v(surface);
70
+ margin-inline-start: -10px;
71
+ &:first-child {
72
+ margin-inline-start: 0;
73
+ }
74
+ }
75
+ }