@fishies/ui 0.8.3

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 (131) hide show
  1. package/LICENSE +26 -0
  2. package/README.md +60 -0
  3. package/dist/FishiesImg.d.ts +9 -0
  4. package/dist/FishiesImg.js +64 -0
  5. package/dist/Select.d.ts +22 -0
  6. package/dist/Select.js +95 -0
  7. package/dist/fx-image.d.ts +3 -0
  8. package/dist/fx-image.js +25 -0
  9. package/dist/icons.d.ts +28 -0
  10. package/dist/icons.generated.d.ts +5 -0
  11. package/dist/icons.generated.js +658 -0
  12. package/dist/icons.js +53 -0
  13. package/dist/index.d.ts +7 -0
  14. package/dist/index.js +7 -0
  15. package/dist/kit.css +267 -0
  16. package/dist/kit.d.ts +159 -0
  17. package/dist/kit.js +104 -0
  18. package/dist/loader.css +48 -0
  19. package/dist/loader.d.ts +17 -0
  20. package/dist/loader.js +27 -0
  21. package/dist/select-state.d.ts +34 -0
  22. package/dist/select-state.js +55 -0
  23. package/dist/select.css +87 -0
  24. package/dist/tokens.d.ts +17 -0
  25. package/dist/tokens.generated.css +97 -0
  26. package/dist/tokens.js +175 -0
  27. package/handbook/README.md +236 -0
  28. package/icons/add.png +0 -0
  29. package/icons/announce.png +0 -0
  30. package/icons/arrange.png +0 -0
  31. package/icons/bell.png +0 -0
  32. package/icons/block.png +0 -0
  33. package/icons/book.png +0 -0
  34. package/icons/bug.png +0 -0
  35. package/icons/camera.png +0 -0
  36. package/icons/cart-add.png +0 -0
  37. package/icons/cart.png +0 -0
  38. package/icons/chat.png +0 -0
  39. package/icons/check.png +0 -0
  40. package/icons/clean.png +0 -0
  41. package/icons/clock.png +0 -0
  42. package/icons/coin.png +0 -0
  43. package/icons/coral.png +0 -0
  44. package/icons/decorate.png +0 -0
  45. package/icons/download.png +0 -0
  46. package/icons/drop.png +0 -0
  47. package/icons/edit.png +0 -0
  48. package/icons/egg.png +0 -0
  49. package/icons/emote-food.png +0 -0
  50. package/icons/emote-heart.png +0 -0
  51. package/icons/emote-hello.png +0 -0
  52. package/icons/emote-music.png +0 -0
  53. package/icons/emote-question.png +0 -0
  54. package/icons/emote-sleep.png +0 -0
  55. package/icons/emote-sparkle.png +0 -0
  56. package/icons/emote-surprise.png +0 -0
  57. package/icons/exit.png +0 -0
  58. package/icons/explore.png +0 -0
  59. package/icons/external.png +0 -0
  60. package/icons/feed.png +0 -0
  61. package/icons/fish.png +0 -0
  62. package/icons/follow.png +0 -0
  63. package/icons/fullscreen.png +0 -0
  64. package/icons/gear.png +0 -0
  65. package/icons/gem.png +0 -0
  66. package/icons/gift.png +0 -0
  67. package/icons/globe.png +0 -0
  68. package/icons/growth.png +0 -0
  69. package/icons/heart.png +0 -0
  70. package/icons/help.png +0 -0
  71. package/icons/home.png +0 -0
  72. package/icons/idea.png +0 -0
  73. package/icons/inspect.png +0 -0
  74. package/icons/inventory.png +0 -0
  75. package/icons/keeper.png +0 -0
  76. package/icons/kelp.png +0 -0
  77. package/icons/keyboard.png +0 -0
  78. package/icons/level.png +0 -0
  79. package/icons/link.png +0 -0
  80. package/icons/lock.png +0 -0
  81. package/icons/mail.png +0 -0
  82. package/icons/map.png +0 -0
  83. package/icons/menu.png +0 -0
  84. package/icons/music.png +0 -0
  85. package/icons/mute.png +0 -0
  86. package/icons/net.png +0 -0
  87. package/icons/people.png +0 -0
  88. package/icons/pin.png +0 -0
  89. package/icons/quest.png +0 -0
  90. package/icons/quote.png +0 -0
  91. package/icons/remedy.png +0 -0
  92. package/icons/reply.png +0 -0
  93. package/icons/report.png +0 -0
  94. package/icons/search.png +0 -0
  95. package/icons/sell.png +0 -0
  96. package/icons/send.png +0 -0
  97. package/icons/share.png +0 -0
  98. package/icons/shell.png +0 -0
  99. package/icons/shop.png +0 -0
  100. package/icons/showcase.png +0 -0
  101. package/icons/smile.png +0 -0
  102. package/icons/solved.png +0 -0
  103. package/icons/sound.png +0 -0
  104. package/icons/staff.png +0 -0
  105. package/icons/star.png +0 -0
  106. package/icons/tank.png +0 -0
  107. package/icons/thermometer.png +0 -0
  108. package/icons/trash.png +0 -0
  109. package/icons/trophy.png +0 -0
  110. package/icons/updates.png +0 -0
  111. package/icons/upvote.png +0 -0
  112. package/icons/views.png +0 -0
  113. package/icons/warning.png +0 -0
  114. package/icons/wave.png +0 -0
  115. package/icons/whisper.png +0 -0
  116. package/package.json +66 -0
  117. package/src/FishiesImg.tsx +58 -0
  118. package/src/Select.tsx +119 -0
  119. package/src/css.d.ts +2 -0
  120. package/src/fx-image.ts +17 -0
  121. package/src/icons.generated.ts +658 -0
  122. package/src/icons.ts +74 -0
  123. package/src/index.ts +8 -0
  124. package/src/kit.css +267 -0
  125. package/src/kit.tsx +190 -0
  126. package/src/loader.css +48 -0
  127. package/src/loader.tsx +35 -0
  128. package/src/select-state.ts +59 -0
  129. package/src/select.css +87 -0
  130. package/src/tokens.generated.css +97 -0
  131. package/src/tokens.ts +196 -0
@@ -0,0 +1,7 @@
1
+ export * from './kit';
2
+ export { TOKEN_GROUPS, TOKENS, fx, tokensCss, type Token, type TokenGroup } from './tokens';
3
+ export { SYSTEM_ICONS, ICON_GROUPS, ICON_SIZES, iconByName, iconUrl, configureIcons, type SystemIcon as SystemIconEntry, type SystemIconName, type IconGroup, type IconSize } from './icons';
4
+ export { ICON_FACTS } from './icons.generated';
5
+ export { SPINNER_SVG, svgDataUri } from './loader';
6
+ export { FX_IMAGE_SCRIPT, reveal } from './fx-image';
7
+ export type { SelectOption } from './Select';
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ 'use client';
2
+ export * from './kit.js';
3
+ export { TOKEN_GROUPS, TOKENS, fx, tokensCss } from './tokens.js';
4
+ export { SYSTEM_ICONS, ICON_GROUPS, ICON_SIZES, iconByName, iconUrl, configureIcons } from './icons.js';
5
+ export { ICON_FACTS } from './icons.generated.js';
6
+ export { SPINNER_SVG, svgDataUri } from './loader.js';
7
+ export { FX_IMAGE_SCRIPT, reveal } from './fx-image.js';
package/dist/kit.css ADDED
@@ -0,0 +1,267 @@
1
+ @layer base, skin, system, responsive, kit;
2
+
3
+ @layer kit {
4
+
5
+ .fx-icon { display: inline-grid; place-items: center; flex: none; line-height: 0; }
6
+ .fx-icon img { display: block; width: 100%; height: 100%; object-fit: contain; pointer-events: none; user-select: none; -webkit-user-drag: none; }
7
+
8
+
9
+ .fx-button {
10
+ position: relative; isolation: isolate; display: inline-flex; align-items: center; justify-content: center; gap: var(--fx-s-2);
11
+ min-height: 46px; padding: 0 var(--fx-s-5); border: 1px solid var(--fx-rim); border-radius: var(--fx-r-full);
12
+ font: 800 var(--fx-t-md)/1 var(--fx-font-display); letter-spacing: var(--fx-track-tight); white-space: nowrap; text-decoration: none;
13
+ cursor: pointer; -webkit-tap-highlight-color: transparent;
14
+ transition: transform var(--m-quick) var(--m-ease-spring), box-shadow var(--m-quick) var(--m-ease-out), filter var(--m-quick) var(--m-ease-out);
15
+ }
16
+ .fx-button.is-sm { min-height: 36px; padding: 0 var(--fx-s-4); font-size: var(--fx-t-sm); gap: var(--fx-s-1); }
17
+ .fx-button.is-lg { min-height: 56px; padding: 0 var(--fx-s-6); font-size: var(--fx-t-lg); }
18
+ .fx-button:focus-visible { outline: 3px solid var(--fx-gold); outline-offset: 3px; }
19
+ .fx-button:disabled, .fx-button[aria-disabled='true'] { cursor: not-allowed; filter: saturate(.35) brightness(.85); transform: none; }
20
+ .fx-button:hover:not(:disabled) { transform: translateY(-2px); }
21
+ .fx-button:active:not(:disabled) { transform: translateY(2px) scale(.98); }
22
+
23
+
24
+ .fx-button, .fx-button.is-glass {
25
+ background: var(--fx-glass); color: var(--fx-ink);
26
+ box-shadow: var(--fx-rim-light), var(--fx-foot);
27
+ }
28
+ .fx-button.is-glass:hover:not(:disabled), .fx-button:not([class*='is-']):hover:not(:disabled) { border-color: var(--fx-rim-bright); }
29
+ .fx-button:active:not(:disabled) { box-shadow: var(--fx-rim-light), 0 1px 0 rgb(4 18 34 / 55%); }
30
+
31
+
32
+ :is(.fx-button.is-gold, .gold-button) {
33
+ border-color: var(--fx-pearl);
34
+ background: radial-gradient(60% 46% at 30% 10%, rgb(255 255 255 / 55%), transparent 70%), linear-gradient(180deg, var(--fx-gold-face), var(--fx-gold));
35
+ box-shadow: inset 0 1px rgb(255 255 255 / 55%), inset 0 -3px 6px rgb(200 138 61 / 30%), 0 3px 0 var(--fx-gold-foot), var(--fx-lift-1);
36
+ color: var(--fx-ink-on-gold); text-shadow: none; -webkit-text-stroke: 0;
37
+ }
38
+ :is(.fx-button.is-gold, .gold-button):hover:not(:disabled) { filter: brightness(1.05) saturate(1.05); }
39
+ :is(.fx-button.is-gold, .gold-button):active:not(:disabled) { box-shadow: inset 0 1px rgb(255 255 255 / 55%), inset 0 -2px 4px rgb(200 138 61 / 30%), 0 1px 0 var(--fx-gold-foot); }
40
+
41
+
42
+ .fx-button.is-ghost { background: transparent; border-color: transparent; box-shadow: none; color: var(--fx-ink-2); }
43
+ .fx-button.is-ghost:hover:not(:disabled) { background: rgb(255 255 255 / 8%); color: var(--fx-ink); transform: none; }
44
+ .fx-button.is-ghost:active:not(:disabled) { box-shadow: none; }
45
+
46
+
47
+ .fx-button.is-danger {
48
+ border-color: color-mix(in oklab, var(--fx-tone-coral), white 30%);
49
+ background: radial-gradient(60% 46% at 30% 10%, rgb(255 255 255 / 34%), transparent 70%), linear-gradient(180deg, var(--fx-tone-coral), color-mix(in oklab, var(--fx-alarm), black 28%));
50
+ box-shadow: inset 0 1px rgb(255 255 255 / 40%), 0 3px 0 color-mix(in oklab, var(--fx-alarm), black 55%), var(--fx-lift-1);
51
+ color: white;
52
+ }
53
+ .fx-button > .fx-icon { margin-left: calc(var(--fx-s-2) * -1); }
54
+
55
+
56
+ .fx-round {
57
+ display: inline-grid; place-items: center; flex: none; width: 38px; height: 38px; padding: 0;
58
+ border: 1px solid var(--fx-rim); border-radius: 50%; color: var(--fx-ink); cursor: pointer;
59
+ background: radial-gradient(circle at 35% 28%, rgb(255 255 255 / 26%), transparent 52%), var(--fx-navy);
60
+ box-shadow: inset 0 -2px 4px rgb(0 0 0 / 25%), 0 2px 0 rgb(2 10 24 / 50%);
61
+ transition: transform var(--m-quick) var(--m-ease-spring), filter var(--m-quick) var(--m-ease-out);
62
+ }
63
+ .fx-round.is-sm { width: 30px; height: 30px; }
64
+ .fx-round.is-lg { width: 48px; height: 48px; }
65
+ .fx-round svg { width: 55%; height: 55%; }
66
+ .fx-round .fx-icon { width: 70%; height: 70%; }
67
+ .fx-round:hover:not(:disabled) { transform: translateY(-1px) scale(1.06); filter: brightness(1.1); }
68
+ .fx-round:active:not(:disabled) { transform: translateY(1px) scale(.94); }
69
+ .fx-round:focus-visible { outline: 3px solid var(--fx-gold); outline-offset: 2px; }
70
+ .fx-round.is-coral { border-color: color-mix(in oklab, var(--fx-tone-coral), white 30%); background: radial-gradient(circle at 35% 28%, rgb(255 255 255 / 30%), transparent 52%), color-mix(in oklab, var(--fx-alarm), black 25%); }
71
+ .fx-round.is-gold { border-color: var(--fx-pearl); color: var(--fx-ink-on-gold); background: radial-gradient(circle at 35% 28%, rgb(255 255 255 / 60%), transparent 52%), linear-gradient(180deg, var(--fx-gold-face), var(--fx-gold)); box-shadow: inset 0 -2px 4px rgb(200 138 61 / 35%), 0 2px 0 var(--fx-gold-foot); }
72
+ .fx-round[aria-pressed='true'] { border-color: var(--fx-gold-rim); box-shadow: inset 0 0 0 2px rgb(255 196 107 / 40%), 0 2px 0 rgb(2 10 24 / 50%); }
73
+
74
+
75
+ .fx-chip {
76
+ display: inline-flex; align-items: center; gap: 6px; min-height: 32px; padding: 0 var(--fx-s-3);
77
+ border: 1px solid var(--fx-rim); border-radius: var(--fx-r-full); background: rgb(255 255 255 / 6%); color: var(--fx-ink);
78
+ font: 800 var(--fx-t-sm)/1 var(--fx-font-display); font-variant-numeric: tabular-nums; white-space: nowrap;
79
+ transition: background var(--m-quick) var(--m-ease-out), transform var(--m-quick) var(--m-ease-spring);
80
+ }
81
+ button.fx-chip { cursor: pointer; }
82
+ button.fx-chip:hover { background: rgb(255 255 255 / 13%); }
83
+ button.fx-chip:active { transform: scale(.95); }
84
+ .fx-chip:focus-visible { outline: 3px solid var(--fx-gold); outline-offset: 2px; }
85
+ .fx-chip > i { width: 8px; height: 8px; border-radius: 50%; background: var(--tone, var(--fx-tone-mute)); box-shadow: 0 0 0 3px color-mix(in oklab, var(--tone, var(--fx-tone-mute)), transparent 75%); }
86
+ .fx-chip .fx-icon { width: 18px; height: 18px; margin-left: -4px; }
87
+ .fx-chip[aria-pressed='true'], .fx-chip[aria-checked='true'], .fx-chip.is-on {
88
+ border-color: var(--fx-pearl); color: var(--fx-ink-on-gold);
89
+ background: linear-gradient(180deg, var(--fx-gold-face), var(--fx-gold)); box-shadow: inset 0 1px rgb(255 255 255 / 55%), 0 2px 0 var(--fx-gold-foot);
90
+ }
91
+
92
+
93
+ .fx-badge {
94
+ display: inline-grid; place-items: center; min-width: 24px; height: 24px; padding: 0 7px; border: 2px solid var(--fx-pearl); border-radius: var(--fx-r-full);
95
+ background: linear-gradient(180deg, var(--fx-tone-coral), color-mix(in oklab, var(--fx-alarm), black 22%));
96
+ color: white; font: 900 var(--fx-t-xs)/1 var(--fx-font-ui); font-variant-numeric: tabular-nums; box-shadow: 0 3px 8px rgb(0 0 0 / 35%);
97
+ }
98
+ .fx-badge.is-dot { min-width: 14px; width: 14px; height: 14px; padding: 0; }
99
+ .fx-tag {
100
+ display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px; border: 1px solid var(--fx-gold-rim); border-radius: var(--fx-r-full);
101
+ color: var(--fx-gold); font: 900 var(--fx-t-xs)/1.2 var(--fx-font-ui); letter-spacing: var(--fx-track-label); text-transform: uppercase;
102
+ }
103
+ .fx-eyebrow { display: block; color: var(--fx-gold); font: 900 var(--fx-t-xs)/1 var(--fx-font-ui); letter-spacing: var(--fx-track-label); text-transform: uppercase; }
104
+
105
+
106
+ .fx-card, .fx-panel, .fx-window, .fx-dock, .fx-toast, .fx-tooltip, .fx-stat {
107
+ border: 1px solid var(--fx-rim); background: var(--fx-glass); color: var(--fx-ink);
108
+ -webkit-backdrop-filter: var(--fx-glass-blur); backdrop-filter: var(--fx-glass-blur);
109
+ }
110
+ .fx-card { display: grid; gap: var(--fx-s-2); padding: var(--fx-s-4); border-radius: var(--fx-r-md); box-shadow: var(--fx-rim-light), var(--fx-lift-2); }
111
+ .fx-card > h3 { margin: 0; font: 800 var(--fx-t-lg)/1.15 var(--fx-font-display); letter-spacing: var(--fx-track-tight); }
112
+ .fx-card > p { margin: 0; color: var(--fx-ink-2); font: 600 var(--fx-t-md)/1.5 var(--fx-font-ui); }
113
+
114
+ .fx-panel { display: flex; flex-direction: column; min-width: 0; border-color: var(--fx-gold-rim); border-radius: var(--fx-r-lg); box-shadow: var(--fx-rim-light), var(--fx-lift-2); overflow: hidden; }
115
+ .fx-panel-head { display: flex; align-items: center; gap: var(--fx-s-3); min-height: 56px; padding: var(--fx-s-2) var(--fx-s-3) var(--fx-s-2) var(--fx-s-4); border-bottom: 1px solid var(--fx-gold-rim-quiet); }
116
+ .fx-panel-head > .fx-icon { width: 30px; height: 30px; filter: drop-shadow(0 3px 3px rgb(0 0 0 / 35%)); }
117
+ .fx-panel-head h2 { flex: 1; min-width: 0; margin: 0; overflow: hidden; color: var(--fx-gold-light); font: 800 var(--fx-t-lg)/1.1 var(--fx-font-display); text-overflow: ellipsis; white-space: nowrap; }
118
+ .fx-panel-body { padding: var(--fx-s-4); }
119
+
120
+ .fx-window { display: flex; flex-direction: column; min-width: 0; border-color: var(--fx-gold-rim); border-radius: 22px; overflow: hidden; box-shadow: var(--fx-rim-light), 0 4px 0 rgb(4 18 34 / 50%), var(--fx-lift-3); }
121
+ .fx-window-bar { display: flex; align-items: center; gap: 10px; min-height: 50px; padding: 6px 8px 6px 14px; background: linear-gradient(180deg, rgb(255 255 255 / 12%), rgb(255 255 255 / 2%)); border-bottom: 1px solid var(--fx-gold-rim-quiet); }
122
+ .fx-window-bar > .fx-icon { width: 26px; height: 26px; }
123
+ .fx-window-bar h2 { flex: 1; min-width: 0; margin: 0; overflow: hidden; color: var(--fx-gold-light); font: 800 17px/1 var(--fx-font-display); text-overflow: ellipsis; white-space: nowrap; }
124
+ .fx-window-buttons { display: flex; gap: 6px; }
125
+ .fx-window-body { flex: 1; min-height: 0; overflow: auto; padding: var(--fx-s-4); background: var(--fx-glass-well); scrollbar-width: thin; }
126
+
127
+
128
+ .fx-tabs { display: inline-flex; gap: 4px; max-width: 100%; padding: 4px; overflow-x: auto; border: 1px solid var(--fx-rim); border-radius: var(--fx-r-full); background: var(--fx-deep); box-shadow: inset 0 2px 6px rgb(0 0 0 / 40%); scrollbar-width: none; }
129
+ .fx-tab {
130
+ display: inline-flex; align-items: center; gap: 6px; min-height: 38px; padding: 0 var(--fx-s-4); border: 1px solid transparent; border-radius: var(--fx-r-full);
131
+ background: none; color: var(--fx-ink-2); font: 800 var(--fx-t-sm)/1 var(--fx-font-display); white-space: nowrap; cursor: pointer;
132
+ transition: background var(--m-base) var(--m-ease-out), color var(--m-base) var(--m-ease-out), transform var(--m-quick) var(--m-ease-spring);
133
+ }
134
+ .fx-tab .fx-icon { width: 22px; height: 22px; }
135
+ .fx-tab:hover { color: var(--fx-ink); background: rgb(255 255 255 / 7%); }
136
+ .fx-tab:active { transform: scale(.96); }
137
+ .fx-tab:focus-visible { outline: 3px solid var(--fx-gold); outline-offset: 1px; }
138
+ .fx-tab[aria-selected='true'] {
139
+ border-color: var(--fx-pearl); color: var(--fx-ink-on-gold);
140
+ background: linear-gradient(180deg, var(--fx-gold-face), var(--fx-gold)); box-shadow: inset 0 1px rgb(255 255 255 / 55%), 0 2px 0 var(--fx-gold-foot);
141
+ }
142
+
143
+
144
+ .fx-list { display: grid; gap: 2px; margin: 0; padding: 0; list-style: none; }
145
+ .fx-row {
146
+ display: flex; align-items: center; gap: var(--fx-s-3); width: 100%; min-height: 56px; padding: var(--fx-s-2) var(--fx-s-3);
147
+ border: 1px solid transparent; border-radius: var(--fx-r-md); background: none; color: var(--fx-ink); text-align: left; font: inherit;
148
+ transition: background var(--m-quick) var(--m-ease-out), border-color var(--m-quick) var(--m-ease-out);
149
+ }
150
+ button.fx-row, a.fx-row { cursor: pointer; text-decoration: none; }
151
+ :is(button, a).fx-row:hover, .fx-row.is-on { background: rgb(108 200 255 / 14%); border-color: var(--fx-gold-rim-quiet); }
152
+ .fx-row:focus-visible { outline: 3px solid var(--fx-gold); outline-offset: 1px; }
153
+ .fx-row > .fx-icon { width: 36px; height: 36px; filter: drop-shadow(0 3px 3px rgb(0 0 0 / 30%)); }
154
+ .fx-row-text { display: grid; flex: 1; gap: 3px; min-width: 0; }
155
+ .fx-row-text b { overflow: hidden; font: 800 var(--fx-t-md)/1.15 var(--fx-font-display); text-overflow: ellipsis; white-space: nowrap; }
156
+ .fx-row-text small { overflow: hidden; color: var(--fx-ink-3); font: 700 var(--fx-t-sm)/1.25 var(--fx-font-ui); text-overflow: ellipsis; white-space: nowrap; }
157
+ .fx-row-end { flex: none; color: var(--fx-gold-light); font: 800 var(--fx-t-md)/1 var(--fx-font-display); font-variant-numeric: tabular-nums; }
158
+
159
+
160
+ .fx-field { display: grid; gap: 6px; min-width: 0; }
161
+ .fx-field > span { color: var(--fx-ink-blue); font: 800 var(--fx-t-xs)/1 var(--fx-font-ui); letter-spacing: var(--fx-track-label); text-transform: uppercase; }
162
+ .fx-field > small { color: var(--fx-ink-3); font: 600 var(--fx-t-sm)/1.35 var(--fx-font-ui); }
163
+ .fx-input {
164
+ width: 100%; min-height: 46px; padding: 0 var(--fx-s-4); border: 1px solid var(--fx-rim); border-radius: var(--fx-r-full);
165
+ background: var(--fx-deep); color: var(--fx-ink); font: 700 var(--fx-t-md)/1.2 var(--fx-font-ui);
166
+ box-shadow: inset 0 2px 6px rgb(0 0 0 / 45%), 0 1px 0 rgb(255 255 255 / 7%); outline: none;
167
+ transition: border-color var(--m-quick) var(--m-ease-out), box-shadow var(--m-quick) var(--m-ease-out);
168
+ }
169
+ .fx-input::placeholder { color: var(--fx-ink-3); }
170
+ .fx-input:focus-visible { border-color: var(--fx-gold-rim); box-shadow: inset 0 2px 6px rgb(0 0 0 / 45%), 0 0 0 4px rgb(255 196 107 / 22%); }
171
+ .fx-input[aria-invalid='true'] { border-color: var(--fx-alarm); }
172
+
173
+ .fx-toggle { display: inline-flex; align-items: center; gap: var(--fx-s-3); color: var(--fx-ink); font: 800 var(--fx-t-md)/1.2 var(--fx-font-display); cursor: pointer; }
174
+ .fx-toggle > input { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
175
+ .fx-toggle > i {
176
+ position: relative; flex: none; width: 52px; height: 30px; border: 1px solid var(--fx-rim); border-radius: var(--fx-r-full);
177
+ background: var(--fx-deep); box-shadow: inset 0 2px 6px rgb(0 0 0 / 50%);
178
+ transition: background var(--m-base) var(--m-ease-out), border-color var(--m-base) var(--m-ease-out);
179
+ }
180
+
181
+ .fx-toggle > i::after {
182
+ content: ''; position: absolute; top: 3px; left: 3px; width: 22px; height: 22px; border-radius: 50%;
183
+ background: radial-gradient(circle at 34% 28%, white, var(--fx-pearl) 55%, var(--fx-cream));
184
+ box-shadow: 0 2px 0 rgb(4 18 34 / 45%), 0 3px 8px rgb(0 0 0 / 35%);
185
+ transition: transform var(--m-move) var(--m-ease-spring);
186
+ }
187
+ .fx-toggle > input:checked + i { border-color: var(--fx-pearl); background: linear-gradient(180deg, var(--fx-gold-face), var(--fx-gold)); box-shadow: inset 0 2px 4px rgb(200 138 61 / 45%); }
188
+ .fx-toggle > input:checked + i::after { transform: translateX(22px); }
189
+ .fx-toggle > input:focus-visible + i { outline: 3px solid var(--fx-gold); outline-offset: 3px; }
190
+ .fx-toggle > input:disabled + i { filter: saturate(.3); opacity: .6; }
191
+
192
+
193
+ .fx-tip { position: relative; display: inline-flex; }
194
+ .fx-tooltip {
195
+ position: absolute; z-index: var(--fx-z-tooltip); left: 50%; bottom: calc(100% + 12px); width: max-content; min-width: 180px; max-width: 280px;
196
+ display: grid; gap: 8px; padding: var(--fx-s-3); border-color: var(--fx-gold-rim); border-radius: var(--fx-r-md);
197
+ box-shadow: var(--fx-rim-light), var(--fx-lift-2); text-align: left; pointer-events: none;
198
+ opacity: 0; visibility: hidden; transform: translate(-50%, 6px) scale(.96); transform-origin: 50% 100%;
199
+ transition: opacity var(--m-base) var(--m-ease-out), transform var(--m-move) var(--m-ease-spring), visibility 0s linear var(--m-base);
200
+ }
201
+ .fx-tooltip::after { content: ''; position: absolute; left: 50%; bottom: -7px; width: 12px; height: 12px; margin-left: -6px; rotate: 45deg; border: solid var(--fx-gold-rim); border-width: 0 1px 1px 0; background: var(--fx-glass-bottom); }
202
+ .fx-tip:is(:hover, :focus-within) > .fx-tooltip, .fx-tooltip.is-open { opacity: 1; visibility: visible; transform: translate(-50%, 0); transition-delay: 0s; }
203
+ .fx-tooltip-head { display: flex; align-items: center; gap: var(--fx-s-2); font: 800 var(--fx-t-md)/1.1 var(--fx-font-display); color: var(--fx-gold-light); }
204
+ .fx-tooltip-head .fx-icon { width: 26px; height: 26px; }
205
+ .fx-tooltip p { margin: 0; color: var(--fx-ink-2); font: 600 var(--fx-t-sm)/1.4 var(--fx-font-ui); }
206
+ .fx-tooltip dl { display: grid; gap: 4px; margin: 0; padding-top: 8px; border-top: 1px solid var(--fx-gold-rim-quiet); }
207
+ .fx-tooltip dl > div { display: flex; justify-content: space-between; gap: var(--fx-s-3); }
208
+ .fx-tooltip dt { color: var(--fx-ink-blue); font: 800 var(--fx-t-sm)/1.2 var(--fx-font-ui); }
209
+ .fx-tooltip dd { margin: 0; font: 800 var(--fx-t-sm)/1.2 var(--fx-font-display); font-variant-numeric: tabular-nums; }
210
+
211
+
212
+ .fx-toast {
213
+ position: relative; display: flex; align-items: center; gap: var(--fx-s-3); width: min(380px, 100%); padding: var(--fx-s-3) var(--fx-s-3) var(--fx-s-3) var(--fx-s-3);
214
+ border-radius: var(--fx-r-lg); box-shadow: var(--fx-rim-light), var(--fx-lift-2); overflow: hidden;
215
+ }
216
+ .fx-toast::before { content: ''; position: absolute; inset: 10px auto 10px 0; width: 4px; border-radius: 0 4px 4px 0; background: var(--tone, var(--fx-gold)); }
217
+ .fx-toast-art { display: grid; place-items: center; flex: none; width: 46px; height: 46px; border-radius: 50%; background: radial-gradient(circle at 35% 28%, rgb(255 255 255 / 30%), transparent 55%), color-mix(in oklab, var(--tone, var(--fx-gold)), var(--fx-deep) 62%); box-shadow: inset 0 -3px 6px rgb(0 0 0 / 30%), 0 2px 0 rgb(2 10 24 / 45%); }
218
+ .fx-toast-art .fx-icon { width: 32px; height: 32px; }
219
+ .fx-toast-text { display: grid; flex: 1; gap: 2px; min-width: 0; }
220
+ .fx-toast-text b { font: 800 var(--fx-t-md)/1.2 var(--fx-font-display); }
221
+ .fx-toast-text span { color: var(--fx-ink-2); font: 600 var(--fx-t-sm)/1.35 var(--fx-font-ui); }
222
+
223
+
224
+ .fx-stats { display: flex; flex-wrap: wrap; gap: 10px; }
225
+ .fx-stat { display: inline-flex; align-items: center; gap: 10px; min-height: 58px; padding: 8px 16px 8px 10px; border-radius: 18px; box-shadow: var(--fx-rim-light), var(--fx-foot); }
226
+ .fx-stat > .fx-icon { width: 38px; height: 38px; filter: drop-shadow(0 3px 3px rgb(0 0 0 / 35%)); }
227
+ .fx-stat b { display: block; color: var(--fx-gold-light); font: 800 22px/1 var(--fx-font-display); font-variant-numeric: tabular-nums; }
228
+ .fx-stat b small { margin-left: 3px; color: var(--fx-ink-3); font-size: var(--fx-t-sm); }
229
+ .fx-stat > span > span { color: var(--fx-ink-3); font: 700 var(--fx-t-sm)/1.3 var(--fx-font-ui); }
230
+
231
+
232
+ .fx-tile { display: flex; flex-direction: column; align-items: stretch; gap: 8px; width: 118px; padding: 0; border: 0; background: none; color: var(--fx-ink); font: inherit; cursor: pointer; }
233
+ .fx-tile-glass {
234
+ position: relative; display: grid; place-items: center; aspect-ratio: 1; border: 1.5px solid var(--fx-gold-rim); border-radius: var(--fx-r-xl);
235
+ background: radial-gradient(circle at 30% 22%, rgb(255 255 255 / 34%), transparent 34%), linear-gradient(160deg, oklch(62% .12 var(--hue, 220)), oklch(36% .1 var(--hue, 220)) 70%, oklch(28% .08 var(--hue, 220)));
236
+ box-shadow: var(--fx-plump), var(--fx-foot-deep), var(--fx-lift-2);
237
+ transition: transform var(--m-quick) var(--m-ease-spring), box-shadow var(--m-quick) var(--m-ease-out);
238
+ }
239
+ .fx-tile-glass > .fx-icon { width: 62%; height: 62%; filter: drop-shadow(0 6px 6px rgb(0 0 0 / 35%)); transition: transform var(--m-move) var(--m-ease-spring); }
240
+ .fx-tile-glass > .fx-badge { position: absolute; top: -6px; right: -6px; }
241
+ .fx-tile:hover .fx-tile-glass, .fx-tile:focus-visible .fx-tile-glass { transform: translateY(-3px); }
242
+ .fx-tile:hover .fx-tile-glass > .fx-icon { transform: scale(1.08) rotate(-3deg); }
243
+ .fx-tile:active .fx-tile-glass { transform: translateY(3px); box-shadow: var(--fx-plump), 0 2px 0 rgb(4 18 34 / 55%); }
244
+ .fx-tile:focus-visible { outline: none; }
245
+ .fx-tile:focus-visible .fx-tile-glass { outline: 3px solid var(--fx-gold); outline-offset: 4px; }
246
+ .fx-tile.is-open .fx-tile-glass::after { content: ''; position: absolute; bottom: -12px; width: 18px; height: 5px; border-radius: 3px; background: var(--fx-gold); box-shadow: 0 0 10px var(--fx-gold); }
247
+ .fx-tile-title { text-align: center; font: 800 var(--fx-t-md)/1.2 var(--fx-font-display); text-shadow: var(--fx-on-water); }
248
+
249
+ .fx-dock { display: inline-flex; align-items: center; gap: 6px; max-width: 100%; padding: 8px 10px; overflow-x: auto; border-color: var(--fx-gold-rim); border-radius: var(--fx-r-xl); box-shadow: var(--fx-rim-light), var(--fx-lift-2); scrollbar-width: none; }
250
+ .fx-dock-item {
251
+ position: relative; flex: none; display: grid; place-items: center; width: 60px; height: 60px; padding: 0; border: 0; border-radius: 18px; background: none; cursor: pointer;
252
+ transition: transform var(--m-quick) var(--m-ease-spring), background var(--m-quick) var(--m-ease-out);
253
+ }
254
+ .fx-dock-item:hover { background: rgb(255 255 255 / 8%); transform: translateY(-4px) scale(1.06); }
255
+ .fx-dock-item:focus-visible { outline: 3px solid var(--fx-gold); outline-offset: 1px; }
256
+ .fx-dock-item > .fx-icon { width: 46px; height: 46px; filter: drop-shadow(0 4px 4px rgb(0 0 0 / 35%)); }
257
+ .fx-dock-item > i { position: absolute; bottom: 1px; left: 50%; width: 6px; height: 6px; margin-left: -3px; border-radius: 50%; }
258
+ .fx-dock-item.is-open > i { background: var(--fx-ink-2); }
259
+ .fx-dock-item.is-front > i { width: 16px; margin-left: -8px; border-radius: 3px; background: var(--fx-gold); box-shadow: 0 0 8px var(--fx-gold); }
260
+ .fx-dock-item > .fx-badge { position: absolute; top: 0; right: 0; }
261
+ .fx-dock-sep { flex: none; width: 1px; height: 40px; margin: 0 4px; background: var(--fx-gold-rim-quiet); }
262
+
263
+ @media (prefers-reduced-motion: reduce) {
264
+ :is(.fx-button, .fx-round, .fx-tile-glass, .fx-dock-item, .fx-tab, .fx-chip):is(:hover, :active) { transform: none !important; }
265
+ .fx-tile:hover .fx-tile-glass > .fx-icon { transform: none; }
266
+ }
267
+ }
package/dist/kit.d.ts ADDED
@@ -0,0 +1,159 @@
1
+ import { type AnchorHTMLAttributes, type ButtonHTMLAttributes, type CSSProperties, type InputHTMLAttributes, type ReactNode } from 'react';
2
+ import { type IconSize, type SystemIconName } from './icons';
3
+ import './kit.css';
4
+ export declare function SystemIcon({ name, size, fallback, className }: {
5
+ name: SystemIconName;
6
+ size?: IconSize | number;
7
+ fallback?: ReactNode;
8
+ className?: string;
9
+ }): import("react").JSX.Element | null;
10
+ export type ButtonTone = 'glass' | 'gold' | 'ghost' | 'danger';
11
+ type ButtonOwn = {
12
+ tone?: ButtonTone;
13
+ size?: 'sm' | 'md' | 'lg';
14
+ icon?: SystemIconName;
15
+ children?: ReactNode;
16
+ };
17
+ /** A plump toy button: glass for everyday actions, gold for the one thing to press, ghost for a quiet third choice, danger for what cannot be undone. */
18
+ export declare function Button({ tone, size, icon, children, className, type, ...rest }: ButtonOwn & ButtonHTMLAttributes<HTMLButtonElement>): import("react").JSX.Element;
19
+ /** The same button as a link, for an action that goes somewhere. */
20
+ export declare function ButtonLink({ tone, size, icon, children, className, ...rest }: ButtonOwn & AnchorHTMLAttributes<HTMLAnchorElement>): import("react").JSX.Element;
21
+ /** A round toy button for window controls, the video player and close; `label` is what it says to a screen reader. */
22
+ export declare function RoundButton({ label, tone, size, icon, children, className, type, ...rest }: {
23
+ label: string;
24
+ tone?: 'navy' | 'coral' | 'gold';
25
+ size?: 'sm' | 'md' | 'lg';
26
+ icon?: SystemIconName;
27
+ children?: ReactNode;
28
+ } & ButtonHTMLAttributes<HTMLButtonElement>): import("react").JSX.Element;
29
+ /** A small reading or choice: a dot of colour for a kind, an icon, or gold when chosen. */
30
+ export declare function Chip({ tone, icon, selected, onClick, children, className }: {
31
+ tone?: string;
32
+ icon?: SystemIconName;
33
+ selected?: boolean;
34
+ onClick?: () => void;
35
+ children: ReactNode;
36
+ className?: string;
37
+ }): import("react").JSX.Element;
38
+ export declare function Badge({ count, label }: {
39
+ count?: number;
40
+ label?: string;
41
+ }): import("react").JSX.Element;
42
+ export declare function Tag({ children }: {
43
+ children: ReactNode;
44
+ }): import("react").JSX.Element;
45
+ /** A glass card: an optional eyebrow, a title and a few lines. */
46
+ export declare function Card({ eyebrow, title, children, className }: {
47
+ eyebrow?: string;
48
+ title?: ReactNode;
49
+ children?: ReactNode;
50
+ className?: string;
51
+ }): import("react").JSX.Element;
52
+ export declare function Panel({ title, icon, actions, children, className, style }: {
53
+ title: ReactNode;
54
+ icon?: SystemIconName;
55
+ actions?: ReactNode;
56
+ children?: ReactNode;
57
+ className?: string;
58
+ style?: CSSProperties;
59
+ }): import("react").JSX.Element;
60
+ /** A Keeper OS window frame: a title bar with round minimise and close buttons over a darker well. */
61
+ export declare function Window({ title, icon, onClose, onMinimize, closeLabel, minimizeLabel, children, className, style }: {
62
+ title: ReactNode;
63
+ icon?: SystemIconName;
64
+ onClose?: () => void;
65
+ onMinimize?: () => void;
66
+ closeLabel?: string;
67
+ minimizeLabel?: string;
68
+ children?: ReactNode;
69
+ className?: string;
70
+ style?: CSSProperties;
71
+ }): import("react").JSX.Element;
72
+ export type TabItem = {
73
+ id: string;
74
+ label: string;
75
+ icon?: SystemIconName;
76
+ };
77
+ export declare function Tabs({ items, value, onChange, label }: {
78
+ items: readonly TabItem[];
79
+ value: string;
80
+ onChange: (id: string) => void;
81
+ label: string;
82
+ }): import("react").JSX.Element;
83
+ /** A list of rows; each row has an icon, a title, a detail line and a value at its end. */
84
+ export declare function List({ children, label }: {
85
+ children: ReactNode;
86
+ label?: string;
87
+ }): import("react").JSX.Element;
88
+ export declare function ListRow({ icon, title, detail, end, onClick, href, selected }: {
89
+ icon?: SystemIconName;
90
+ title: ReactNode;
91
+ detail?: ReactNode;
92
+ end?: ReactNode;
93
+ onClick?: () => void;
94
+ href?: string;
95
+ selected?: boolean;
96
+ }): import("react").JSX.Element;
97
+ export declare function Field({ label, hint, className, ...input }: {
98
+ label: string;
99
+ hint?: string;
100
+ } & InputHTMLAttributes<HTMLInputElement>): import("react").JSX.Element;
101
+ export declare function Toggle({ label, checked, onChange, disabled }: {
102
+ label: ReactNode;
103
+ checked: boolean;
104
+ onChange: (checked: boolean) => void;
105
+ disabled?: boolean;
106
+ }): import("react").JSX.Element;
107
+ export type TooltipRow = {
108
+ label: ReactNode;
109
+ value: ReactNode;
110
+ };
111
+ /** A designed tooltip, never plain text: an icon and title, a line, and labelled values; it opens on hover and on keyboard focus. */
112
+ export declare function Tooltip({ title, icon, text, rows, children, open }: {
113
+ title: ReactNode;
114
+ icon?: SystemIconName;
115
+ text?: ReactNode;
116
+ rows?: readonly TooltipRow[];
117
+ children: ReactNode;
118
+ open?: boolean;
119
+ }): import("react").JSX.Element;
120
+ export declare function Toast({ title, children, icon, tone, onClose, closeLabel }: {
121
+ title: ReactNode;
122
+ children?: ReactNode;
123
+ icon?: SystemIconName;
124
+ tone?: string;
125
+ onClose?: () => void;
126
+ closeLabel?: string;
127
+ }): import("react").JSX.Element;
128
+ /** A number a keeper reads at a glance: an icon, the value in gold with its unit, and what it counts. */
129
+ export declare function Stat({ icon, value, unit, label }: {
130
+ icon?: SystemIconName;
131
+ value: ReactNode;
132
+ unit?: string;
133
+ label: ReactNode;
134
+ }): import("react").JSX.Element;
135
+ /** A desktop tile: a plump glass square in its own hue holding an icon, a title under it, a badge and an open mark. */
136
+ export declare function DockTile({ icon, title, hue, badge, open, onClick }: {
137
+ icon: SystemIconName;
138
+ title: string;
139
+ hue: number;
140
+ badge?: number;
141
+ open?: boolean;
142
+ onClick?: () => void;
143
+ }): import("react").JSX.Element;
144
+ export declare function Dock({ children, label }: {
145
+ children: ReactNode;
146
+ label: string;
147
+ }): import("react").JSX.Element;
148
+ /** One icon on a dock. */
149
+ export declare function DockItem({ icon, label, front, open, badge, onClick }: {
150
+ icon: SystemIconName;
151
+ label: string;
152
+ front?: boolean;
153
+ open?: boolean;
154
+ badge?: number;
155
+ onClick?: () => void;
156
+ }): import("react").JSX.Element;
157
+ export { Spinner, Progress } from './loader';
158
+ export { default as Select } from './Select';
159
+ export { default as Image } from './FishiesImg';
package/dist/kit.js ADDED
@@ -0,0 +1,104 @@
1
+ 'use client';
2
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useId, useState } from 'react';
4
+ import FishiesImg from './FishiesImg.js';
5
+ import { ICON_SIZES, iconByName, iconUrl } from './icons.js';
6
+ import './kit.css';
7
+ const cx = (...names) => names.filter(Boolean).join(' ');
8
+ const px = (size) => typeof size === 'number' ? size : ICON_SIZES[size];
9
+ export function SystemIcon({ name, size = 'md', fallback, className }) {
10
+ const [failed, setFailed] = useState(false);
11
+ const icon = iconByName(name);
12
+ const side = px(size);
13
+ if (!icon || failed)
14
+ return fallback ? _jsx(_Fragment, { children: fallback }) : null;
15
+ return _jsx("span", { className: cx('fx-icon', className), style: { width: side, height: side }, "aria-hidden": "true", children: _jsx(FishiesImg, { src: iconUrl(icon), width: side, height: side, alt: "", draggable: false, decoding: "async", onError: () => setFailed(true) }) });
16
+ }
17
+ /** A plump toy button: glass for everyday actions, gold for the one thing to press, ghost for a quiet third choice, danger for what cannot be undone. */
18
+ export function Button({ tone = 'glass', size = 'md', icon, children, className, type = 'button', ...rest }) {
19
+ return _jsxs("button", { type: type, className: cx('fx-button', `is-${tone}`, size !== 'md' && `is-${size}`, className), ...rest, children: [icon && _jsx(SystemIcon, { name: icon, size: size === 'lg' ? 30 : size === 'sm' ? 20 : 26 }), children] });
20
+ }
21
+ /** The same button as a link, for an action that goes somewhere. */
22
+ export function ButtonLink({ tone = 'glass', size = 'md', icon, children, className, ...rest }) {
23
+ return _jsxs("a", { className: cx('fx-button', `is-${tone}`, size !== 'md' && `is-${size}`, className), ...rest, children: [icon && _jsx(SystemIcon, { name: icon, size: size === 'lg' ? 30 : size === 'sm' ? 20 : 26 }), children] });
24
+ }
25
+ /** A round toy button for window controls, the video player and close; `label` is what it says to a screen reader. */
26
+ export function RoundButton({ label, tone = 'navy', size = 'md', icon, children, className, type = 'button', ...rest }) {
27
+ return _jsx("button", { type: type, "aria-label": label, title: label, className: cx('fx-round', tone !== 'navy' && `is-${tone}`, size !== 'md' && `is-${size}`, className), ...rest, children: icon ? _jsx(SystemIcon, { name: icon, size: size === 'lg' ? 34 : 26 }) : children });
28
+ }
29
+ /** A small reading or choice: a dot of colour for a kind, an icon, or gold when chosen. */
30
+ export function Chip({ tone, icon, selected, onClick, children, className }) {
31
+ const style = tone ? { '--tone': tone } : undefined;
32
+ const inner = _jsxs(_Fragment, { children: [tone && _jsx("i", { "aria-hidden": "true" }), icon && _jsx(SystemIcon, { name: icon, size: 18 }), children] });
33
+ return onClick
34
+ ? _jsx("button", { type: "button", className: cx('fx-chip', className), style: style, "aria-pressed": selected, onClick: onClick, children: inner })
35
+ : _jsx("span", { className: cx('fx-chip', selected && 'is-on', className), style: style, children: inner });
36
+ }
37
+ export function Badge({ count, label }) {
38
+ return count === undefined
39
+ ? _jsx("span", { className: "fx-badge is-dot", role: "status", "aria-label": label })
40
+ : _jsx("span", { className: "fx-badge", role: "status", "aria-label": label, children: count > 99 ? '99+' : count });
41
+ }
42
+ export function Tag({ children }) {
43
+ return _jsx("span", { className: "fx-tag", children: children });
44
+ }
45
+ /** A glass card: an optional eyebrow, a title and a few lines. */
46
+ export function Card({ eyebrow, title, children, className }) {
47
+ return _jsxs("article", { className: cx('fx-card', className), children: [eyebrow && _jsx("span", { className: "fx-eyebrow", children: eyebrow }), title && _jsx("h3", { children: title }), children] });
48
+ }
49
+ export function Panel({ title, icon, actions, children, className, style }) {
50
+ return _jsxs("section", { className: cx('fx-panel', className), style: style, children: [_jsxs("header", { className: "fx-panel-head", children: [icon && _jsx(SystemIcon, { name: icon, size: 30 }), _jsx("h2", { children: title }), actions] }), _jsx("div", { className: "fx-panel-body", children: children })] });
51
+ }
52
+ /** A Keeper OS window frame: a title bar with round minimise and close buttons over a darker well. */
53
+ export function Window({ title, icon, onClose, onMinimize, closeLabel = 'Close', minimizeLabel = 'Minimise', children, className, style }) {
54
+ return _jsxs("section", { className: cx('fx-window', className), style: style, role: "dialog", "aria-label": typeof title === 'string' ? title : undefined, children: [_jsxs("header", { className: "fx-window-bar", children: [icon && _jsx(SystemIcon, { name: icon, size: 26 }), _jsx("h2", { children: title }), _jsxs("span", { className: "fx-window-buttons", children: [onMinimize && _jsx(RoundButton, { label: minimizeLabel, size: "sm", onClick: onMinimize, children: _jsx("svg", { viewBox: "0 0 16 16", "aria-hidden": "true", children: _jsx("path", { d: "M3.5 8h9", stroke: "currentColor", strokeWidth: "2.4", strokeLinecap: "round" }) }) }), onClose && _jsx(RoundButton, { label: closeLabel, size: "sm", tone: "coral", onClick: onClose, children: _jsx("svg", { viewBox: "0 0 16 16", "aria-hidden": "true", children: _jsx("path", { d: "m4.5 4.5 7 7m0-7-7 7", stroke: "currentColor", strokeWidth: "2.4", strokeLinecap: "round" }) }) })] })] }), _jsx("div", { className: "fx-window-body", children: children })] });
55
+ }
56
+ export function Tabs({ items, value, onChange, label }) {
57
+ const move = (from, step) => onChange(items[(from + step + items.length) % items.length].id);
58
+ return _jsx("div", { className: "fx-tabs", role: "tablist", "aria-label": label, children: items.map((item, index) => _jsxs("button", { type: "button", role: "tab", className: "fx-tab", "aria-selected": item.id === value, tabIndex: item.id === value ? 0 : -1, onClick: () => onChange(item.id), onKeyDown: event => { if (event.key === 'ArrowRight')
59
+ move(index, 1); if (event.key === 'ArrowLeft')
60
+ move(index, -1); }, children: [item.icon && _jsx(SystemIcon, { name: item.icon, size: 22 }), item.label] }, item.id)) });
61
+ }
62
+ /** A list of rows; each row has an icon, a title, a detail line and a value at its end. */
63
+ export function List({ children, label }) {
64
+ return _jsx("ul", { className: "fx-list", "aria-label": label, children: children });
65
+ }
66
+ export function ListRow({ icon, title, detail, end, onClick, href, selected }) {
67
+ const inner = _jsxs(_Fragment, { children: [icon && _jsx(SystemIcon, { name: icon, size: 36 }), _jsxs("span", { className: "fx-row-text", children: [_jsx("b", { children: title }), detail && _jsx("small", { children: detail })] }), end !== undefined && _jsx("span", { className: "fx-row-end", children: end })] });
68
+ return _jsx("li", { children: href ? _jsx("a", { className: cx('fx-row', selected && 'is-on'), href: href, children: inner })
69
+ : onClick ? _jsx("button", { type: "button", className: cx('fx-row', selected && 'is-on'), onClick: onClick, "aria-pressed": selected, children: inner })
70
+ : _jsx("div", { className: cx('fx-row', selected && 'is-on'), children: inner }) });
71
+ }
72
+ export function Field({ label, hint, className, ...input }) {
73
+ const id = useId();
74
+ return _jsxs("label", { className: cx('fx-field', className), htmlFor: id, children: [_jsx("span", { children: label }), _jsx("input", { id: id, className: "fx-input", "aria-describedby": hint ? `${id}-hint` : undefined, ...input }), hint && _jsx("small", { id: `${id}-hint`, children: hint })] });
75
+ }
76
+ export function Toggle({ label, checked, onChange, disabled }) {
77
+ return _jsxs("label", { className: "fx-toggle", children: [_jsx("input", { type: "checkbox", role: "switch", checked: checked, disabled: disabled, onChange: event => onChange(event.target.checked) }), _jsx("i", { "aria-hidden": "true" }), label] });
78
+ }
79
+ /** A designed tooltip, never plain text: an icon and title, a line, and labelled values; it opens on hover and on keyboard focus. */
80
+ export function Tooltip({ title, icon, text, rows, children, open }) {
81
+ const id = useId();
82
+ return _jsxs("span", { className: "fx-tip", "aria-describedby": id, children: [children, _jsxs("span", { className: cx('fx-tooltip', open && 'is-open'), role: "tooltip", id: id, children: [_jsxs("span", { className: "fx-tooltip-head", children: [icon && _jsx(SystemIcon, { name: icon, size: 26 }), title] }), text && _jsx("p", { children: text }), rows && rows.length > 0 && _jsx("dl", { children: rows.map((row, index) => _jsxs("div", { children: [_jsx("dt", { children: row.label }), _jsx("dd", { children: row.value })] }, index)) })] })] });
83
+ }
84
+ export function Toast({ title, children, icon, tone, onClose, closeLabel = 'Close' }) {
85
+ return _jsxs("div", { className: "fx-toast", role: "status", style: tone ? { '--tone': tone } : undefined, children: [icon && _jsx("span", { className: "fx-toast-art", children: _jsx(SystemIcon, { name: icon, size: 32 }) }), _jsxs("span", { className: "fx-toast-text", children: [_jsx("b", { children: title }), children && _jsx("span", { children: children })] }), onClose && _jsx(RoundButton, { label: closeLabel, size: "sm", onClick: onClose, children: _jsx("svg", { viewBox: "0 0 16 16", "aria-hidden": "true", children: _jsx("path", { d: "m4.5 4.5 7 7m0-7-7 7", stroke: "currentColor", strokeWidth: "2.4", strokeLinecap: "round" }) }) })] });
86
+ }
87
+ /** A number a keeper reads at a glance: an icon, the value in gold with its unit, and what it counts. */
88
+ export function Stat({ icon, value, unit, label }) {
89
+ return _jsxs("div", { className: "fx-stat", children: [icon && _jsx(SystemIcon, { name: icon, size: 38 }), _jsxs("span", { children: [_jsxs("b", { children: [value, unit && _jsx("small", { children: unit })] }), _jsx("span", { children: label })] })] });
90
+ }
91
+ /** A desktop tile: a plump glass square in its own hue holding an icon, a title under it, a badge and an open mark. */
92
+ export function DockTile({ icon, title, hue, badge, open, onClick }) {
93
+ return _jsxs("button", { type: "button", className: cx('fx-tile', open && 'is-open'), onClick: onClick, style: { '--hue': hue }, children: [_jsxs("span", { className: "fx-tile-glass", children: [_jsx(SystemIcon, { name: icon, size: 72 }), badge !== undefined && badge > 0 && _jsx(Badge, { count: badge })] }), _jsx("span", { className: "fx-tile-title", children: title })] });
94
+ }
95
+ export function Dock({ children, label }) {
96
+ return _jsx("nav", { className: "fx-dock", "aria-label": label, children: children });
97
+ }
98
+ /** One icon on a dock. */
99
+ export function DockItem({ icon, label, front, open, badge, onClick }) {
100
+ return _jsxs("button", { type: "button", className: cx('fx-dock-item', open && 'is-open', front && 'is-front'), "aria-label": label, title: label, onClick: onClick, children: [_jsx(SystemIcon, { name: icon, size: "lg" }), badge !== undefined && badge > 0 && _jsx(Badge, { count: badge }), _jsx("i", { "aria-hidden": "true" })] });
101
+ }
102
+ export { Spinner, Progress } from './loader.js';
103
+ export { default as Select } from './Select.js';
104
+ export { default as Image } from './FishiesImg.js';
@@ -0,0 +1,48 @@
1
+ .fx-spinner {
2
+ display: inline-grid; place-items: center; flex: none;
3
+ width: var(--fx-size, 32px); height: var(--fx-size, 32px);
4
+ filter: drop-shadow(0 4px 10px rgb(1 8 20 / 45%));
5
+ }
6
+ .fx-spinner > svg { display: block; width: 100%; height: 100%; }
7
+
8
+ .fx-progress {
9
+ position: relative; display: block; overflow: hidden;
10
+ width: min(280px, 100%); height: 10px; border-radius: 999px;
11
+ border: 1px solid rgb(196 236 250 / 20%);
12
+ background: linear-gradient(180deg, rgb(4 16 26 / 72%), rgb(12 38 56 / 72%));
13
+ box-shadow: inset 0 1px 3px rgb(0 0 0 / 50%), 0 1px 0 rgb(255 255 255 / 7%);
14
+ }
15
+ .fx-progress > i {
16
+ position: absolute; top: 1px; bottom: 1px; left: 1px;
17
+ width: max(8px, calc((100% - 2px) * var(--fx-value, 0)));
18
+ border-radius: inherit;
19
+ background: linear-gradient(180deg, var(--fx-gold-face), var(--fx-gold) 55%, var(--fx-gold-deep));
20
+ box-shadow: 0 0 8px var(--fx-gold-rim);
21
+ transition: width 320ms cubic-bezier(.2, .8, .2, 1);
22
+ }
23
+
24
+ .fx-progress > i::after {
25
+ content: ''; position: absolute; top: 50%; right: 1px; width: 6px; height: 6px; margin-top: -3px;
26
+ border-radius: 50%; background: radial-gradient(circle at 35% 35%, #fff, rgb(255 255 255 / 35%) 70%);
27
+ }
28
+ .fx-progress[data-indeterminate] > i { width: 34%; animation: fx-sweep 1.4s cubic-bezier(.45, 0, .55, 1) infinite; }
29
+ @keyframes fx-sweep { from { transform: translateX(-105%); } to { transform: translateX(300%); } }
30
+
31
+ :is(img, video)[data-fx='wait'], img[data-fx='error'] { object-position: -99999px -99999px !important; }
32
+ :is(img, video)[data-fx='wait'] {
33
+ background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'%3E%3Cstyle%3E.a%7Btransform-origin:24px 24px;animation:s 1.1s linear infinite%7D.b%7Bopacity:0;animation:u 1.8s ease-in infinite%7D.c%7Banimation-delay:.6s%7D.d%7Banimation-delay:1.2s%7D@keyframes s%7Bto%7Btransform:rotate(360deg)%7D%7D@keyframes u%7B0%25%7Bopacity:0;transform:translateY(7px)%7D30%25%7Bopacity:.95%7D100%25%7Bopacity:0;transform:translateY(-9px)%7D%7D@media (prefers-reduced-motion:reduce)%7B.a%7Banimation-duration:3.6s%7D.b%7Banimation:none;opacity:.8%7D%7D%3C/style%3E%3Ccircle cx='24' cy='24' r='21' fill='%230b2a3c' fill-opacity='.74' stroke='%23c4ecfa' stroke-opacity='.24'/%3E%3Ccircle cx='24' cy='24' r='16' fill='none' stroke='%23c4ecfa' stroke-opacity='.13' stroke-width='3'/%3E%3Ccircle class='a' cx='24' cy='24' r='16' fill='none' stroke='%23ffc46b' stroke-width='3' stroke-linecap='round' stroke-dasharray='25 76'/%3E%3Cg fill='%23dff6ff' fill-opacity='.16' stroke='%23dff6ff' stroke-width='1.1'%3E%3Ccircle class='b' cx='21.5' cy='26' r='2.7'/%3E%3Ccircle class='b c' cx='27' cy='27.5' r='1.9'/%3E%3Ccircle class='b d' cx='23.5' cy='30' r='1.3'/%3E%3C/g%3E%3C/svg%3E") center / clamp(12px, 36%, 40px) no-repeat,
34
+ radial-gradient(closest-side, rgb(196 236 250 / 10%), rgb(196 236 250 / 4%)) !important;
35
+ }
36
+ img[data-fx='error'] { background: rgb(196 236 250 / 5%) !important; }
37
+ :is(img, video)[data-fx='fade'] { animation: fx-in 240ms cubic-bezier(.2, .8, .2, 1); }
38
+ @keyframes fx-in { from { opacity: 0; } }
39
+
40
+ @media (prefers-reduced-motion: reduce) {
41
+ .fx-progress > i { transition: none; }
42
+ .fx-progress[data-indeterminate] > i { animation-duration: 4s; }
43
+ :is(img, video)[data-fx='fade'] { animation: none; }
44
+ :is(img, video)[data-fx='wait'] {
45
+ background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'%3E%3Ccircle cx='24' cy='24' r='21' fill='%230b2a3c' fill-opacity='.74' stroke='%23c4ecfa' stroke-opacity='.24'/%3E%3Ccircle cx='24' cy='24' r='16' fill='none' stroke='%23c4ecfa' stroke-opacity='.13' stroke-width='3'/%3E%3Ccircle class='a' cx='24' cy='24' r='16' fill='none' stroke='%23ffc46b' stroke-width='3' stroke-linecap='round' stroke-dasharray='25 76'/%3E%3Cg fill='%23dff6ff' fill-opacity='.16' stroke='%23dff6ff' stroke-width='1.1'%3E%3Ccircle class='b' cx='21.5' cy='26' r='2.7'/%3E%3Ccircle class='b c' cx='27' cy='27.5' r='1.9'/%3E%3Ccircle class='b d' cx='23.5' cy='30' r='1.3'/%3E%3C/g%3E%3C/svg%3E") center / clamp(12px, 36%, 40px) no-repeat,
46
+ radial-gradient(closest-side, rgb(196 236 250 / 10%), rgb(196 236 250 / 4%)) !important;
47
+ }
48
+ }
@@ -0,0 +1,17 @@
1
+ import type { CSSProperties } from 'react';
2
+ import './loader.css';
3
+ export declare const SPINNER_SVG: string;
4
+ export declare function svgDataUri(svg: string): string;
5
+ export declare function Spinner({ size, label, className, style }: {
6
+ size?: number;
7
+ label?: string;
8
+ className?: string;
9
+ style?: CSSProperties;
10
+ }): import("react").JSX.Element;
11
+ /** A progress bar: a glass channel filling with gold, a bubble riding its leading edge; with no value the gold sweeps. */
12
+ export declare function Progress({ value, label, className, style }: {
13
+ value?: number;
14
+ label?: string;
15
+ className?: string;
16
+ style?: CSSProperties;
17
+ }): import("react").JSX.Element;