@birdycom/omniaui 0.12.5 → 0.13.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 (102) hide show
  1. package/README.md +1 -1
  2. package/bin/lib/copy-js.mjs +12 -1
  3. package/bin/omniaui.mjs +2 -2
  4. package/dist/barcode.d.ts +15 -0
  5. package/dist/barcode.js +164 -0
  6. package/dist/components-extra.css +15 -1
  7. package/dist/components.css +2237 -256
  8. package/dist/components.min.css +1 -1
  9. package/dist/contact-form.d.ts +5 -0
  10. package/dist/contact-form.js +138 -0
  11. package/dist/data-table.js +135 -98
  12. package/dist/fly.d.ts +4 -0
  13. package/dist/fly.js +80 -0
  14. package/dist/form-extras.d.ts +11 -0
  15. package/dist/form-extras.js +214 -0
  16. package/dist/lazy.d.ts +5 -0
  17. package/dist/lazy.js +37 -0
  18. package/dist/oc-theme.css +3 -1
  19. package/dist/parts/app-data-table-footer.css +38 -0
  20. package/dist/parts/app-data-table-rich.css +161 -0
  21. package/dist/parts/app-data-table.css +44 -74
  22. package/dist/parts/app-name-card.css +65 -0
  23. package/dist/parts/app-result.css +39 -0
  24. package/dist/parts/app-titlebar.css +16 -8
  25. package/dist/parts/data-meter-row.css +40 -0
  26. package/dist/parts/data-price.css +20 -0
  27. package/dist/parts/data-ribbon.css +76 -0
  28. package/dist/parts/data-spec-list.css +36 -0
  29. package/dist/parts/data-stock.css +41 -0
  30. package/dist/parts/forms-affix-input.css +1 -1
  31. package/dist/parts/forms-avatar-picker.css +43 -0
  32. package/dist/parts/forms-date-field.css +53 -0
  33. package/dist/parts/forms-density.css +13 -0
  34. package/dist/parts/forms-form-section.css +64 -0
  35. package/dist/parts/forms-form.css +76 -0
  36. package/dist/parts/forms-quantity.css +55 -0
  37. package/dist/parts/forms-rating.css +81 -0
  38. package/dist/parts/media-attachment.css +1 -1
  39. package/dist/parts/media-barcode.css +23 -0
  40. package/dist/parts/media-chat-composer.css +3 -0
  41. package/dist/parts/media-emoji-picker.css +3 -1
  42. package/dist/parts/media-gallery.css +55 -0
  43. package/dist/parts/media-glass-liquid.css +99 -0
  44. package/dist/parts/media-glass-options.css +50 -0
  45. package/dist/parts/media-lazy.css +46 -0
  46. package/dist/parts/media-lightbox.css +4 -0
  47. package/dist/parts/media-message-thread.css +1 -1
  48. package/dist/parts/media-message.css +1 -1
  49. package/dist/parts/nav-topbar-variants.css +96 -0
  50. package/dist/parts/site-base.css +33 -0
  51. package/dist/parts/site-brand-mark.css +56 -0
  52. package/dist/parts/site-cards.css +183 -0
  53. package/dist/parts/site-fly.css +40 -0
  54. package/dist/parts/site-header-variants.css +70 -0
  55. package/dist/parts/site-layout.css +69 -0
  56. package/dist/parts/site-panel.css +29 -0
  57. package/dist/parts/site-prose.css +13 -1
  58. package/dist/parts/utility-hover.css +44 -0
  59. package/dist/showcase.css +114 -22
  60. package/manifest.json +193 -9
  61. package/package.json +7 -2
  62. package/snippets/accordion-accent.html +1 -1
  63. package/snippets/attachment.html +4 -4
  64. package/snippets/avatar-picker.html +40 -0
  65. package/snippets/barcode.html +20 -0
  66. package/snippets/brand-mark.html +41 -0
  67. package/snippets/chat-composer.html +3 -3
  68. package/snippets/data-table.html +51 -76
  69. package/snippets/date-field.html +44 -0
  70. package/snippets/doc-viewer.html +1 -1
  71. package/snippets/emoji-picker.html +1 -1
  72. package/snippets/feature-card.html +52 -0
  73. package/snippets/fly-in.html +30 -0
  74. package/snippets/form-density.html +25 -0
  75. package/snippets/form-section.html +47 -0
  76. package/snippets/gallery.html +25 -0
  77. package/snippets/hover-effects.html +19 -0
  78. package/snippets/icon-list.html +28 -0
  79. package/snippets/lazy.html +39 -0
  80. package/snippets/message-thread.html +12 -12
  81. package/snippets/message.html +10 -10
  82. package/snippets/meter-row.html +29 -0
  83. package/snippets/name-card.html +72 -0
  84. package/snippets/page-header.html +1 -1
  85. package/snippets/page-sections.html +50 -0
  86. package/snippets/panel.html +18 -0
  87. package/snippets/price.html +25 -0
  88. package/snippets/prose.html +3 -3
  89. package/snippets/quantity.html +12 -0
  90. package/snippets/rating.html +26 -0
  91. package/snippets/result.html +31 -0
  92. package/snippets/ribbon.html +48 -0
  93. package/snippets/shot-grid.html +27 -0
  94. package/snippets/sidebar.html +3 -3
  95. package/snippets/spec-list.html +34 -0
  96. package/snippets/stock.html +20 -0
  97. package/snippets/table-actions.html +5 -5
  98. package/snippets/table.html +2 -2
  99. package/snippets/titlebar.html +1 -1
  100. package/snippets/user.html +3 -3
  101. package/dist/parts/app-column-menu.css +0 -85
  102. package/snippets/column-menu.html +0 -38
@@ -1,5 +1,6 @@
1
- /* File: packages/ui/src/parts/app-data-table.css | Map: shell -> toolbar -> frame and scroll -> header resize -> select and actions columns -> deleted row -> bulk bar -> card view -> footer and pager -> compact density */
2
- /* List table from Korrekt and OmniaDesk. The frame, toolbar and footer are styling.
1
+ /* File: packages/ui/src/parts/app-data-table.css | Map: shell -> toolbar -> frame and scroll -> header label -> select and actions columns -> deleted row -> bulk bar -> card view -> compact density */
2
+ /* List table for business apps. The frame, toolbar and footer are styling. Tabs, filter row, rich cells and
3
+ card fields: app-data-table-rich.css; footer and pager: app-data-table-footer.css.
3
4
  Behaviour (search, sort, pages, resize, drag, trash, bulk) lives in src/data-table.js.
4
5
  Set --oc-data-table-max on the shell when the body should scroll and the header should stick. */
5
6
 
@@ -12,13 +13,16 @@
12
13
  background: var(--oc-card);
13
14
  border: 1px solid var(--oc-border);
14
15
  border-radius: var(--oc-radius);
16
+ /* Full width, and in a grid or flex parent the table scrolls inside the frame instead of stretching the card. */
17
+ inline-size: 100%;
18
+ min-inline-size: 0;
15
19
  }
16
20
 
17
- /* Toolbar: title + gear, search, trash pills, table/cards toggle, page actions. */
21
+ /* Toolbar: title + gear/filter button, search (takes the free width), trash pills, table/cards toggle, page actions. */
18
22
  .oc-data-table-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; }
19
23
  .oc-data-table-heading { display: flex; align-items: center; gap: 0.5rem; min-inline-size: 0; }
20
24
  .oc-data-table-title { font-size: 0.875rem; font-weight: 600; }
21
- .oc-data-table-search { flex: 1 1 12rem; min-inline-size: 10rem; max-inline-size: 22rem; }
25
+ .oc-data-table-search { flex: 1 1 10rem; min-inline-size: 9rem; }
22
26
  .oc-data-table-actions { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-inline-start: auto; }
23
27
  .oc-data-table-view { display: inline-flex; gap: 0.25rem; }
24
28
  .oc-data-table-view [aria-pressed="true"] {
@@ -26,55 +30,52 @@
26
30
  background: oklch(from var(--oc-primary) l c h / 0.1);
27
31
  border-color: oklch(from var(--oc-primary) l c h / 0.35);
28
32
  }
29
- .oc-data-table-sort-summary { flex-basis: 100%; margin: 0; font-size: 0.75rem; color: var(--oc-muted-foreground); }
30
- .oc-data-table-sort-summary:empty { display: none; }
31
- .oc-data-table-file { display: none; }
33
+ /* In the toolbar the trash pills read as one switch: the chosen one is filled. */
34
+ .oc-data-table-toolbar .oc-filter-pills { border-inline-start: 0; padding-inline-start: 0; }
35
+ .oc-data-table-toolbar .oc-filter-pill[aria-current="true"] {
36
+ color: var(--oc-primary-foreground);
37
+ background: var(--oc-primary);
38
+ border-color: var(--oc-primary);
39
+ }
32
40
 
33
41
  /* The table sits in its own rounded frame. The scroll box keeps the header sticky. */
34
42
  .oc-data-table-frame {
43
+ min-inline-size: 0;
35
44
  overflow: hidden;
36
- background: var(--oc-background);
45
+ background: var(--oc-card);
37
46
  border: 1px solid var(--oc-border);
38
47
  border-radius: calc(var(--oc-radius) - 2px);
39
48
  }
40
49
  .oc-data-table-scroll { overflow: auto; max-block-size: var(--oc-data-table-max, none); }
41
50
  .oc-data-table .oc-table :is(th, td) { border-inline: 0; border-block-start: 0; }
51
+ /* Columns size to their content and cells stay on one line: a narrow screen scrolls the frame sideways instead of
52
+ squeezing names and phone numbers onto two lines. .oc-data-table-wrap on a cell lets long text wrap again. */
53
+ .oc-data-table .oc-data-table-table { table-layout: auto; }
54
+ .oc-data-table .oc-data-table-table :is(th, td) { white-space: nowrap; vertical-align: middle; padding-inline: 0.625rem; }
55
+ .oc-data-table .oc-data-table-table td.oc-data-table-wrap { white-space: normal; }
42
56
  .oc-data-table .oc-table thead th {
43
57
  position: sticky;
44
58
  inset-block-start: 0;
45
59
  z-index: 1;
46
- block-size: 2.5rem;
47
- font-size: 0.75rem;
48
- font-weight: 500;
60
+ block-size: 2.75rem;
61
+ font-size: 0.8125rem;
62
+ font-weight: 600;
63
+ color: var(--oc-foreground);
49
64
  background: var(--oc-card);
50
65
  }
51
- .oc-data-table .oc-table tbody tr:nth-child(even) { background: var(--oc-surface); }
66
+ /* Striped rows (every second visible row), the row under the pointer a step stronger. Solid colors, so the sticky
67
+ actions cell can take the same background (inherit) and nothing scrolls through it. */
68
+ .oc-data-table {
69
+ --oc-data-table-stripe: color-mix(in oklch, var(--oc-card), var(--oc-foreground) 3.5%);
70
+ --oc-data-table-hover: color-mix(in oklch, var(--oc-card), var(--oc-primary) 8%);
71
+ }
72
+ .oc-data-table .oc-table tbody tr:nth-child(even of :not([hidden])) { background: var(--oc-data-table-stripe); }
73
+ .oc-data-table .oc-table tbody tr:hover { background: var(--oc-data-table-hover); }
74
+ .oc-data-table--plain .oc-table tbody tr:nth-child(even of :not([hidden])):not(:hover) { background: none; }
52
75
  .oc-data-table .oc-table tbody tr:last-child > td { border-block-end: 0; }
53
76
 
54
- /* Header label and the drag handle that sets the column width. */
55
- .oc-data-table-th { display: flex; align-items: center; gap: 0.25rem; position: relative; min-inline-size: 0; }
56
- .oc-data-table-resize {
57
- position: absolute;
58
- inset-block: 0;
59
- inset-inline-end: -0.45rem;
60
- inline-size: 0.55rem;
61
- padding: 0;
62
- background: none;
63
- border: 0;
64
- cursor: col-resize;
65
- touch-action: none;
66
- }
67
- .oc-data-table-resize::after {
68
- content: "";
69
- position: absolute;
70
- inset-block: 0.4rem;
71
- inset-inline-start: 0.25rem;
72
- inline-size: 1px;
73
- background: var(--oc-border);
74
- }
75
- .oc-data-table.is-resizing { cursor: col-resize; user-select: none; }
76
- /* After a resize the table grows with its columns and the frame scrolls sideways. */
77
- .oc-data-table-table--sized { inline-size: max-content; min-inline-size: 100%; }
77
+ /* Header label (column width by dragging is OmniaUI Pro: app-data-table-pro.css). */
78
+ .oc-data-table-th { display: flex; align-items: center; gap: 0.25rem; min-inline-size: 0; }
78
79
 
79
80
  /* Narrow control columns. The actions column stays on the right while the rest scrolls. */
80
81
  .oc-data-table .oc-table :is(th, td)[data-oc-col="select"],
@@ -90,8 +91,8 @@
90
91
  background: var(--oc-card);
91
92
  box-shadow: -1px 0 0 var(--oc-border);
92
93
  }
93
- .oc-data-table .oc-table thead th[data-oc-col="actions"] { z-index: 3; }
94
- .oc-data-table .oc-table tbody tr:nth-child(even) > td[data-oc-col="actions"] { background: var(--oc-surface); }
94
+ .oc-data-table .oc-table thead th[data-oc-col="actions"] { z-index: 3; background: var(--oc-card); }
95
+ .oc-data-table .oc-table tbody tr:is(:nth-child(even of :not([hidden])), :hover) > td[data-oc-col="actions"] { background: inherit; }
95
96
 
96
97
  /* Trash and archive rows stay readable but quieter than active rows. */
97
98
  .oc-data-table tr[data-oc-status="deleted"],
@@ -120,55 +121,24 @@
120
121
  padding: 0.75rem;
121
122
  }
122
123
  .oc-data-table-card {
123
- padding: 0.75rem;
124
+ display: flex;
125
+ flex-direction: column;
126
+ gap: 0.75rem;
127
+ padding: 0.875rem;
124
128
  background: var(--oc-card);
125
129
  border: 1px solid var(--oc-border);
126
130
  border-radius: calc(var(--oc-radius) - 2px);
127
131
  }
128
132
  .oc-data-table-card-title { margin: 0; font-size: 0.875rem; font-weight: 600; }
129
-
133
+ .oc-data-table-card-title .oc-data-table-name-sub { font-weight: 400; }
130
134
  .oc-data-table-empty { margin: 0; padding: 2rem 1rem; text-align: center; color: var(--oc-muted-foreground); }
131
135
  .oc-data-table-empty[hidden] { display: none; }
132
136
 
133
- /* Footer: count on the left, rows-per-page in the middle, pager on the right. */
134
- .oc-data-table-footer {
135
- display: grid;
136
- grid-template-columns: 1fr auto 1fr;
137
- align-items: center;
138
- gap: 0.75rem;
139
- }
140
- .oc-data-table-summary { margin: 0; font-size: 0.8125rem; color: var(--oc-muted-foreground); }
141
- .oc-data-table-per-page { display: inline-flex; align-items: center; gap: 0.5rem; font-size: 0.8125rem; }
142
- .oc-data-table-per-page .oc-select { inline-size: auto; }
143
- .oc-data-table-pager { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 0.25rem; }
144
- .oc-data-table-page {
145
- min-inline-size: 2rem;
146
- padding: 0.25rem 0.45rem;
147
- font: inherit;
148
- font-size: 0.8125rem;
149
- color: var(--oc-foreground);
150
- background: var(--oc-card);
151
- border: 1px solid var(--oc-border);
152
- border-radius: calc(var(--oc-radius) - 2px);
153
- cursor: pointer;
154
- }
155
- .oc-data-table-page[aria-current="page"] {
156
- color: var(--oc-primary);
157
- background: oklch(from var(--oc-primary) l c h / 0.1);
158
- border-color: oklch(from var(--oc-primary) l c h / 0.35);
159
- }
160
- .oc-data-table-page:disabled { opacity: 0.4; cursor: default; }
161
- .oc-data-table-page:focus-visible { outline: 2px solid var(--oc-ring); outline-offset: 1px; }
162
- .oc-data-table-ellipsis { padding-inline: 0.25rem; color: var(--oc-muted-foreground); }
163
137
 
164
138
  /* Design choice from the gear menu: tighter rows, like the compact article lists. */
165
139
  .oc-data-table--compact .oc-table :is(th, td) { padding: 0.25rem 0.5rem; font-size: 0.8125rem; }
166
140
  .oc-data-table--compact .oc-table thead th { block-size: 2rem; }
167
141
 
168
- @media (max-width: 40rem) {
169
- .oc-data-table-footer { grid-template-columns: 1fr; }
170
- .oc-data-table-pager { justify-content: flex-start; }
171
- }
172
142
  @media (prefers-reduced-motion: reduce) {
173
143
  .oc-data-table .oc-table tbody tr { transition: none; }
174
144
  }
@@ -0,0 +1,65 @@
1
+ /* File: packages/ui/src/parts/app-name-card.css | Map: card (details) -> head (chevron, name, rename, amount, actions) -> rename input -> body -> nested cards */
2
+ /* Name card: a collapsible card for one part of a quote or plan,
3
+ with its name, a rename pencil, an optional amount and a delete button. Native <details>: it opens without script;
4
+ packages/ui/src/name-card.js adds the rename (pencil -> input, Enter saves, Escape cancels). */
5
+
6
+ .oc-name-card {
7
+ background: var(--oc-card);
8
+ border: 1px solid var(--oc-border);
9
+ border-radius: var(--oc-radius);
10
+ }
11
+ .oc-name-card + .oc-name-card { margin-block-start: 0.5rem; }
12
+ .oc-name-card__head {
13
+ display: flex;
14
+ align-items: center;
15
+ gap: 0.5rem;
16
+ min-block-size: 2.75rem;
17
+ padding: 0.375rem 0.5rem 0.375rem 0.75rem;
18
+ list-style: none;
19
+ cursor: pointer;
20
+ }
21
+ .oc-name-card__head::-webkit-details-marker { display: none; }
22
+ .oc-name-card__head::before {
23
+ content: "";
24
+ flex-shrink: 0;
25
+ inline-size: 1rem;
26
+ block-size: 1rem;
27
+ background: var(--oc-muted-foreground);
28
+ -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M8.59 16.58L13.17 12L8.59 7.41L10 6l6 6l-6 6z'/%3E%3C/svg%3E") center / contain no-repeat;
29
+ mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M8.59 16.58L13.17 12L8.59 7.41L10 6l6 6l-6 6z'/%3E%3C/svg%3E") center / contain no-repeat;
30
+ transition: rotate var(--oc-transition-fast);
31
+ }
32
+ .oc-name-card[open] > .oc-name-card__head::before { rotate: 90deg; }
33
+ .oc-name-card__head:focus-visible { outline: 2px solid var(--oc-ring); outline-offset: -2px; border-radius: var(--oc-radius); }
34
+ .oc-name-card__title { min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; }
35
+ .oc-name-card__meta { font-size: 0.75rem; color: var(--oc-muted-foreground); white-space: nowrap; }
36
+ .oc-name-card__head .oc-btn { flex-shrink: 0; }
37
+ /* Amount and actions sit on the right; the amount is tabular so a column of cards lines up. */
38
+ .oc-name-card__amount { margin-inline-start: auto; font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; }
39
+ .oc-name-card__actions { display: inline-flex; flex-shrink: 0; gap: 0.25rem; margin-inline-start: auto; }
40
+ .oc-name-card__amount + .oc-name-card__actions { margin-inline-start: 0.5rem; }
41
+
42
+ /* Rename: the title becomes an input of the same size (hidden toggles between the two). */
43
+ .oc-name-card__input {
44
+ flex: 1;
45
+ min-inline-size: 0;
46
+ block-size: 2rem;
47
+ padding: 0 0.5rem;
48
+ font: inherit;
49
+ font-weight: 600;
50
+ color: var(--oc-foreground);
51
+ background: var(--oc-background);
52
+ border: 1px solid var(--oc-ring);
53
+ border-radius: calc(var(--oc-radius) - 2px);
54
+ }
55
+ .oc-name-card__input[hidden],
56
+ .oc-name-card__title[hidden] { display: none; }
57
+
58
+ .oc-name-card__body { padding: 0.75rem 0.75rem 0.875rem 2.25rem; border-block-start: 1px solid var(--oc-border); }
59
+ /* Nested cards (the parts of a title) use a quieter surface; --total colors the amount of the main card. */
60
+ .oc-name-card__body > .oc-name-card { background: var(--oc-surface); }
61
+ .oc-name-card--total > .oc-name-card__head .oc-name-card__amount { color: var(--oc-primary); }
62
+
63
+ @media (prefers-reduced-motion: reduce) {
64
+ .oc-name-card__head::before { transition: none; }
65
+ }
@@ -0,0 +1,39 @@
1
+ /* File: packages/ui/src/parts/app-result.css */
2
+ /* Map: result -> icon tile -> title -> text -> actions */
3
+ /* The outcome of an action in the place where the form was: "Thank you, your message was sent" with an icon tile, a
4
+ short text and a button ("Send another message"). Give it role="status" and tabindex="-1" and move the focus to it.
5
+ --center centers it; the tile takes its color from an .oc-accent--* preset. */
6
+
7
+ .oc-result {
8
+ display: grid;
9
+ justify-items: start;
10
+ gap: 0.75rem;
11
+ }
12
+ .oc-result:focus { outline: none; }
13
+ .oc-result[hidden] { display: none; }
14
+ .oc-result > .oc-icon-tile { margin-block-end: 0.35rem; }
15
+ .oc-result__title {
16
+ margin: 0;
17
+ font-size: 1.2rem;
18
+ line-height: 1.25;
19
+ }
20
+ .oc-result__text {
21
+ margin: 0;
22
+ color: var(--oc-muted-foreground);
23
+ }
24
+ .oc-result__text a {
25
+ color: var(--oc-primary);
26
+ text-decoration: underline;
27
+ text-underline-offset: 3px;
28
+ }
29
+ .oc-result__actions {
30
+ display: flex;
31
+ flex-wrap: wrap;
32
+ gap: 0.75rem;
33
+ margin-block-start: 0.5rem;
34
+ }
35
+ .oc-result--center {
36
+ justify-items: center;
37
+ text-align: center;
38
+ }
39
+ .oc-result--center .oc-result__actions { justify-content: center; }
@@ -10,6 +10,11 @@
10
10
  min-block-size: 0;
11
11
  timeline-scope: --oc-titlebar-scroll;
12
12
  }
13
+ /* A card with a title bar on top: the bar runs edge to edge, its last button follows the card corner. */
14
+ .oc-card:has(> .oc-titlebar:first-child) { padding: 0; --oc-titlebar-radius: calc(var(--oc-radius) + 3px); }
15
+ /* As the heading of a card section (bar + .oc-card-content): the title lines up with the content below. */
16
+ .oc-card > .oc-titlebar:first-child:has(+ .oc-card-content) { padding-inline-start: 24px; }
17
+ .oc-card > .oc-titlebar:first-child:has(+ .oc-card-content) .oc-titlebar__title { font-size: 0.9375rem; }
13
18
 
14
19
  .oc-titlebar {
15
20
  display: flex;
@@ -17,7 +22,7 @@
17
22
  align-items: center;
18
23
  gap: 2px;
19
24
  min-block-size: 46px;
20
- padding: 0 8px 0 14px;
25
+ padding: 0 0 0 14px;
21
26
  border-block-end: 1px solid transparent;
22
27
  -webkit-user-select: none;
23
28
  user-select: none;
@@ -34,7 +39,7 @@
34
39
  /* Title area: grows, cuts the subtitle off with …. */
35
40
  .oc-titlebar__main { display: flex; flex: 1; align-items: center; gap: 8px; min-inline-size: 0; }
36
41
  .oc-titlebar__icon { flex: none; inline-size: 18px; block-size: 18px; }
37
- .oc-titlebar__title { flex: none; font-size: 0.8667rem; font-weight: 650; letter-spacing: -0.01em; }
42
+ .oc-titlebar__title { flex: none; margin: 0; font-size: 0.8667rem; font-weight: 650; letter-spacing: -0.01em; }
38
43
  .oc-titlebar__subtitle {
39
44
  min-inline-size: 0;
40
45
  overflow: hidden;
@@ -44,17 +49,19 @@
44
49
  white-space: nowrap;
45
50
  }
46
51
  .oc-titlebar__subtitle::before { content: "·"; margin-inline-end: 8px; }
47
- .oc-titlebar__actions { display: flex; flex: none; align-items: center; gap: 2px; }
52
+ /* Window buttons sit in the top right corner (as on a desktop window), not centred on the title line. */
53
+ .oc-titlebar__actions { display: flex; flex: none; align-self: flex-start; align-items: flex-start; margin-inline-start: auto; }
48
54
 
49
- /* Compact icon buttons: muted, a light surface on hover, the close button red. */
55
+ /* Window buttons: muted, a light surface on hover, the close button red. The last one follows the
56
+ rounded corner of the card or window around the bar (--oc-titlebar-radius). */
50
57
  .oc-titlebar__btn {
51
58
  display: inline-grid;
52
59
  place-items: center;
53
- inline-size: 30px;
54
- block-size: 30px;
60
+ inline-size: 40px;
61
+ block-size: 34px;
55
62
  padding: 0;
56
63
  border: 0;
57
- border-radius: 8px;
64
+ border-radius: 0;
58
65
  background: transparent;
59
66
  color: var(--oc-muted-foreground);
60
67
  font: inherit;
@@ -65,6 +72,7 @@
65
72
  .oc-titlebar__btn:hover { background: var(--oc-glass-panel-strong, var(--oc-accent)); color: var(--oc-foreground); }
66
73
  .oc-titlebar__btn:focus-visible { outline: 2px solid var(--oc-ring); outline-offset: -2px; }
67
74
  .oc-titlebar__btn--close:hover { background: var(--oc-destructive); color: var(--oc-destructive-foreground); }
75
+ .oc-titlebar__actions > .oc-titlebar__btn:last-child { border-start-end-radius: var(--oc-titlebar-radius, calc(var(--oc-radius) - 1px)); }
68
76
 
69
77
  /* Body: the part that scrolls under the bar. */
70
78
  .oc-titlebar-body {
@@ -89,7 +97,7 @@
89
97
 
90
98
  /* Sizes */
91
99
  .oc-titlebar--sm { min-block-size: 36px; padding-inline-start: 10px; }
92
- .oc-titlebar--sm .oc-titlebar__btn { inline-size: 26px; block-size: 26px; border-radius: 6px; }
100
+ .oc-titlebar--sm .oc-titlebar__btn { inline-size: 34px; block-size: 28px; }
93
101
 
94
102
  @media (prefers-reduced-motion: reduce) {
95
103
  .oc-titlebar-window > .oc-titlebar { animation: none; }
@@ -0,0 +1,40 @@
1
+ /* File: packages/ui/src/parts/data-meter-row.css | Map: row grid -> label and value -> bar and fill -> levels -> reset line -> list */
2
+ /* Usage meter row: a label, the usage as text, a bar and when it resets. The fill comes from
3
+ --oc-meter (0–100%); data-level="warn" or "critical" changes the color. */
4
+
5
+ .oc-meter-row {
6
+ display: grid;
7
+ grid-template-columns: minmax(0, 1fr) auto;
8
+ align-items: baseline;
9
+ gap: 0.375rem 0.75rem;
10
+ }
11
+ .oc-meter-row__label { min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.875rem; font-weight: 600; }
12
+ .oc-meter-row__value { font-size: 0.8125rem; font-variant-numeric: tabular-nums; color: var(--oc-muted-foreground); white-space: nowrap; }
13
+ .oc-meter-row__value strong { color: var(--oc-foreground); }
14
+ .oc-meter-row__bar {
15
+ grid-column: 1 / -1;
16
+ block-size: 0.5rem;
17
+ overflow: hidden;
18
+ background: var(--oc-muted);
19
+ border-radius: 999px;
20
+ }
21
+ .oc-meter-row__bar::before {
22
+ content: "";
23
+ display: block;
24
+ inline-size: clamp(0%, var(--oc-meter, 0%), 100%);
25
+ block-size: 100%;
26
+ background: var(--oc-meter-color, var(--oc-success));
27
+ border-radius: inherit;
28
+ transition: inline-size 0.4s ease;
29
+ }
30
+ .oc-meter-row[data-level="warn"] { --oc-meter-color: var(--oc-warning); }
31
+ .oc-meter-row[data-level="critical"] { --oc-meter-color: var(--oc-destructive); }
32
+ .oc-meter-row[data-level="critical"] .oc-meter-row__value strong { color: var(--oc-destructive); }
33
+ .oc-meter-row__reset { grid-column: 1 / -1; display: inline-flex; align-items: center; gap: 0.25rem; font-size: 0.75rem; color: var(--oc-muted-foreground); }
34
+ .oc-meter-list { display: grid; gap: 1rem; }
35
+ .oc-meter-list--compact { gap: 0.625rem; }
36
+ .oc-meter-list--compact .oc-meter-row__bar { block-size: 0.375rem; }
37
+
38
+ @media (prefers-reduced-motion: reduce) {
39
+ .oc-meter-row__bar::before { transition: none; }
40
+ }
@@ -0,0 +1,20 @@
1
+ /* File: packages/ui/src/parts/data-price.css | Map: price block -> main line (amount, old price, discount) -> VAT note and alternative -> sizes and sale */
2
+ /* A price as shops and quotes show it: the amount, an optional old price and discount badge, a VAT note
3
+ ("excl. VAT") and the other amount ("€ 121,00 incl. VAT"). Numbers use tabular digits. */
4
+
5
+ .oc-price { display: inline-grid; gap: 0.125rem; min-inline-size: 0; }
6
+ .oc-price__main { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.25rem 0.5rem; }
7
+ .oc-price__amount { font-size: 1.25rem; font-weight: 700; line-height: 1.2; letter-spacing: -0.01em; font-variant-numeric: tabular-nums; white-space: nowrap; }
8
+ .oc-price__old { font-size: 0.875rem; color: var(--oc-muted-foreground); text-decoration: line-through; text-decoration-thickness: 1px; font-variant-numeric: tabular-nums; white-space: nowrap; }
9
+ .oc-price__main > .oc-badge { align-self: center; }
10
+ .oc-price__note { font-size: 0.75rem; font-weight: 500; color: var(--oc-muted-foreground); }
11
+ .oc-price__alt { font-size: 0.8125rem; color: var(--oc-muted-foreground); font-variant-numeric: tabular-nums; }
12
+
13
+ .oc-price--sm .oc-price__amount { font-size: 1rem; }
14
+ .oc-price--lg .oc-price__amount { font-size: 1.75rem; }
15
+ .oc-price--sale .oc-price__amount { color: var(--oc-destructive); }
16
+ /* Inline: amount and note on one line (table cells, lists). */
17
+ .oc-price--inline { display: inline-flex; flex-wrap: wrap; align-items: baseline; gap: 0.25rem 0.5rem; }
18
+ .oc-price--inline .oc-price__amount { font-size: inherit; font-weight: 600; }
19
+ td[data-numeric] > .oc-price, .oc-price--end { justify-items: end; text-align: end; }
20
+ td[data-numeric] > .oc-price .oc-price__main, .oc-price--end .oc-price__main { justify-content: flex-end; }
@@ -0,0 +1,76 @@
1
+ /* File: packages/ui/src/parts/data-ribbon.css | Map: host (positioned, clipped) -> ribbon band -> four corners -> tones -> size -> RTL */
2
+ /* Corner ribbon: a diagonal band across one corner of a card, an image or a document ("Paid", "New", "Sale").
3
+ Put .oc-ribbon as a child of the box; the box gets position: relative and clips the band (zero specificity,
4
+ so your own position or overflow wins). The text is real text, so screen readers read it. */
5
+
6
+ :where(:has(> .oc-ribbon)) { position: relative; overflow: clip; }
7
+
8
+ .oc-ribbon {
9
+ /* Distance from the corner to the middle of the band, along both edges. */
10
+ --oc-ribbon-inset: 2.25rem;
11
+ --oc-ribbon-x: 1;
12
+ --oc-ribbon-y: -1;
13
+ --oc-ribbon-turn: 45deg;
14
+ --oc-ribbon-bg: var(--oc-primary);
15
+ --oc-ribbon-fg: var(--oc-primary-foreground);
16
+ position: absolute;
17
+ z-index: 2;
18
+ inset-block-start: var(--oc-ribbon-inset);
19
+ inset-inline-end: var(--oc-ribbon-inset);
20
+ inline-size: calc(var(--oc-ribbon-inset) * 2.83 + 3rem);
21
+ padding: 0.3em 0;
22
+ overflow: hidden;
23
+ color: var(--oc-ribbon-fg);
24
+ background: var(--oc-ribbon-bg);
25
+ box-shadow: 0 1px 3px oklch(0 0 0 / 0.18);
26
+ font-size: 0.75rem;
27
+ font-weight: 700;
28
+ line-height: 1.25;
29
+ letter-spacing: 0.08em;
30
+ text-align: center;
31
+ text-overflow: ellipsis;
32
+ text-transform: uppercase;
33
+ white-space: nowrap;
34
+ pointer-events: none;
35
+ translate: calc(50% * var(--oc-ribbon-x)) calc(50% * var(--oc-ribbon-y));
36
+ rotate: var(--oc-ribbon-turn);
37
+ }
38
+
39
+ /* Corners: top end is the default. */
40
+ .oc-ribbon--top-start {
41
+ inset-inline-end: auto;
42
+ inset-inline-start: var(--oc-ribbon-inset);
43
+ --oc-ribbon-x: -1;
44
+ --oc-ribbon-turn: -45deg;
45
+ }
46
+ .oc-ribbon--bottom-end {
47
+ inset-block-start: auto;
48
+ inset-block-end: var(--oc-ribbon-inset);
49
+ --oc-ribbon-y: 1;
50
+ --oc-ribbon-turn: -45deg;
51
+ }
52
+ .oc-ribbon--bottom-start {
53
+ inset-block-start: auto;
54
+ inset-block-end: var(--oc-ribbon-inset);
55
+ inset-inline-end: auto;
56
+ inset-inline-start: var(--oc-ribbon-inset);
57
+ --oc-ribbon-x: -1;
58
+ --oc-ribbon-y: 1;
59
+ }
60
+
61
+ /* Tones. */
62
+ .oc-ribbon--success { --oc-ribbon-bg: var(--oc-success); --oc-ribbon-fg: var(--oc-success-foreground); }
63
+ .oc-ribbon--warning { --oc-ribbon-bg: var(--oc-warning); --oc-ribbon-fg: var(--oc-warning-foreground); }
64
+ .oc-ribbon--destructive { --oc-ribbon-bg: var(--oc-destructive); --oc-ribbon-fg: var(--oc-destructive-foreground); }
65
+ .oc-ribbon--muted { --oc-ribbon-bg: var(--oc-muted); --oc-ribbon-fg: var(--oc-muted-foreground); }
66
+
67
+ /* Sizes: a smaller band for a product card, a larger one for a document or a hero image. */
68
+ .oc-ribbon--sm { --oc-ribbon-inset: 1.6rem; font-size: 0.625rem; }
69
+ .oc-ribbon--lg { --oc-ribbon-inset: 2.9rem; font-size: 0.875rem; }
70
+
71
+ /* Right to left: start and end swap sides, so the band leans the other way. */
72
+ .oc-ribbon:dir(rtl) { translate: calc(-50% * var(--oc-ribbon-x)) calc(50% * var(--oc-ribbon-y)); rotate: calc(var(--oc-ribbon-turn) * -1); }
73
+
74
+ @media (forced-colors: active) {
75
+ .oc-ribbon { forced-color-adjust: none; color: Canvas; background: CanvasText; }
76
+ }
@@ -0,0 +1,36 @@
1
+ /* File: packages/ui/src/parts/data-spec-list.css | Map: list (container) -> groups in columns -> group title -> rows (term / value) -> stack modifier */
2
+ /* Technical specifications in named groups (Dimensions, Electrical, Package …): label / value rows with tabular
3
+ digits. Two columns of groups once the list is wide enough (container query), one column with --stack. */
4
+
5
+ .oc-spec-list { container-type: inline-size; inline-size: 100%; display: grid; gap: 1.25rem; min-inline-size: 0; }
6
+ @container (min-width: 40rem) {
7
+ .oc-spec-list:not(.oc-spec-list--stack) { grid-template-columns: repeat(2, minmax(0, 1fr)); align-items: start; gap: 1.25rem 2rem; }
8
+ }
9
+ .oc-spec-group { min-inline-size: 0; }
10
+ .oc-spec-group__title {
11
+ display: flex;
12
+ align-items: center;
13
+ gap: 0.5rem;
14
+ margin: 0 0 0.25rem;
15
+ padding-block-end: 0.375rem;
16
+ font-size: 0.75rem;
17
+ font-weight: 600;
18
+ letter-spacing: 0.04em;
19
+ text-transform: uppercase;
20
+ color: var(--oc-muted-foreground);
21
+ border-block-end: 1px solid var(--oc-border);
22
+ }
23
+ .oc-spec-group__title > :is(svg, iconify-icon) { inline-size: 1rem; block-size: 1rem; font-size: 1rem; }
24
+ .oc-spec-group__rows { margin: 0; }
25
+ .oc-spec-row {
26
+ display: grid;
27
+ grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr);
28
+ gap: 1rem;
29
+ padding: 0.4rem 0;
30
+ font-size: 0.875rem;
31
+ border-block-end: 1px dashed var(--oc-border);
32
+ }
33
+ .oc-spec-row:last-child { border-block-end: 0; }
34
+ .oc-spec-row > dt { margin: 0; color: var(--oc-muted-foreground); }
35
+ .oc-spec-row > dd { margin: 0; min-inline-size: 0; font-weight: 500; font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
36
+ .oc-spec-row > dd > small { margin-inline-start: 0.25rem; font-weight: 400; color: var(--oc-muted-foreground); }
@@ -0,0 +1,41 @@
1
+ /* File: packages/ui/src/parts/data-stock.css | Map: indicator (dot + text) -> levels (ok, low, out, order) -> badge variant -> count */
2
+ /* Stock status: a colored dot with a short text ("In stock", "Only 3 left", "Out of stock", "On order").
3
+ data-level picks the color; the text always says it too, so color is never the only signal. */
4
+
5
+ .oc-stock {
6
+ --oc-stock-color: var(--oc-success);
7
+ /* Inline text (not flex), so "Only <b>3</b> left" keeps normal word spacing. */
8
+ display: inline-block;
9
+ font-size: 0.8125rem;
10
+ font-weight: 500;
11
+ line-height: 1.3;
12
+ color: var(--oc-foreground);
13
+ white-space: nowrap;
14
+ }
15
+ .oc-stock::before {
16
+ content: "";
17
+ display: inline-block;
18
+ margin-inline-end: 0.4rem;
19
+ vertical-align: 0.05em;
20
+ inline-size: 0.5rem;
21
+ block-size: 0.5rem;
22
+ background: var(--oc-stock-color);
23
+ border-radius: 50%;
24
+ box-shadow: 0 0 0 3px oklch(from var(--oc-stock-color) l c h / 0.18);
25
+ }
26
+ .oc-stock[data-level="low"] { --oc-stock-color: var(--oc-warning); }
27
+ .oc-stock[data-level="out"] { --oc-stock-color: var(--oc-destructive); }
28
+ .oc-stock[data-level="order"] { --oc-stock-color: var(--oc-info, light-dark(var(--color-deepskyblue-70), var(--color-skyblue-50))); }
29
+ .oc-stock__count { font-weight: 700; font-variant-numeric: tabular-nums; }
30
+ .oc-stock__note { font-weight: 400; color: var(--oc-muted-foreground); }
31
+
32
+ /* Badge: a tinted pill (table cells, product cards). */
33
+ .oc-stock--badge {
34
+ padding: 0.125rem 0.5rem 0.125rem 0.45rem;
35
+ font-size: 0.75rem;
36
+ color: color-mix(in oklch, var(--oc-stock-color) 75%, var(--oc-foreground));
37
+ background: oklch(from var(--oc-stock-color) l c h / 0.12);
38
+ border: 1px solid oklch(from var(--oc-stock-color) l c h / 0.3);
39
+ border-radius: 999px;
40
+ }
41
+ .oc-stock--badge::before { inline-size: 0.4rem; block-size: 0.4rem; margin-inline-end: 0.3rem; vertical-align: 0.1em; box-shadow: none; }
@@ -25,7 +25,7 @@
25
25
  min-inline-size: 0;
26
26
  }
27
27
 
28
- /* Symbol column: same chrome as the Korrekt / OmniaDesk fields (€, %, @, barcode icon).
28
+ /* Symbol column: the same chrome as the other fields (€, %, @, barcode icon).
29
29
  A divider separates the symbol from the value. */
30
30
  .oc-input-group.oc-input-group--affix .oc-input-addon {
31
31
  align-self: stretch;
@@ -0,0 +1,43 @@
1
+ /* File: packages/ui/src/parts/forms-avatar-picker.css | Map: picker -> preview (initials or image) -> file input -> text and buttons -> drop state -> square and logo */
2
+ /* Logo or avatar picker: a preview, "Upload" and "Remove", a hint, and dropping a file
3
+ on the preview. The file input is a real <input type="file">; packages/ui/src/avatar-picker.js shows the preview. */
4
+
5
+ .oc-avatar-picker { display: flex; align-items: center; gap: 1rem; }
6
+ .oc-avatar-picker__preview {
7
+ position: relative;
8
+ display: inline-flex;
9
+ align-items: center;
10
+ justify-content: center;
11
+ flex-shrink: 0;
12
+ inline-size: var(--oc-avatar-picker-size, 4.5rem);
13
+ block-size: var(--oc-avatar-picker-size, 4.5rem);
14
+ overflow: hidden;
15
+ font-size: 1.25rem;
16
+ font-weight: 600;
17
+ color: var(--oc-muted-foreground);
18
+ background: var(--oc-muted);
19
+ border: 1px dashed var(--oc-border-strong, var(--oc-border));
20
+ border-radius: 50%;
21
+ cursor: pointer;
22
+ }
23
+ .oc-avatar-picker--square .oc-avatar-picker__preview { border-radius: var(--oc-radius); }
24
+ .oc-avatar-picker__preview img { inline-size: 100%; block-size: 100%; object-fit: cover; }
25
+ .oc-avatar-picker--logo .oc-avatar-picker__preview img { object-fit: contain; padding: 0.375rem; background: var(--color-white-50); }
26
+ .oc-avatar-picker__preview:has(img) { border-style: solid; }
27
+ .oc-avatar-picker__preview:hover,
28
+ .oc-avatar-picker.is-dragover .oc-avatar-picker__preview {
29
+ color: var(--oc-primary);
30
+ background: oklch(from var(--oc-primary) l c h / 0.08);
31
+ border-color: var(--oc-primary);
32
+ }
33
+ .oc-avatar-picker__preview:has(:focus-visible) { outline: 2px solid var(--oc-ring); outline-offset: 2px; }
34
+ .oc-avatar-picker__file { position: absolute; inset: 0; opacity: 0; cursor: pointer; }
35
+ .oc-avatar-picker__text { display: grid; gap: 0.375rem; min-inline-size: 0; }
36
+ .oc-avatar-picker__label { font-size: 0.875rem; font-weight: 600; }
37
+ .oc-avatar-picker__hint { font-size: 0.75rem; color: var(--oc-muted-foreground); }
38
+ .oc-avatar-picker__actions { display: flex; flex-wrap: wrap; gap: 0.375rem; }
39
+ /* "Remove" only while there is an image. */
40
+ .oc-avatar-picker:not(:has(.oc-avatar-picker__preview img)) [data-oc-avatar-remove] { display: none; }
41
+ /* A refused file (wrong type or too large): red outline and the reason under the buttons. */
42
+ .oc-avatar-picker.is-invalid .oc-avatar-picker__preview { border-color: var(--oc-destructive); }
43
+ .oc-avatar-picker__error { font-size: 0.75rem; font-weight: 500; color: var(--oc-destructive); }