@ambasada/ui 0.7.1 → 0.7.4
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 +16 -1
- package/dist/ambasada-ui.css +289 -2
- package/dist/components.css +288 -1
- package/dist/react.d.ts +46 -1
- package/dist/react.js +53 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -61,6 +61,16 @@ The shell's insets, for anything you position yourself:
|
|
|
61
61
|
| `ui-empty` (`ui-empty-icon`) | Empty state: icon, `<strong>` title, hint, action. |
|
|
62
62
|
| `ui-skeleton` | Grey bars (`<span>`s) while loading: skeletons, not spinners. |
|
|
63
63
|
|
|
64
|
+
For tools built for the laptop that still open in the Ambasada app (the Developer app):
|
|
65
|
+
|
|
66
|
+
| Class | What |
|
|
67
|
+
|---|---|
|
|
68
|
+
| `ui-shell` > `ui-shell-side`, `ui-shell-scrim`, `ui-shell-main` > `ui-shell-top` (`ui-shell-menu`), `ui-shell-content` | 240 px sidebar, sticky top bar, content up to 1088 px. Under 900 px the sidebar is a drawer (`ui-shell open`). Clears the host bar and safe areas once. |
|
|
69
|
+
| `ui-nav` (`ui-nav-title`), `ui-nav-item` (`ui-nav-label`, `ui-nav-right`, `on`) | Sidebar navigation; the current item gets a red marker. |
|
|
70
|
+
| `ui-tabs` | Tabs as links under a title (`a.on`); scrolls sideways when narrow. |
|
|
71
|
+
| `ui-field` (`ui-field-label`, `ui-field-hint` `good` / `bad`), `ui-input` | Form field with one line of hint or error. |
|
|
72
|
+
| `ui-table` (`.num`, `tr.tappable`) | Data table: hairline rows, tabular figures. |
|
|
73
|
+
|
|
64
74
|
```html
|
|
65
75
|
<main class="ui-page">
|
|
66
76
|
<p class="ui-label">// My Company</p>
|
|
@@ -83,7 +93,7 @@ The shell's insets, for anything you position yourself:
|
|
|
83
93
|
## React
|
|
84
94
|
|
|
85
95
|
```tsx
|
|
86
|
-
import { Button, Card, Empty, Group, Page, Pill, Row, Sheet, Skeleton, Stepper } from "@ambasada/ui/react";
|
|
96
|
+
import { Button, Card, Empty, Field, Group, Nav, NavItem, Page, Pill, Row, Shell, Sheet, Skeleton, Stepper, Tabs } from "@ambasada/ui/react";
|
|
87
97
|
|
|
88
98
|
<Page>
|
|
89
99
|
<Card title="Your name as a folk poem">
|
|
@@ -96,6 +106,11 @@ import { Button, Card, Empty, Group, Page, Pill, Row, Sheet, Skeleton, Stepper }
|
|
|
96
106
|
<Stepper value={amount} onChange={setAmount} min={1} unit="TON" presets={[1, 2, 5]} />
|
|
97
107
|
</Sheet>
|
|
98
108
|
</Page>
|
|
109
|
+
|
|
110
|
+
<Shell sidebar={<Nav title="Apps"><NavItem label="Acme" href="/p/acme" current /></Nav>} top="Acme" open={menu} onMenu={setMenu}>
|
|
111
|
+
<Tabs tabs={[{ id: "overview", label: "Overview", href: "/p/acme" }]} current="overview" />
|
|
112
|
+
<Field label="Slug" value={slug} onChange={(e) => setSlug(e.target.value)} hint="acme.ambasada.dev" />
|
|
113
|
+
</Shell>
|
|
99
114
|
```
|
|
100
115
|
|
|
101
116
|
The wrappers render markup only and import no CSS (load the stylesheet yourself). The module is marked
|
package/dist/ambasada-ui.css
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* @ambasada/ui 0.7.
|
|
1
|
+
/* @ambasada/ui 0.7.4: fonts, tokens and components in one stylesheet. MIT, CENTYSO. https://ambasada.id */
|
|
2
2
|
/*
|
|
3
3
|
* Inter and JetBrains Mono, self-hosted (variable, SIL Open Font License 1.1: fonts/OFL-*.txt). Latin and
|
|
4
4
|
* Latin Extended, so Romanian ț and ș render. The URLs are relative to this file: bundlers copy the files,
|
|
@@ -163,7 +163,8 @@
|
|
|
163
163
|
|
|
164
164
|
/*
|
|
165
165
|
* @ambasada/ui components, in plain CSS: page, card, buttons, list rows, pill, bottom sheet, stepper,
|
|
166
|
-
* empty state, skeleton, the mono label
|
|
166
|
+
* empty state, skeleton, the mono label; for desktop tools the app shell, sidebar nav, tabs, fields and tables.
|
|
167
|
+
* Tokens only (tokens.css): no raw colours.
|
|
167
168
|
* Markup for each one: packages/ui/README.md, or the React wrappers in @ambasada/ui/react.
|
|
168
169
|
*/
|
|
169
170
|
|
|
@@ -605,6 +606,289 @@ body.ui-sheet-open { overflow: hidden; }
|
|
|
605
606
|
}
|
|
606
607
|
@keyframes ui-shimmer { to { background-position: -200% 0; } }
|
|
607
608
|
|
|
609
|
+
/* ── app shell (desktop tools) ──────────────────────────────────────────────
|
|
610
|
+
* For apps built for the laptop that still open in the Ambasada app: a sidebar, a top bar, the content. Under
|
|
611
|
+
* 900 px the sidebar becomes a drawer behind the top bar's menu button. Clears the host bar and the safe areas
|
|
612
|
+
* once: the top bar takes the top, the content the bottom (docs/MINI-APPS.md §15 decision 11). */
|
|
613
|
+
|
|
614
|
+
.ui-shell {
|
|
615
|
+
min-height: 100vh;
|
|
616
|
+
min-height: 100dvh;
|
|
617
|
+
display: grid;
|
|
618
|
+
grid-template-columns: 240px minmax(0, 1fr);
|
|
619
|
+
}
|
|
620
|
+
.ui-shell-side {
|
|
621
|
+
position: sticky;
|
|
622
|
+
top: 0;
|
|
623
|
+
height: 100vh;
|
|
624
|
+
height: 100dvh;
|
|
625
|
+
display: flex;
|
|
626
|
+
flex-direction: column;
|
|
627
|
+
gap: var(--space-4);
|
|
628
|
+
padding:
|
|
629
|
+
calc(var(--space-3) + var(--ambasada-safe-area-top, 0px) + var(--ambasada-host-bar, 0px))
|
|
630
|
+
var(--space-3)
|
|
631
|
+
calc(var(--space-3) + var(--ambasada-safe-area-bottom, 0px))
|
|
632
|
+
calc(var(--space-3) + var(--ambasada-safe-area-left, 0px));
|
|
633
|
+
background: var(--color-surface);
|
|
634
|
+
border-right: 1px solid var(--color-line);
|
|
635
|
+
overflow-y: auto;
|
|
636
|
+
}
|
|
637
|
+
.ui-shell-main {
|
|
638
|
+
min-width: 0;
|
|
639
|
+
display: flex;
|
|
640
|
+
flex-direction: column;
|
|
641
|
+
}
|
|
642
|
+
.ui-shell-top {
|
|
643
|
+
position: sticky;
|
|
644
|
+
top: 0;
|
|
645
|
+
z-index: 20;
|
|
646
|
+
display: flex;
|
|
647
|
+
align-items: center;
|
|
648
|
+
gap: var(--space-3);
|
|
649
|
+
min-height: calc(56px + var(--ambasada-safe-area-top, 0px) + var(--ambasada-host-bar, 0px));
|
|
650
|
+
padding:
|
|
651
|
+
calc(var(--ambasada-safe-area-top, 0px) + var(--ambasada-host-bar, 0px))
|
|
652
|
+
calc(var(--space-6) + var(--ambasada-safe-area-right, 0px))
|
|
653
|
+
0
|
|
654
|
+
var(--space-6);
|
|
655
|
+
background: var(--color-bg);
|
|
656
|
+
border-bottom: 1px solid var(--color-line);
|
|
657
|
+
}
|
|
658
|
+
.ui-shell-menu {
|
|
659
|
+
display: none;
|
|
660
|
+
}
|
|
661
|
+
.ui-shell-content {
|
|
662
|
+
flex: 1;
|
|
663
|
+
width: 100%;
|
|
664
|
+
max-width: 1088px;
|
|
665
|
+
padding:
|
|
666
|
+
var(--space-6)
|
|
667
|
+
calc(var(--space-6) + var(--ambasada-safe-area-right, 0px))
|
|
668
|
+
calc(var(--space-10) + var(--ambasada-safe-area-bottom, 0px))
|
|
669
|
+
var(--space-6);
|
|
670
|
+
display: flex;
|
|
671
|
+
flex-direction: column;
|
|
672
|
+
gap: var(--space-6);
|
|
673
|
+
}
|
|
674
|
+
.ui-shell-scrim {
|
|
675
|
+
display: none;
|
|
676
|
+
}
|
|
677
|
+
@media (max-width: 899px) {
|
|
678
|
+
.ui-shell {
|
|
679
|
+
grid-template-columns: minmax(0, 1fr);
|
|
680
|
+
}
|
|
681
|
+
.ui-shell-side {
|
|
682
|
+
position: fixed;
|
|
683
|
+
inset: 0 auto 0 0;
|
|
684
|
+
z-index: 40;
|
|
685
|
+
width: min(300px, 86vw);
|
|
686
|
+
transform: translateX(-100%);
|
|
687
|
+
transition: transform var(--duration) var(--ease);
|
|
688
|
+
box-shadow: var(--shadow-raised);
|
|
689
|
+
}
|
|
690
|
+
.ui-shell.open .ui-shell-side {
|
|
691
|
+
transform: none;
|
|
692
|
+
}
|
|
693
|
+
.ui-shell.open .ui-shell-scrim {
|
|
694
|
+
display: block;
|
|
695
|
+
position: fixed;
|
|
696
|
+
inset: 0;
|
|
697
|
+
z-index: 30;
|
|
698
|
+
background: var(--color-backdrop);
|
|
699
|
+
}
|
|
700
|
+
.ui-shell-menu {
|
|
701
|
+
display: inline-grid;
|
|
702
|
+
margin-left: calc(-1 * var(--space-2));
|
|
703
|
+
}
|
|
704
|
+
.ui-shell-top {
|
|
705
|
+
padding-left: calc(var(--space-4) + var(--ambasada-safe-area-left, 0px));
|
|
706
|
+
padding-right: calc(var(--space-4) + var(--ambasada-safe-area-right, 0px));
|
|
707
|
+
}
|
|
708
|
+
.ui-shell-content {
|
|
709
|
+
padding-left: calc(var(--space-4) + var(--ambasada-safe-area-left, 0px));
|
|
710
|
+
padding-right: calc(var(--space-4) + var(--ambasada-safe-area-right, 0px));
|
|
711
|
+
padding-top: var(--space-4);
|
|
712
|
+
gap: var(--space-5);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
/* Sidebar navigation: a title, then items (icon, label, a value on the right). The current one is marked. */
|
|
717
|
+
.ui-nav {
|
|
718
|
+
display: flex;
|
|
719
|
+
flex-direction: column;
|
|
720
|
+
gap: 2px;
|
|
721
|
+
}
|
|
722
|
+
.ui-nav-title {
|
|
723
|
+
margin: 0;
|
|
724
|
+
padding: var(--space-2) var(--space-2) var(--space-1);
|
|
725
|
+
font-size: var(--text-xs);
|
|
726
|
+
font-weight: var(--weight-medium);
|
|
727
|
+
color: var(--color-ink-3);
|
|
728
|
+
}
|
|
729
|
+
.ui-nav-item {
|
|
730
|
+
position: relative;
|
|
731
|
+
display: flex;
|
|
732
|
+
align-items: center;
|
|
733
|
+
gap: var(--space-2);
|
|
734
|
+
min-height: 36px;
|
|
735
|
+
padding: 6px var(--space-2);
|
|
736
|
+
border: 0;
|
|
737
|
+
border-radius: var(--radius-sm);
|
|
738
|
+
background: none;
|
|
739
|
+
color: var(--color-ink-2);
|
|
740
|
+
font: inherit;
|
|
741
|
+
font-size: var(--text-sm);
|
|
742
|
+
text-align: left;
|
|
743
|
+
text-decoration: none;
|
|
744
|
+
cursor: pointer;
|
|
745
|
+
}
|
|
746
|
+
.ui-nav-item:hover {
|
|
747
|
+
background: var(--color-surface-2);
|
|
748
|
+
color: var(--color-ink);
|
|
749
|
+
}
|
|
750
|
+
.ui-nav-item.on {
|
|
751
|
+
background: var(--color-surface-2);
|
|
752
|
+
color: var(--color-ink);
|
|
753
|
+
font-weight: var(--weight-medium);
|
|
754
|
+
}
|
|
755
|
+
.ui-nav-item.on::before {
|
|
756
|
+
content: "";
|
|
757
|
+
position: absolute;
|
|
758
|
+
left: calc(-1 * var(--space-3));
|
|
759
|
+
top: 8px;
|
|
760
|
+
bottom: 8px;
|
|
761
|
+
width: 3px;
|
|
762
|
+
border-radius: 0 2px 2px 0;
|
|
763
|
+
background: var(--color-accent);
|
|
764
|
+
}
|
|
765
|
+
.ui-nav-item:focus-visible,
|
|
766
|
+
.ui-tabs a:focus-visible {
|
|
767
|
+
outline: 2px solid var(--color-accent);
|
|
768
|
+
outline-offset: -2px;
|
|
769
|
+
}
|
|
770
|
+
.ui-nav-label {
|
|
771
|
+
flex: 1;
|
|
772
|
+
min-width: 0;
|
|
773
|
+
overflow: hidden;
|
|
774
|
+
text-overflow: ellipsis;
|
|
775
|
+
white-space: nowrap;
|
|
776
|
+
}
|
|
777
|
+
.ui-nav-right {
|
|
778
|
+
flex: none;
|
|
779
|
+
color: var(--color-ink-3);
|
|
780
|
+
font-size: var(--text-xs);
|
|
781
|
+
font-variant-numeric: tabular-nums;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
/* Tabs: a row of links under a page title; scrolls sideways when it doesn't fit. */
|
|
785
|
+
.ui-tabs {
|
|
786
|
+
display: flex;
|
|
787
|
+
gap: var(--space-1);
|
|
788
|
+
margin: 0;
|
|
789
|
+
border-bottom: 1px solid var(--color-line);
|
|
790
|
+
overflow-x: auto;
|
|
791
|
+
scrollbar-width: none;
|
|
792
|
+
}
|
|
793
|
+
.ui-tabs::-webkit-scrollbar {
|
|
794
|
+
display: none;
|
|
795
|
+
}
|
|
796
|
+
.ui-tabs a {
|
|
797
|
+
flex: none;
|
|
798
|
+
padding: 10px var(--space-3);
|
|
799
|
+
margin-bottom: -1px;
|
|
800
|
+
border-bottom: 2px solid transparent;
|
|
801
|
+
color: var(--color-ink-3);
|
|
802
|
+
font-size: var(--text-sm);
|
|
803
|
+
text-decoration: none;
|
|
804
|
+
}
|
|
805
|
+
.ui-tabs a:hover {
|
|
806
|
+
color: var(--color-ink);
|
|
807
|
+
}
|
|
808
|
+
.ui-tabs a.on {
|
|
809
|
+
color: var(--color-ink);
|
|
810
|
+
font-weight: var(--weight-medium);
|
|
811
|
+
border-bottom-color: var(--color-ink);
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
/* Form field: label, the input, one line of hint or error under it. */
|
|
815
|
+
.ui-field {
|
|
816
|
+
display: flex;
|
|
817
|
+
flex-direction: column;
|
|
818
|
+
gap: 6px;
|
|
819
|
+
}
|
|
820
|
+
.ui-field-label {
|
|
821
|
+
font-size: var(--text-sm);
|
|
822
|
+
font-weight: var(--weight-medium);
|
|
823
|
+
}
|
|
824
|
+
.ui-field-hint {
|
|
825
|
+
font-size: var(--text-xs);
|
|
826
|
+
color: var(--color-ink-3);
|
|
827
|
+
}
|
|
828
|
+
.ui-field-hint.bad {
|
|
829
|
+
color: var(--color-danger);
|
|
830
|
+
}
|
|
831
|
+
.ui-field-hint.good {
|
|
832
|
+
color: var(--color-success);
|
|
833
|
+
}
|
|
834
|
+
.ui-input {
|
|
835
|
+
width: 100%;
|
|
836
|
+
min-height: 40px;
|
|
837
|
+
padding: 8px 12px;
|
|
838
|
+
border: 1px solid var(--color-line);
|
|
839
|
+
border-radius: var(--radius-sm);
|
|
840
|
+
background: var(--color-surface);
|
|
841
|
+
color: var(--color-ink);
|
|
842
|
+
font: inherit;
|
|
843
|
+
font-size: var(--text-sm);
|
|
844
|
+
}
|
|
845
|
+
.ui-input:hover {
|
|
846
|
+
border-color: var(--color-line-strong);
|
|
847
|
+
}
|
|
848
|
+
.ui-input:focus {
|
|
849
|
+
outline: none;
|
|
850
|
+
border-color: var(--color-ink);
|
|
851
|
+
box-shadow: 0 0 0 3px var(--color-surface-2);
|
|
852
|
+
}
|
|
853
|
+
.ui-input:disabled {
|
|
854
|
+
background: var(--color-surface-2);
|
|
855
|
+
color: var(--color-ink-3);
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
/* Data table: hairline rows, numbers right-aligned in tabular figures (`.num`). */
|
|
859
|
+
.ui-table {
|
|
860
|
+
width: 100%;
|
|
861
|
+
border-collapse: collapse;
|
|
862
|
+
font-size: var(--text-sm);
|
|
863
|
+
font-variant-numeric: tabular-nums;
|
|
864
|
+
}
|
|
865
|
+
.ui-table th {
|
|
866
|
+
padding: 10px var(--space-4);
|
|
867
|
+
text-align: left;
|
|
868
|
+
font-weight: var(--weight-medium);
|
|
869
|
+
color: var(--color-ink-3);
|
|
870
|
+
background: var(--color-surface-2);
|
|
871
|
+
border-bottom: 1px solid var(--color-line);
|
|
872
|
+
white-space: nowrap;
|
|
873
|
+
}
|
|
874
|
+
.ui-table td {
|
|
875
|
+
padding: 12px var(--space-4);
|
|
876
|
+
border-bottom: 1px solid var(--color-line);
|
|
877
|
+
vertical-align: middle;
|
|
878
|
+
}
|
|
879
|
+
.ui-table tr:last-child td {
|
|
880
|
+
border-bottom: 0;
|
|
881
|
+
}
|
|
882
|
+
.ui-table .num {
|
|
883
|
+
text-align: right;
|
|
884
|
+
}
|
|
885
|
+
.ui-table tbody tr.tappable {
|
|
886
|
+
cursor: pointer;
|
|
887
|
+
}
|
|
888
|
+
.ui-table tbody tr.tappable:hover td {
|
|
889
|
+
background: var(--color-surface-2);
|
|
890
|
+
}
|
|
891
|
+
|
|
608
892
|
@media (prefers-reduced-motion: reduce) {
|
|
609
893
|
.ui-sheet,
|
|
610
894
|
.ui-sheet-backdrop,
|
|
@@ -612,4 +896,7 @@ body.ui-sheet-open { overflow: hidden; }
|
|
|
612
896
|
.ui-skeleton span {
|
|
613
897
|
animation: none;
|
|
614
898
|
}
|
|
899
|
+
.ui-shell-side {
|
|
900
|
+
transition: none;
|
|
901
|
+
}
|
|
615
902
|
}
|
package/dist/components.css
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* @ambasada/ui components, in plain CSS: page, card, buttons, list rows, pill, bottom sheet, stepper,
|
|
3
|
-
* empty state, skeleton, the mono label
|
|
3
|
+
* empty state, skeleton, the mono label; for desktop tools the app shell, sidebar nav, tabs, fields and tables.
|
|
4
|
+
* Tokens only (tokens.css): no raw colours.
|
|
4
5
|
* Markup for each one: packages/ui/README.md, or the React wrappers in @ambasada/ui/react.
|
|
5
6
|
*/
|
|
6
7
|
|
|
@@ -442,6 +443,289 @@ body.ui-sheet-open { overflow: hidden; }
|
|
|
442
443
|
}
|
|
443
444
|
@keyframes ui-shimmer { to { background-position: -200% 0; } }
|
|
444
445
|
|
|
446
|
+
/* ── app shell (desktop tools) ──────────────────────────────────────────────
|
|
447
|
+
* For apps built for the laptop that still open in the Ambasada app: a sidebar, a top bar, the content. Under
|
|
448
|
+
* 900 px the sidebar becomes a drawer behind the top bar's menu button. Clears the host bar and the safe areas
|
|
449
|
+
* once: the top bar takes the top, the content the bottom (docs/MINI-APPS.md §15 decision 11). */
|
|
450
|
+
|
|
451
|
+
.ui-shell {
|
|
452
|
+
min-height: 100vh;
|
|
453
|
+
min-height: 100dvh;
|
|
454
|
+
display: grid;
|
|
455
|
+
grid-template-columns: 240px minmax(0, 1fr);
|
|
456
|
+
}
|
|
457
|
+
.ui-shell-side {
|
|
458
|
+
position: sticky;
|
|
459
|
+
top: 0;
|
|
460
|
+
height: 100vh;
|
|
461
|
+
height: 100dvh;
|
|
462
|
+
display: flex;
|
|
463
|
+
flex-direction: column;
|
|
464
|
+
gap: var(--space-4);
|
|
465
|
+
padding:
|
|
466
|
+
calc(var(--space-3) + var(--ambasada-safe-area-top, 0px) + var(--ambasada-host-bar, 0px))
|
|
467
|
+
var(--space-3)
|
|
468
|
+
calc(var(--space-3) + var(--ambasada-safe-area-bottom, 0px))
|
|
469
|
+
calc(var(--space-3) + var(--ambasada-safe-area-left, 0px));
|
|
470
|
+
background: var(--color-surface);
|
|
471
|
+
border-right: 1px solid var(--color-line);
|
|
472
|
+
overflow-y: auto;
|
|
473
|
+
}
|
|
474
|
+
.ui-shell-main {
|
|
475
|
+
min-width: 0;
|
|
476
|
+
display: flex;
|
|
477
|
+
flex-direction: column;
|
|
478
|
+
}
|
|
479
|
+
.ui-shell-top {
|
|
480
|
+
position: sticky;
|
|
481
|
+
top: 0;
|
|
482
|
+
z-index: 20;
|
|
483
|
+
display: flex;
|
|
484
|
+
align-items: center;
|
|
485
|
+
gap: var(--space-3);
|
|
486
|
+
min-height: calc(56px + var(--ambasada-safe-area-top, 0px) + var(--ambasada-host-bar, 0px));
|
|
487
|
+
padding:
|
|
488
|
+
calc(var(--ambasada-safe-area-top, 0px) + var(--ambasada-host-bar, 0px))
|
|
489
|
+
calc(var(--space-6) + var(--ambasada-safe-area-right, 0px))
|
|
490
|
+
0
|
|
491
|
+
var(--space-6);
|
|
492
|
+
background: var(--color-bg);
|
|
493
|
+
border-bottom: 1px solid var(--color-line);
|
|
494
|
+
}
|
|
495
|
+
.ui-shell-menu {
|
|
496
|
+
display: none;
|
|
497
|
+
}
|
|
498
|
+
.ui-shell-content {
|
|
499
|
+
flex: 1;
|
|
500
|
+
width: 100%;
|
|
501
|
+
max-width: 1088px;
|
|
502
|
+
padding:
|
|
503
|
+
var(--space-6)
|
|
504
|
+
calc(var(--space-6) + var(--ambasada-safe-area-right, 0px))
|
|
505
|
+
calc(var(--space-10) + var(--ambasada-safe-area-bottom, 0px))
|
|
506
|
+
var(--space-6);
|
|
507
|
+
display: flex;
|
|
508
|
+
flex-direction: column;
|
|
509
|
+
gap: var(--space-6);
|
|
510
|
+
}
|
|
511
|
+
.ui-shell-scrim {
|
|
512
|
+
display: none;
|
|
513
|
+
}
|
|
514
|
+
@media (max-width: 899px) {
|
|
515
|
+
.ui-shell {
|
|
516
|
+
grid-template-columns: minmax(0, 1fr);
|
|
517
|
+
}
|
|
518
|
+
.ui-shell-side {
|
|
519
|
+
position: fixed;
|
|
520
|
+
inset: 0 auto 0 0;
|
|
521
|
+
z-index: 40;
|
|
522
|
+
width: min(300px, 86vw);
|
|
523
|
+
transform: translateX(-100%);
|
|
524
|
+
transition: transform var(--duration) var(--ease);
|
|
525
|
+
box-shadow: var(--shadow-raised);
|
|
526
|
+
}
|
|
527
|
+
.ui-shell.open .ui-shell-side {
|
|
528
|
+
transform: none;
|
|
529
|
+
}
|
|
530
|
+
.ui-shell.open .ui-shell-scrim {
|
|
531
|
+
display: block;
|
|
532
|
+
position: fixed;
|
|
533
|
+
inset: 0;
|
|
534
|
+
z-index: 30;
|
|
535
|
+
background: var(--color-backdrop);
|
|
536
|
+
}
|
|
537
|
+
.ui-shell-menu {
|
|
538
|
+
display: inline-grid;
|
|
539
|
+
margin-left: calc(-1 * var(--space-2));
|
|
540
|
+
}
|
|
541
|
+
.ui-shell-top {
|
|
542
|
+
padding-left: calc(var(--space-4) + var(--ambasada-safe-area-left, 0px));
|
|
543
|
+
padding-right: calc(var(--space-4) + var(--ambasada-safe-area-right, 0px));
|
|
544
|
+
}
|
|
545
|
+
.ui-shell-content {
|
|
546
|
+
padding-left: calc(var(--space-4) + var(--ambasada-safe-area-left, 0px));
|
|
547
|
+
padding-right: calc(var(--space-4) + var(--ambasada-safe-area-right, 0px));
|
|
548
|
+
padding-top: var(--space-4);
|
|
549
|
+
gap: var(--space-5);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/* Sidebar navigation: a title, then items (icon, label, a value on the right). The current one is marked. */
|
|
554
|
+
.ui-nav {
|
|
555
|
+
display: flex;
|
|
556
|
+
flex-direction: column;
|
|
557
|
+
gap: 2px;
|
|
558
|
+
}
|
|
559
|
+
.ui-nav-title {
|
|
560
|
+
margin: 0;
|
|
561
|
+
padding: var(--space-2) var(--space-2) var(--space-1);
|
|
562
|
+
font-size: var(--text-xs);
|
|
563
|
+
font-weight: var(--weight-medium);
|
|
564
|
+
color: var(--color-ink-3);
|
|
565
|
+
}
|
|
566
|
+
.ui-nav-item {
|
|
567
|
+
position: relative;
|
|
568
|
+
display: flex;
|
|
569
|
+
align-items: center;
|
|
570
|
+
gap: var(--space-2);
|
|
571
|
+
min-height: 36px;
|
|
572
|
+
padding: 6px var(--space-2);
|
|
573
|
+
border: 0;
|
|
574
|
+
border-radius: var(--radius-sm);
|
|
575
|
+
background: none;
|
|
576
|
+
color: var(--color-ink-2);
|
|
577
|
+
font: inherit;
|
|
578
|
+
font-size: var(--text-sm);
|
|
579
|
+
text-align: left;
|
|
580
|
+
text-decoration: none;
|
|
581
|
+
cursor: pointer;
|
|
582
|
+
}
|
|
583
|
+
.ui-nav-item:hover {
|
|
584
|
+
background: var(--color-surface-2);
|
|
585
|
+
color: var(--color-ink);
|
|
586
|
+
}
|
|
587
|
+
.ui-nav-item.on {
|
|
588
|
+
background: var(--color-surface-2);
|
|
589
|
+
color: var(--color-ink);
|
|
590
|
+
font-weight: var(--weight-medium);
|
|
591
|
+
}
|
|
592
|
+
.ui-nav-item.on::before {
|
|
593
|
+
content: "";
|
|
594
|
+
position: absolute;
|
|
595
|
+
left: calc(-1 * var(--space-3));
|
|
596
|
+
top: 8px;
|
|
597
|
+
bottom: 8px;
|
|
598
|
+
width: 3px;
|
|
599
|
+
border-radius: 0 2px 2px 0;
|
|
600
|
+
background: var(--color-accent);
|
|
601
|
+
}
|
|
602
|
+
.ui-nav-item:focus-visible,
|
|
603
|
+
.ui-tabs a:focus-visible {
|
|
604
|
+
outline: 2px solid var(--color-accent);
|
|
605
|
+
outline-offset: -2px;
|
|
606
|
+
}
|
|
607
|
+
.ui-nav-label {
|
|
608
|
+
flex: 1;
|
|
609
|
+
min-width: 0;
|
|
610
|
+
overflow: hidden;
|
|
611
|
+
text-overflow: ellipsis;
|
|
612
|
+
white-space: nowrap;
|
|
613
|
+
}
|
|
614
|
+
.ui-nav-right {
|
|
615
|
+
flex: none;
|
|
616
|
+
color: var(--color-ink-3);
|
|
617
|
+
font-size: var(--text-xs);
|
|
618
|
+
font-variant-numeric: tabular-nums;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/* Tabs: a row of links under a page title; scrolls sideways when it doesn't fit. */
|
|
622
|
+
.ui-tabs {
|
|
623
|
+
display: flex;
|
|
624
|
+
gap: var(--space-1);
|
|
625
|
+
margin: 0;
|
|
626
|
+
border-bottom: 1px solid var(--color-line);
|
|
627
|
+
overflow-x: auto;
|
|
628
|
+
scrollbar-width: none;
|
|
629
|
+
}
|
|
630
|
+
.ui-tabs::-webkit-scrollbar {
|
|
631
|
+
display: none;
|
|
632
|
+
}
|
|
633
|
+
.ui-tabs a {
|
|
634
|
+
flex: none;
|
|
635
|
+
padding: 10px var(--space-3);
|
|
636
|
+
margin-bottom: -1px;
|
|
637
|
+
border-bottom: 2px solid transparent;
|
|
638
|
+
color: var(--color-ink-3);
|
|
639
|
+
font-size: var(--text-sm);
|
|
640
|
+
text-decoration: none;
|
|
641
|
+
}
|
|
642
|
+
.ui-tabs a:hover {
|
|
643
|
+
color: var(--color-ink);
|
|
644
|
+
}
|
|
645
|
+
.ui-tabs a.on {
|
|
646
|
+
color: var(--color-ink);
|
|
647
|
+
font-weight: var(--weight-medium);
|
|
648
|
+
border-bottom-color: var(--color-ink);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/* Form field: label, the input, one line of hint or error under it. */
|
|
652
|
+
.ui-field {
|
|
653
|
+
display: flex;
|
|
654
|
+
flex-direction: column;
|
|
655
|
+
gap: 6px;
|
|
656
|
+
}
|
|
657
|
+
.ui-field-label {
|
|
658
|
+
font-size: var(--text-sm);
|
|
659
|
+
font-weight: var(--weight-medium);
|
|
660
|
+
}
|
|
661
|
+
.ui-field-hint {
|
|
662
|
+
font-size: var(--text-xs);
|
|
663
|
+
color: var(--color-ink-3);
|
|
664
|
+
}
|
|
665
|
+
.ui-field-hint.bad {
|
|
666
|
+
color: var(--color-danger);
|
|
667
|
+
}
|
|
668
|
+
.ui-field-hint.good {
|
|
669
|
+
color: var(--color-success);
|
|
670
|
+
}
|
|
671
|
+
.ui-input {
|
|
672
|
+
width: 100%;
|
|
673
|
+
min-height: 40px;
|
|
674
|
+
padding: 8px 12px;
|
|
675
|
+
border: 1px solid var(--color-line);
|
|
676
|
+
border-radius: var(--radius-sm);
|
|
677
|
+
background: var(--color-surface);
|
|
678
|
+
color: var(--color-ink);
|
|
679
|
+
font: inherit;
|
|
680
|
+
font-size: var(--text-sm);
|
|
681
|
+
}
|
|
682
|
+
.ui-input:hover {
|
|
683
|
+
border-color: var(--color-line-strong);
|
|
684
|
+
}
|
|
685
|
+
.ui-input:focus {
|
|
686
|
+
outline: none;
|
|
687
|
+
border-color: var(--color-ink);
|
|
688
|
+
box-shadow: 0 0 0 3px var(--color-surface-2);
|
|
689
|
+
}
|
|
690
|
+
.ui-input:disabled {
|
|
691
|
+
background: var(--color-surface-2);
|
|
692
|
+
color: var(--color-ink-3);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/* Data table: hairline rows, numbers right-aligned in tabular figures (`.num`). */
|
|
696
|
+
.ui-table {
|
|
697
|
+
width: 100%;
|
|
698
|
+
border-collapse: collapse;
|
|
699
|
+
font-size: var(--text-sm);
|
|
700
|
+
font-variant-numeric: tabular-nums;
|
|
701
|
+
}
|
|
702
|
+
.ui-table th {
|
|
703
|
+
padding: 10px var(--space-4);
|
|
704
|
+
text-align: left;
|
|
705
|
+
font-weight: var(--weight-medium);
|
|
706
|
+
color: var(--color-ink-3);
|
|
707
|
+
background: var(--color-surface-2);
|
|
708
|
+
border-bottom: 1px solid var(--color-line);
|
|
709
|
+
white-space: nowrap;
|
|
710
|
+
}
|
|
711
|
+
.ui-table td {
|
|
712
|
+
padding: 12px var(--space-4);
|
|
713
|
+
border-bottom: 1px solid var(--color-line);
|
|
714
|
+
vertical-align: middle;
|
|
715
|
+
}
|
|
716
|
+
.ui-table tr:last-child td {
|
|
717
|
+
border-bottom: 0;
|
|
718
|
+
}
|
|
719
|
+
.ui-table .num {
|
|
720
|
+
text-align: right;
|
|
721
|
+
}
|
|
722
|
+
.ui-table tbody tr.tappable {
|
|
723
|
+
cursor: pointer;
|
|
724
|
+
}
|
|
725
|
+
.ui-table tbody tr.tappable:hover td {
|
|
726
|
+
background: var(--color-surface-2);
|
|
727
|
+
}
|
|
728
|
+
|
|
445
729
|
@media (prefers-reduced-motion: reduce) {
|
|
446
730
|
.ui-sheet,
|
|
447
731
|
.ui-sheet-backdrop,
|
|
@@ -449,4 +733,7 @@ body.ui-sheet-open { overflow: hidden; }
|
|
|
449
733
|
.ui-skeleton span {
|
|
450
734
|
animation: none;
|
|
451
735
|
}
|
|
736
|
+
.ui-shell-side {
|
|
737
|
+
transition: none;
|
|
738
|
+
}
|
|
452
739
|
}
|
package/dist/react.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ButtonHTMLAttributes, type ReactNode } from "react";
|
|
1
|
+
import { type ButtonHTMLAttributes, type InputHTMLAttributes, type MouseEventHandler, type ReactNode } from "react";
|
|
2
2
|
/** The mini-app column: clear of the host bar and the safe areas. */
|
|
3
3
|
export declare function Page({ children, className }: {
|
|
4
4
|
children: ReactNode;
|
|
@@ -68,3 +68,48 @@ export declare function Empty({ icon, title, hint, action }: {
|
|
|
68
68
|
export declare function Skeleton({ lines }: {
|
|
69
69
|
lines?: number;
|
|
70
70
|
}): import("react").JSX.Element;
|
|
71
|
+
/**
|
|
72
|
+
* Sidebar, top bar, content: for apps built for the laptop that still open in the Ambasada app. Under 900 px the
|
|
73
|
+
* sidebar is a drawer: `open` shows it, the top bar's menu button and the scrim call `onMenu(next)`.
|
|
74
|
+
*/
|
|
75
|
+
export declare function Shell({ sidebar, top, children, open, onMenu }: {
|
|
76
|
+
sidebar: ReactNode;
|
|
77
|
+
top?: ReactNode;
|
|
78
|
+
children: ReactNode;
|
|
79
|
+
open?: boolean;
|
|
80
|
+
onMenu?: (open: boolean) => void;
|
|
81
|
+
}): import("react").JSX.Element;
|
|
82
|
+
/** A sidebar section: an optional title over its items. */
|
|
83
|
+
export declare function Nav({ title, children, label }: {
|
|
84
|
+
title?: ReactNode;
|
|
85
|
+
children: ReactNode;
|
|
86
|
+
label?: string;
|
|
87
|
+
}): import("react").JSX.Element;
|
|
88
|
+
export type NavItemProps = {
|
|
89
|
+
icon?: ReactNode;
|
|
90
|
+
label: ReactNode;
|
|
91
|
+
right?: ReactNode;
|
|
92
|
+
href?: string;
|
|
93
|
+
onClick?: MouseEventHandler<HTMLElement>;
|
|
94
|
+
current?: boolean;
|
|
95
|
+
};
|
|
96
|
+
/** A sidebar item: a link with `href` (current = aria-current page), else a button. */
|
|
97
|
+
export declare function NavItem({ icon, label, right, href, onClick, current }: NavItemProps): import("react").JSX.Element;
|
|
98
|
+
/** Tabs as links (each tab its own URL); `onClick` lets a client router take plain clicks. */
|
|
99
|
+
export declare function Tabs({ tabs, current, onClick, label }: {
|
|
100
|
+
tabs: {
|
|
101
|
+
id: string;
|
|
102
|
+
label: ReactNode;
|
|
103
|
+
href: string;
|
|
104
|
+
}[];
|
|
105
|
+
current: string;
|
|
106
|
+
onClick?: MouseEventHandler<HTMLAnchorElement>;
|
|
107
|
+
label?: string;
|
|
108
|
+
}): import("react").JSX.Element;
|
|
109
|
+
/** Label, input, one line of hint (or `error`, which wins) under it. */
|
|
110
|
+
export declare function Field({ label, hint, error, tone, className, ...input }: InputHTMLAttributes<HTMLInputElement> & {
|
|
111
|
+
label: ReactNode;
|
|
112
|
+
hint?: ReactNode;
|
|
113
|
+
error?: ReactNode;
|
|
114
|
+
tone?: "good" | "bad";
|
|
115
|
+
}): import("react").JSX.Element;
|
package/dist/react.js
CHANGED
|
@@ -100,15 +100,67 @@ function Empty({ icon, title, hint, action }) {
|
|
|
100
100
|
function Skeleton({ lines = 3 }) {
|
|
101
101
|
return /* @__PURE__ */ jsx("div", { className: "ui-skeleton", "aria-busy": "true", children: Array.from({ length: lines }).map((_, i) => /* @__PURE__ */ jsx("span", { style: { width: `${90 - i % 3 * 18}%` } }, i)) });
|
|
102
102
|
}
|
|
103
|
+
function Shell({ sidebar, top, children, open = false, onMenu }) {
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
if (!open) return;
|
|
106
|
+
const onKey = (e) => e.key === "Escape" && onMenu?.(false);
|
|
107
|
+
document.addEventListener("keydown", onKey);
|
|
108
|
+
return () => document.removeEventListener("keydown", onKey);
|
|
109
|
+
}, [open, onMenu]);
|
|
110
|
+
return /* @__PURE__ */ jsxs("div", { className: cx("ui-shell", open && "open"), children: [
|
|
111
|
+
/* @__PURE__ */ jsx("aside", { className: "ui-shell-side", children: sidebar }),
|
|
112
|
+
/* @__PURE__ */ jsx("div", { className: "ui-shell-scrim", onClick: () => onMenu?.(false) }),
|
|
113
|
+
/* @__PURE__ */ jsxs("div", { className: "ui-shell-main", children: [
|
|
114
|
+
/* @__PURE__ */ jsxs("header", { className: "ui-shell-top", children: [
|
|
115
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "ui-icon-btn ui-shell-menu", "aria-label": "Menu", "aria-expanded": open, onClick: () => onMenu?.(!open), children: /* @__PURE__ */ jsx(Icon, { d: "M4 7h16M4 12h16M4 17h16" }) }),
|
|
116
|
+
top
|
|
117
|
+
] }),
|
|
118
|
+
/* @__PURE__ */ jsx("main", { className: "ui-shell-content", children })
|
|
119
|
+
] })
|
|
120
|
+
] });
|
|
121
|
+
}
|
|
122
|
+
function Nav({ title, children, label }) {
|
|
123
|
+
return /* @__PURE__ */ jsxs("nav", { className: "ui-nav", "aria-label": label, children: [
|
|
124
|
+
title && /* @__PURE__ */ jsx("h2", { className: "ui-nav-title", children: title }),
|
|
125
|
+
children
|
|
126
|
+
] });
|
|
127
|
+
}
|
|
128
|
+
function NavItem({ icon, label, right, href, onClick, current }) {
|
|
129
|
+
const body = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
130
|
+
icon,
|
|
131
|
+
/* @__PURE__ */ jsx("span", { className: "ui-nav-label", children: label }),
|
|
132
|
+
right !== void 0 && /* @__PURE__ */ jsx("span", { className: "ui-nav-right", children: right })
|
|
133
|
+
] });
|
|
134
|
+
const cls = cx("ui-nav-item", current && "on");
|
|
135
|
+
if (href) return /* @__PURE__ */ jsx("a", { className: cls, href, onClick, "aria-current": current ? "page" : void 0, children: body });
|
|
136
|
+
return /* @__PURE__ */ jsx("button", { type: "button", className: cls, onClick, children: body });
|
|
137
|
+
}
|
|
138
|
+
function Tabs({ tabs, current, onClick, label }) {
|
|
139
|
+
return /* @__PURE__ */ jsx("nav", { className: "ui-tabs", "aria-label": label, children: tabs.map((t) => /* @__PURE__ */ jsx("a", { href: t.href, onClick, className: t.id === current ? "on" : void 0, "aria-current": t.id === current ? "page" : void 0, children: t.label }, t.id)) });
|
|
140
|
+
}
|
|
141
|
+
function Field({ label, hint, error, tone, className, ...input }) {
|
|
142
|
+
const id = useId();
|
|
143
|
+
const note = error ?? hint;
|
|
144
|
+
return /* @__PURE__ */ jsxs("div", { className: "ui-field", children: [
|
|
145
|
+
/* @__PURE__ */ jsx("label", { className: "ui-field-label", htmlFor: input.id ?? id, children: label }),
|
|
146
|
+
/* @__PURE__ */ jsx("input", { id, ...input, className: cx("ui-input", className), "aria-invalid": error ? true : void 0, "aria-describedby": note ? `${input.id ?? id}-hint` : void 0 }),
|
|
147
|
+
note && /* @__PURE__ */ jsx("span", { id: `${input.id ?? id}-hint`, className: cx("ui-field-hint", error ? "bad" : tone), children: note })
|
|
148
|
+
] });
|
|
149
|
+
}
|
|
103
150
|
export {
|
|
104
151
|
Button,
|
|
105
152
|
Card,
|
|
106
153
|
Empty,
|
|
154
|
+
Field,
|
|
107
155
|
Group,
|
|
156
|
+
Nav,
|
|
157
|
+
NavItem,
|
|
108
158
|
Page,
|
|
109
159
|
Pill,
|
|
110
160
|
Row,
|
|
111
161
|
Sheet,
|
|
162
|
+
Shell,
|
|
112
163
|
Skeleton,
|
|
113
|
-
Stepper
|
|
164
|
+
Stepper,
|
|
165
|
+
Tabs
|
|
114
166
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ambasada/ui",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.4",
|
|
4
4
|
"description": "The Ambasada look for mini apps: design tokens (colours, Inter + JetBrains Mono, radii, spacing, safe areas) and a small component set in plain CSS, with optional React wrappers. One stylesheet to <link>.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|