neba 1.16.0 → 1.16.2

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/dist/tailwind.css CHANGED
@@ -1177,6 +1177,256 @@
1177
1177
  }
1178
1178
  }
1179
1179
 
1180
+ /* ---------------------------------------------------------------------------
1181
+ * The notch
1182
+ *
1183
+ * A field whose label stands on its top edge: `labelPlacement` `notch` or
1184
+ * `float`. The shell drops its own edge, and `internal/notch.tsx` draws one in
1185
+ * three pieces on a grid: the start, as wide as the corner's radius; the gap,
1186
+ * as wide as the label because the label sits in its column; and the end, which
1187
+ * takes the rest. The gap has no top line, and that missing line is the notch.
1188
+ *
1189
+ * Every piece reads its colour out of `--n-notch-line` and its top light out of
1190
+ * `--n-notch-top`, which the rules below set from the variant and from the state
1191
+ * of the element the notch sits in: the shell, or for a Select the frame that
1192
+ * holds the notch beside its trigger. The ring
1193
+ * is each piece's `::before`, two pixels outside it, and like every ring in the
1194
+ * library it is declared at no width and arrives in colour.
1195
+ *
1196
+ * It is CSS rather than Tailwind because the conditions are relations between
1197
+ * three elements — the holder's hover and focus, the control's emptiness and
1198
+ * the notch itself — and the Tailwind spelling of "a later sibling of an empty
1199
+ * input whose parent has no focus" is not something anybody should have to read.
1200
+ * ------------------------------------------------------------------------- */
1201
+
1202
+ /* The shell, and the label's width under it at no height, in one cell. */
1203
+ .neba-notch-frame {
1204
+ position: relative;
1205
+ display: grid;
1206
+ }
1207
+
1208
+ .neba-notch-frame > * {
1209
+ grid-area: 1 / 1;
1210
+ }
1211
+
1212
+ .neba-notch-sizer {
1213
+ height: 0;
1214
+ overflow: hidden;
1215
+ visibility: hidden;
1216
+ white-space: nowrap;
1217
+ padding-inline: calc(var(--n-notch-r) + var(--n-notch-gap));
1218
+ font-size: var(--n-float-size);
1219
+ }
1220
+
1221
+ .neba-notch-sizer[data-sample]::before {
1222
+ content: attr(data-sample);
1223
+ }
1224
+
1225
+ .neba-notch {
1226
+ position: absolute;
1227
+ inset: var(--n-notch-inset, 0);
1228
+ display: grid;
1229
+ grid-template-columns:
1230
+ var(--n-notch-r)
1231
+ fit-content(calc(100% - 2 * var(--n-notch-r)))
1232
+ minmax(var(--n-notch-r), 1fr);
1233
+ grid-template-rows: 100%;
1234
+ border-radius: inherit;
1235
+ pointer-events: none;
1236
+
1237
+ /* The label on the edge, and the gap open under it. A resting label swaps
1238
+ every one of these below. */
1239
+ --n-label-top: calc(0.5px - var(--n-float-leading) / 2);
1240
+ --n-label-x: 0px;
1241
+ --n-label-size: var(--n-float-size);
1242
+ --n-label-leading: var(--n-float-leading);
1243
+ --n-gap-line: transparent;
1244
+ --n-gap-top: transparent;
1245
+ }
1246
+
1247
+ .neba-notch[data-variant='outline'] {
1248
+ --n-notch-line: var(--n-line);
1249
+ --n-notch-top: var(--neba-plate-top);
1250
+ }
1251
+
1252
+ .neba-notch[data-variant='solid'] {
1253
+ --n-notch-line: var(--neba-plate-edge);
1254
+ --n-notch-top: var(--neba-plate-top);
1255
+ }
1256
+
1257
+ @media (hover: hover) {
1258
+ :hover > .neba-notch[data-variant='outline']:not([data-state]) {
1259
+ --n-notch-line: var(--n-line-hover);
1260
+ }
1261
+ }
1262
+
1263
+ /* After the hover rule and at its specificity, so a pointer resting on a
1264
+ focused field leaves the edge the colour of the ring it is flush against. */
1265
+ :focus-within > .neba-notch[data-variant='outline']:not([data-state]) {
1266
+ --n-notch-line: var(--n-ring);
1267
+ }
1268
+
1269
+ .neba-notch[data-state='disabled'] {
1270
+ --n-notch-top: transparent;
1271
+ }
1272
+
1273
+ .neba-notch[data-state='disabled'][data-variant='outline'] {
1274
+ --n-notch-line: var(--neba-disabled-border);
1275
+ }
1276
+
1277
+ .neba-notch[data-state='disabled'][data-variant='solid'] {
1278
+ --n-notch-line: transparent;
1279
+ }
1280
+
1281
+ .neba-notch-start,
1282
+ .neba-notch-gap,
1283
+ .neba-notch-end {
1284
+ position: relative;
1285
+ grid-row: 1;
1286
+ border: 0 solid var(--n-notch-line, transparent);
1287
+ border-block-width: 1px;
1288
+ box-shadow: inset 0 1px 0 var(--n-notch-top, transparent);
1289
+ transition:
1290
+ border-color var(--neba-duration) var(--neba-ease),
1291
+ box-shadow var(--neba-duration) var(--neba-ease);
1292
+ }
1293
+
1294
+ .neba-notch-start {
1295
+ grid-column: 1;
1296
+ border-inline-start-width: 1px;
1297
+ border-start-start-radius: inherit;
1298
+ border-end-start-radius: inherit;
1299
+ }
1300
+
1301
+ .neba-notch-gap {
1302
+ grid-column: 2;
1303
+ border-top-color: var(--n-gap-line);
1304
+ box-shadow: inset 0 1px 0 var(--n-gap-top);
1305
+ }
1306
+
1307
+ .neba-notch-end {
1308
+ grid-column: 3;
1309
+ border-inline-end-width: 1px;
1310
+ border-start-end-radius: inherit;
1311
+ border-end-end-radius: inherit;
1312
+ }
1313
+
1314
+ /* The ring, drawn over the two pixels outside each piece. The radius is the
1315
+ corner's plus the ring's own width, so its inner edge meets the hairline. */
1316
+ .neba-notch-start::before,
1317
+ .neba-notch-gap::before,
1318
+ .neba-notch-end::before {
1319
+ content: '';
1320
+ position: absolute;
1321
+ inset-block: -3px;
1322
+ border: 0 solid transparent;
1323
+ transition: border-color var(--neba-duration) var(--neba-ease);
1324
+ }
1325
+
1326
+ .neba-notch-start::before {
1327
+ inset-inline: -3px 0;
1328
+ border-start-start-radius: calc(var(--n-notch-r) + 2px);
1329
+ border-end-start-radius: calc(var(--n-notch-r) + 2px);
1330
+ }
1331
+
1332
+ .neba-notch-gap::before {
1333
+ inset-inline: 0;
1334
+ }
1335
+
1336
+ .neba-notch-end::before {
1337
+ inset-inline: 0 -3px;
1338
+ border-start-end-radius: calc(var(--n-notch-r) + 2px);
1339
+ border-end-end-radius: calc(var(--n-notch-r) + 2px);
1340
+ }
1341
+
1342
+ /* Only the bottom of the gap: a field with the focus in it has its label on
1343
+ the edge, so the top of the gap is always open when the ring is drawn. */
1344
+ :has(:focus-visible) > .neba-notch > .neba-notch-start::before {
1345
+ border-color: var(--n-ring);
1346
+ border-block-width: 2px;
1347
+ border-inline-start-width: 2px;
1348
+ }
1349
+
1350
+ :has(:focus-visible) > .neba-notch > .neba-notch-gap::before {
1351
+ border-color: var(--n-ring);
1352
+ border-block-end-width: 2px;
1353
+ }
1354
+
1355
+ :has(:focus-visible) > .neba-notch > .neba-notch-end::before {
1356
+ border-color: var(--n-ring);
1357
+ border-block-width: 2px;
1358
+ border-inline-end-width: 2px;
1359
+ }
1360
+
1361
+ /*
1362
+ * The label, centred on the edge line. It moves on `top` and
1363
+ * `inset-inline-start` and changes size on `font-size`, never on a transform:
1364
+ * it is set again at every size on the way rather than scaled, so no frame of
1365
+ * it is a resampled picture of the word.
1366
+ *
1367
+ * A press goes straight through it to what is underneath. A resting label
1368
+ * covers most of the control, and a Select opens on the pointer going down on
1369
+ * its trigger, which a `<label>` in front of it would swallow; the input
1370
+ * under a field's label is where the caret belongs anyway.
1371
+ */
1372
+ .neba-notch-label {
1373
+ grid-area: 1 / 2;
1374
+ align-self: start;
1375
+ position: relative;
1376
+ top: var(--n-label-top);
1377
+ inset-inline-start: var(--n-label-x);
1378
+ min-width: 0;
1379
+ padding-inline: var(--n-notch-gap);
1380
+ overflow: hidden;
1381
+ white-space: nowrap;
1382
+ text-overflow: ellipsis;
1383
+ font-size: var(--n-label-size);
1384
+ line-height: var(--n-label-leading);
1385
+ transition-property: top, inset-inline-start, font-size, line-height, color;
1386
+ transition-duration: var(--neba-duration);
1387
+ transition-timing-function: var(--neba-ease);
1388
+ }
1389
+
1390
+ /*
1391
+ * A `float` label at rest: set in the control's own text, where the text would
1392
+ * start, with the gap closed over it. Three ways to know the field is empty,
1393
+ * one per kind of control — an input's `:placeholder-shown`, a trigger's
1394
+ * `data-placeholder`, and `data-empty` from a component that has no control to
1395
+ * ask — and the same rule for all three: nothing inside the holder has the
1396
+ * focus and nothing is open. Autofill counts as a value before the reader has
1397
+ * accepted it, because the text is already drawn where the label would be.
1398
+ */
1399
+ :not(:focus-within) > .neba-float-control:placeholder-shown:not(:autofill) ~ .neba-notch-float,
1400
+ :not(:focus-within)
1401
+ > .neba-float-control[data-placeholder]:not([data-popup-open])
1402
+ ~ .neba-notch-float,
1403
+ :not(:focus-within) > .neba-notch-float[data-empty] {
1404
+ --n-label-top: var(--n-rest-top);
1405
+ --n-label-x: var(--n-rest-x);
1406
+ --n-label-size: var(--n-rest-size);
1407
+ --n-label-leading: var(--n-rest-leading);
1408
+ --n-label-ink: var(--neba-muted-fg);
1409
+ --n-gap-line: var(--n-notch-line, transparent);
1410
+ --n-gap-top: var(--n-notch-top, transparent);
1411
+ }
1412
+
1413
+ /* And the placeholder it is standing in for, which comes back with the focus.
1414
+ Everything inside a trigger's placeholder as well as the placeholder itself:
1415
+ a DateRangePicker's is two words and an arrow, each with an ink of its own.
1416
+ Spelled out rather than as `:is([data-placeholder] *)`, which would also match
1417
+ the chevron — a Select's trigger carries `data-placeholder` itself. */
1418
+ .neba-float-control::placeholder,
1419
+ .neba-float-control [data-placeholder],
1420
+ .neba-float-control [data-placeholder] * {
1421
+ transition: color var(--neba-duration) var(--neba-ease);
1422
+ }
1423
+
1424
+ .neba-float-control:not(:focus)::placeholder,
1425
+ .neba-float-control:not(:focus):not([data-popup-open]) [data-placeholder],
1426
+ .neba-float-control:not(:focus):not([data-popup-open]) [data-placeholder] * {
1427
+ color: transparent;
1428
+ }
1429
+
1180
1430
  /* ---------------------------------------------------------------------------
1181
1431
  * Forced colours
1182
1432
  *
@@ -1200,6 +1450,13 @@
1200
1450
  outline-offset: -1px;
1201
1451
  }
1202
1452
 
1453
+ /* A forced palette paints a transparent line too, so a notched shell's own
1454
+ border and the closed top of the gap are both drawn through the label.
1455
+ The page under a forced palette is opaque, and so is this. */
1456
+ .neba-notch-label.neba-notch-label {
1457
+ background-color: Canvas;
1458
+ }
1459
+
1203
1460
  /* Behind `@supports` as well, which every browser that can be in a forced
1204
1461
  palette passes: a colour that cannot opt out of the palette would be
1205
1462
  replaced by it, and a fill the same colour as its track says nothing. */
package/dist/types.d.ts CHANGED
@@ -179,6 +179,24 @@ export type NebaVariant = 'solid' | 'outline' | 'text';
179
179
  * pressing removes one, so a flat control still answers a press without moving.
180
180
  */
181
181
  export type NebaElevation = 0 | 1 | 2 | 3;
182
+ /**
183
+ * Where a field-shaped component draws its label.
184
+ *
185
+ * - `top` — above the field, in a line of its own. The default.
186
+ * - `notch` — on the field's top edge, in a gap cut out of the border.
187
+ * - `float` — inside the field, where the placeholder would be, while the field
188
+ * is empty and nothing in it has the focus; it rises into the notch the
189
+ * moment either stops being true.
190
+ *
191
+ * `notch` and `float` take no height of their own. The label stands half a
192
+ * line above the field's top edge instead of a line above it, so a column of
193
+ * notched fields is shorter than the same column with their labels on top.
194
+ *
195
+ * A field that draws something at its start — a `startIcon`, the split
196
+ * steppers of a NumberField — keeps a `float` label in the notch, because the
197
+ * place it would rest is taken.
198
+ */
199
+ export type NebaLabelPlacement = 'top' | 'notch' | 'float';
182
200
  /**
183
201
  * The seven effects the `transition` prop takes, which is the **entrance**
184
202
  * vocabulary: everything a component can be given to arrive on.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neba",
3
- "version": "1.16.0",
3
+ "version": "1.16.2",
4
4
  "description": "A comprehensive React UI component library — buttons, fields, menus, dialogs, tables and much more — accessible and themeable, sharing one prop vocabulary and a translucent acrylic design language. ESM only, types included, dark mode built in.",
5
5
  "type": "module",
6
6
  "types": "dist/index.d.ts",
@@ -154,15 +154,15 @@
154
154
  "@a2ui/react": "^0.11.1",
155
155
  "@a2ui/web_core": "^0.11.0",
156
156
  "@eslint/js": "^9.39.1",
157
- "@mdn/browser-compat-data": "^8.1.1",
157
+ "@mdn/browser-compat-data": "^8.1.3",
158
158
  "@tailwindcss/postcss": "^4.3.3",
159
- "@types/node": "^26.4.1",
160
- "@types/react": "^19.2.18",
161
- "@types/react-dom": "^19.2.7",
162
- "@typescript-eslint/parser": "^8.69.0",
159
+ "@types/node": "^26.6.3",
160
+ "@types/react": "^19.3.0",
161
+ "@types/react-dom": "^19.3.0",
162
+ "@typescript-eslint/parser": "^8.70.1",
163
163
  "@vitejs/plugin-react": "^6.1.1",
164
- "@vitest/browser": "^5.0.0",
165
- "@vitest/browser-playwright": "^5.0.0",
164
+ "@vitest/browser": "^5.0.2",
165
+ "@vitest/browser-playwright": "^5.0.2",
166
166
  "eslint": "^9.39.1",
167
167
  "eslint-config-prettier": "^10.1.8",
168
168
  "eslint-plugin-n": "^18.3.0",
@@ -171,20 +171,20 @@
171
171
  "jiti": "^2.7.0",
172
172
  "playwright": "^1.63.0",
173
173
  "postcss": "^8.5.28",
174
- "prettier": "^3.9.6",
174
+ "prettier": "^3.9.9",
175
175
  "prettier-plugin-tailwindcss": "^0.8.1",
176
- "react": "^19.2.8",
177
- "react-dom": "^19.2.8",
176
+ "react": "^19.3.0",
177
+ "react-dom": "^19.3.0",
178
178
  "tailwindcss": "^4.3.3",
179
179
  "terser": "^5.51.2",
180
- "tsx": "^4.23.13",
180
+ "tsx": "^4.23.15",
181
181
  "typescript": "^6.0.3",
182
- "typescript-eslint": "^8.69.0",
183
- "vite": "^8.2.2",
182
+ "typescript-eslint": "^8.70.1",
183
+ "vite": "^8.3.1",
184
184
  "vitepress": "^1.6.4",
185
185
  "vitepress-i18n": "^1.4.0",
186
186
  "vitepress-sidebar": "^1.40.0",
187
- "vitest": "^5.0.0",
187
+ "vitest": "^5.0.2",
188
188
  "vitest-browser-react": "^2.3.0",
189
189
  "zod": "^3.25.76"
190
190
  },