@discuss-ds/ui 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -338,6 +338,17 @@ interface InlineEditProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'on
338
338
  disabled?: boolean;
339
339
  /** Called when editing starts or ends. */
340
340
  onEditingChange?: (editing: boolean) => void;
341
+ /** Called on every keystroke while editing (and with the saved value when editing ends), e.g. for a live character counter. */
342
+ onDraftChange?: (value: string) => void;
343
+ /**
344
+ * `default` is a rounded field. A short single-line label shows the pencil and hugs its text; `multiline` (longer
345
+ * text) hides the pencil and fills the width. `question` is for question text: it fills the width with no pencil,
346
+ * has square corners and shows a bottom border on hover, focus and while `selected`. Enter saves; Shift+Enter adds a
347
+ * line break when `multiline`.
348
+ */
349
+ variant?: 'default' | 'question';
350
+ /** `question` variant: keeps the bottom border while the owning question is selected. */
351
+ selected?: boolean;
341
352
  /** Classes for the displayed/edited text, e.g. a heading text style. The field itself keeps the same box. */
342
353
  textClassName?: string;
343
354
  }
package/dist/index.d.ts CHANGED
@@ -338,6 +338,17 @@ interface InlineEditProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'on
338
338
  disabled?: boolean;
339
339
  /** Called when editing starts or ends. */
340
340
  onEditingChange?: (editing: boolean) => void;
341
+ /** Called on every keystroke while editing (and with the saved value when editing ends), e.g. for a live character counter. */
342
+ onDraftChange?: (value: string) => void;
343
+ /**
344
+ * `default` is a rounded field. A short single-line label shows the pencil and hugs its text; `multiline` (longer
345
+ * text) hides the pencil and fills the width. `question` is for question text: it fills the width with no pencil,
346
+ * has square corners and shows a bottom border on hover, focus and while `selected`. Enter saves; Shift+Enter adds a
347
+ * line break when `multiline`.
348
+ */
349
+ variant?: 'default' | 'question';
350
+ /** `question` variant: keeps the bottom border while the owning question is selected. */
351
+ selected?: boolean;
341
352
  /** Classes for the displayed/edited text, e.g. a heading text style. The field itself keeps the same box. */
342
353
  textClassName?: string;
343
354
  }
package/dist/index.js CHANGED
@@ -1378,6 +1378,9 @@ var InlineEdit = React24.forwardRef(
1378
1378
  maxLength,
1379
1379
  disabled,
1380
1380
  onEditingChange,
1381
+ onDraftChange,
1382
+ variant = "default",
1383
+ selected,
1381
1384
  "aria-label": ariaLabel,
1382
1385
  ...props
1383
1386
  }, ref) => {
@@ -1413,26 +1416,32 @@ var InlineEdit = React24.forwardRef(
1413
1416
  }
1414
1417
  setEditing(false);
1415
1418
  onEditingChange?.(false);
1419
+ onDraftChange?.(commit ? draft : value);
1416
1420
  };
1417
1421
  const textClasses = cn("text-ui-input-value text-surface-foreground", textClassName);
1418
- return /* @__PURE__ */ jsx25("div", { ref, className: cn("inline-flex min-w-0 max-w-full", className), ...props, children: editing ? React24.createElement(multiline ? "textarea" : "input", {
1422
+ const question = variant === "question";
1423
+ const fill = multiline || question;
1424
+ return /* @__PURE__ */ jsx25("div", { ref, className: cn(fill ? "flex w-full min-w-0" : "inline-flex min-w-0 max-w-full", className), ...props, children: editing ? React24.createElement(multiline ? "textarea" : "input", {
1419
1425
  ref: fieldRef,
1420
1426
  value: draft,
1421
1427
  maxLength,
1422
1428
  "aria-label": ariaLabel,
1423
1429
  ...multiline ? { rows: 1 } : { type: "text" },
1424
1430
  className: cn(
1425
- "w-full rounded-md px-3 py-1 outline-none focus-visible:border-input-accent",
1431
+ "w-full px-3 py-1 outline-none",
1426
1432
  multiline && "field-sizing-content resize-none",
1427
- fieldSurfaceClasses("default"),
1433
+ question ? "rounded-none border-0 border-b-2 border-input-accent bg-transparent" : cn("rounded-md focus-visible:border-input-accent", fieldSurfaceClasses("default")),
1428
1434
  textClasses
1429
1435
  ),
1430
- onChange: (event) => setDraft(event.target.value),
1436
+ onChange: (event) => {
1437
+ setDraft(event.target.value);
1438
+ onDraftChange?.(event.target.value);
1439
+ },
1431
1440
  onKeyDown: (event) => {
1432
1441
  if (event.key === "Escape") {
1433
1442
  cancelled.current = true;
1434
1443
  finish(false, true);
1435
- } else if (event.key === "Enter" && (!multiline || event.metaKey || event.ctrlKey)) {
1444
+ } else if (event.key === "Enter" && (!multiline || event.metaKey || event.ctrlKey || question && !event.shiftKey)) {
1436
1445
  event.preventDefault();
1437
1446
  finish(true, true);
1438
1447
  }
@@ -1449,13 +1458,16 @@ var InlineEdit = React24.forwardRef(
1449
1458
  "aria-label": `${ariaLabel}: ${value || placeholder}. Edit`,
1450
1459
  onClick: start,
1451
1460
  className: cn(
1452
- "group inline-flex min-w-0 max-w-full items-center gap-2 rounded-md border border-transparent px-3 py-1 text-left outline-none",
1461
+ "group inline-flex min-w-0 max-w-full items-center gap-2 px-3 py-1 text-left outline-none",
1462
+ fill && "w-full",
1463
+ question ? cn("rounded-none border-b-2", selected ? "border-input-accent" : "border-transparent") : "rounded-md border border-transparent",
1453
1464
  "enabled:hover:bg-secondary enabled:focus-visible:border-input-accent disabled:cursor-not-allowed disabled:opacity-50",
1465
+ question && "enabled:hover:border-input-accent",
1454
1466
  textClasses
1455
1467
  ),
1456
1468
  children: [
1457
- /* @__PURE__ */ jsx25("span", { className: cn("min-w-0 truncate", multiline && "whitespace-pre-wrap", !value && "text-muted-foreground"), children: value || placeholder }),
1458
- /* @__PURE__ */ jsx25(
1469
+ /* @__PURE__ */ jsx25("span", { className: cn("min-w-0", multiline ? "whitespace-pre-wrap" : "truncate", fill && "flex-1", !value && "text-muted-foreground"), children: value || placeholder }),
1470
+ !fill && /* @__PURE__ */ jsx25(
1459
1471
  PencilSimple,
1460
1472
  {
1461
1473
  size: 16,
@@ -2489,10 +2501,12 @@ import { jsx as jsx41, jsxs as jsxs28 } from "react/jsx-runtime";
2489
2501
  var eyebrow = "text-ui-caption uppercase text-secondary-muted-foreground";
2490
2502
  var QuestionHeader = React40.forwardRef(
2491
2503
  ({ className, number, title, required, error, onDismissError, editor, onTitleChange, actions, maxLength = 2e3, active, showCounter = true, ...props }, ref) => {
2504
+ const [draft, setDraft] = React40.useState(null);
2492
2505
  const label = String(number).padStart(2, "0");
2493
2506
  const showError = error !== void 0 && error !== false;
2494
2507
  if (editor) {
2495
2508
  const text = typeof title === "string" ? title : "";
2509
+ const count = (draft ?? text).length;
2496
2510
  return /* @__PURE__ */ jsxs28("div", { ref, className: cn("group/header flex w-full flex-col gap-2", className), ...props, children: [
2497
2511
  /* @__PURE__ */ jsxs28("div", { className: "flex items-center justify-between", children: [
2498
2512
  /* @__PURE__ */ jsxs28(
@@ -2512,18 +2526,26 @@ var QuestionHeader = React40.forwardRef(
2512
2526
  actions && /* @__PURE__ */ jsx41("div", { className: "flex items-center gap-2 opacity-0 transition-opacity group-focus-within/header:opacity-100 group-hover/header:opacity-100", children: actions })
2513
2527
  ] }),
2514
2528
  /* @__PURE__ */ jsx41(
2515
- "textarea",
2529
+ InlineEdit,
2516
2530
  {
2517
- rows: 1,
2531
+ variant: "question",
2532
+ multiline: true,
2533
+ selected: active,
2534
+ className: "-mx-3 w-auto",
2518
2535
  "aria-label": "Question title",
2536
+ placeholder: "Click to write the question text",
2519
2537
  value: text,
2520
2538
  maxLength,
2521
- onChange: (event) => onTitleChange?.(event.target.value),
2522
- className: "field-sizing-content w-full resize-none bg-transparent text-heading-h4 text-surface-foreground outline-none"
2539
+ onValueChange: onTitleChange,
2540
+ onDraftChange: setDraft,
2541
+ onEditingChange: (editing) => {
2542
+ if (!editing) setDraft(null);
2543
+ },
2544
+ textClassName: "text-heading-h4"
2523
2545
  }
2524
2546
  ),
2525
2547
  showCounter && /* @__PURE__ */ jsxs28("p", { className: "text-ui-caption text-secondary-muted-foreground", children: [
2526
- text.length,
2548
+ count,
2527
2549
  "/",
2528
2550
  maxLength,
2529
2551
  " characters"