myoperator-mcp 0.2.197 → 0.2.199

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 (2) hide show
  1. package/dist/index.js +170 -187
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1364,31 +1364,17 @@ ContactListItem.displayName = "ContactListItem";
1364
1364
  export { ContactListItem };
1365
1365
  `,
1366
1366
  "creatable-multi-select": `import * as React from "react"
1367
- import { cva } from "class-variance-authority"
1368
- import { ChevronDown, ChevronRight, Plus, X, Info } from "lucide-react"
1367
+ import { ChevronRight, Plus, Info } from "lucide-react"
1369
1368
 
1370
1369
  import { cn } from "@/lib/utils"
1370
+ import {
1371
+ creatableSelectTriggerVariants,
1372
+ creatableEnterHintKbdClassName,
1373
+ creatableToneHintRowClassName,
1374
+ } from "./creatable-select"
1371
1375
 
1372
- const creatableMultiSelectTriggerVariants = cva(
1373
- "flex items-center gap-2 flex-wrap min-h-[42px] w-full px-4 py-2 rounded bg-semantic-bg-primary cursor-text transition-shadow",
1374
- {
1375
- variants: {
1376
- state: {
1377
- default:
1378
- "border border-solid border-semantic-border-input hover:border-semantic-border-input-focus",
1379
- error:
1380
- "border border-solid border-semantic-error-primary/40 hover:border-semantic-error-primary",
1381
- focused:
1382
- "border border-solid border-semantic-border-focus shadow-[0_0_0_1px_rgba(43,188,202,0.15)]",
1383
- "focused-error":
1384
- "border border-solid border-semantic-error-primary/60 shadow-[0_0_0_1px_rgba(240,68,56,0.1)]",
1385
- },
1386
- },
1387
- defaultVariants: {
1388
- state: "default",
1389
- },
1390
- }
1391
- )
1376
+ /** @deprecated Use \`creatableSelectTriggerVariants\` from \`./creatable-select\` \u2014 aliases the same trigger styles as Primary Role. */
1377
+ const creatableMultiSelectTriggerVariants = creatableSelectTriggerVariants
1392
1378
 
1393
1379
  export interface CreatableMultiSelectOption {
1394
1380
  value: string
@@ -1421,6 +1407,11 @@ export interface CreatableMultiSelectProps
1421
1407
  maxItems?: number
1422
1408
  /** Max character length per item when typing/creating (default: unlimited) */
1423
1409
  maxLengthPerItem?: number
1410
+ /**
1411
+ * When true (default), shows \`current/max\` under the trigger while typing when \`maxLengthPerItem\` is set.
1412
+ * Set to false to match Figma (counter lives only in the field flow / not under the control).
1413
+ */
1414
+ showPerItemCharacterCounter?: boolean
1424
1415
  /**
1425
1416
  * When set, the text input is transformed (e.g. strip invalid characters).
1426
1417
  * If the raw value differs from the sanitized value, \`onInvalidCharacters\` is called.
@@ -1435,6 +1426,15 @@ export interface CreatableMultiSelectProps
1435
1426
  onValidInput?: () => void
1436
1427
  }
1437
1428
 
1429
+ function joinSelectedLabels(
1430
+ values: string[],
1431
+ options: CreatableMultiSelectOption[]
1432
+ ): string {
1433
+ return values
1434
+ .map((val) => options.find((o) => o.value === val)?.label ?? val)
1435
+ .join(", ")
1436
+ }
1437
+
1438
1438
  const CreatableMultiSelect = React.forwardRef(
1439
1439
  (
1440
1440
  {
@@ -1449,6 +1449,7 @@ const CreatableMultiSelect = React.forwardRef(
1449
1449
  createHintText,
1450
1450
  maxItems,
1451
1451
  maxLengthPerItem,
1452
+ showPerItemCharacterCounter = true,
1452
1453
  sanitizeInput,
1453
1454
  onInvalidCharacters,
1454
1455
  onValidInput,
@@ -1464,30 +1465,39 @@ const CreatableMultiSelect = React.forwardRef(
1464
1465
 
1465
1466
  React.useImperativeHandle(ref, () => containerRef.current!)
1466
1467
 
1467
- const addValue = React.useCallback(
1468
- (val: string) => {
1469
- const afterSanitize = sanitizeInput ? sanitizeInput(val) : val
1470
- const trimmed = afterSanitize.trim()
1471
- if (!trimmed || value.includes(trimmed)) return
1472
- if (maxItems != null && value.length >= maxItems) return
1473
- const toAdd =
1474
- maxLengthPerItem != null
1475
- ? trimmed.slice(0, maxLengthPerItem)
1476
- : trimmed
1477
- if (toAdd) {
1478
- onValueChange?.([...value, toAdd])
1479
- setInputValue("")
1480
- }
1481
- },
1482
- [value, onValueChange, maxItems, maxLengthPerItem, sanitizeInput]
1483
- )
1468
+ const derivedState = state === "error" ? "error" : "default"
1469
+
1470
+ const selectedSummary = joinSelectedLabels(value, options)
1471
+
1472
+ const addValue = (val: string) => {
1473
+ const afterSanitize = sanitizeInput ? sanitizeInput(val) : val
1474
+ const trimmed = afterSanitize.trim()
1475
+ if (!trimmed || value.includes(trimmed)) return
1476
+ if (maxItems != null && value.length >= maxItems) return
1477
+ const toAdd =
1478
+ maxLengthPerItem != null
1479
+ ? trimmed.slice(0, maxLengthPerItem)
1480
+ : trimmed
1481
+ if (toAdd) {
1482
+ onValueChange?.([...value, toAdd])
1483
+ setInputValue("")
1484
+ }
1485
+ }
1484
1486
 
1485
- const removeValue = React.useCallback(
1486
- (val: string) => {
1487
- onValueChange?.(value.filter((v) => v !== val))
1488
- },
1489
- [value, onValueChange]
1490
- )
1487
+ const removeValue = (val: string) => {
1488
+ onValueChange?.(value.filter((v) => v !== val))
1489
+ }
1490
+
1491
+ const handleOpen = React.useCallback(() => {
1492
+ if (disabled) return
1493
+ setIsOpen(true)
1494
+ setInputValue("")
1495
+ }, [disabled])
1496
+
1497
+ React.useEffect(() => {
1498
+ if (!isOpen) return
1499
+ requestAnimationFrame(() => inputRef.current?.focus())
1500
+ }, [isOpen])
1491
1501
 
1492
1502
  const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
1493
1503
  if (e.key === "Enter") {
@@ -1542,18 +1552,11 @@ const CreatableMultiSelect = React.forwardRef(
1542
1552
 
1543
1553
  const hasHintCopy = Boolean(createHintText) || maxItems != null
1544
1554
 
1545
- const showHintsRow =
1546
- hasHintCopy || canShowEnterAffordance
1555
+ const showHintsSection = hasHintCopy || canShowEnterAffordance
1547
1556
 
1548
- const triggerState = isOpen
1549
- ? state === "error"
1550
- ? "focused-error"
1551
- : "focused"
1552
- : state
1553
-
1554
- /** Must match \`CreatableSelect\` hint-row \`<kbd>\` (Primary Role) \u2014 includes \`font-sans\` to override UA monospace on \`<kbd>\`. */
1555
- const creatableHintEnterKbdClassName =
1556
- "inline-flex items-center gap-0.5 rounded border border-solid border-semantic-border-layout bg-semantic-bg-ui px-1.5 py-0.5 font-sans text-[10px] font-medium text-semantic-text-muted"
1557
+ const inputPlaceholder = isOpen
1558
+ ? selectedSummary || placeholder
1559
+ : ""
1557
1560
 
1558
1561
  return (
1559
1562
  <div
@@ -1561,146 +1564,107 @@ const CreatableMultiSelect = React.forwardRef(
1561
1564
  className={cn("relative w-full", className)}
1562
1565
  {...props}
1563
1566
  >
1564
- {/* Positioning context = trigger only so the dropdown aligns like CreatableSelect (counter sits below, not above the panel). */}
1565
1567
  <div className="relative w-full">
1566
- {/* Trigger */}
1567
- <div
1568
- className={cn(
1569
- creatableMultiSelectTriggerVariants({ state: triggerState }),
1570
- disabled && "cursor-not-allowed opacity-50"
1571
- )}
1572
- onClick={() => {
1573
- if (disabled) return
1574
- setIsOpen(true)
1575
- inputRef.current?.focus()
1576
- }}
1577
- >
1578
- {/* Selected chips */}
1579
- {value.map((val) => {
1580
- const optLabel =
1581
- options.find((o) => o.value === val)?.label || val
1582
- return (
1583
- <span
1584
- key={val}
1585
- className="inline-flex items-center gap-2 bg-semantic-info-surface px-2 py-1 rounded text-sm text-semantic-text-primary whitespace-nowrap"
1586
- >
1587
- {optLabel}
1588
- <button
1589
- type="button"
1590
- onMouseDown={(e) => {
1591
- e.stopPropagation()
1592
- e.preventDefault()
1593
- removeValue(val)
1594
- }}
1595
- className="shrink-0 flex items-center justify-center text-semantic-text-muted hover:text-semantic-text-primary transition-colors"
1596
- aria-label={\`Remove \${optLabel}\`}
1597
- >
1598
- <X className="size-2.5" />
1599
- </button>
1600
- </span>
1601
- )
1602
- })}
1603
-
1604
- {/* Text input */}
1605
- <input
1606
- ref={inputRef}
1607
- type="text"
1608
- value={inputValue}
1609
- onChange={(e) => {
1610
- const raw = e.target.value
1611
- const sanitized = sanitizeInput ? sanitizeInput(raw) : raw
1612
- if (sanitizeInput) {
1613
- if (raw !== sanitized) onInvalidCharacters?.()
1614
- else onValidInput?.()
1615
- }
1616
- setInputValue(
1617
- maxLengthPerItem != null
1618
- ? sanitized.slice(0, maxLengthPerItem)
1619
- : sanitized
1620
- )
1621
- if (!isOpen) setIsOpen(true)
1622
- }}
1623
- maxLength={maxLengthPerItem}
1624
- onFocus={() => {
1625
- if (!disabled) setIsOpen(true)
1626
- }}
1627
- onKeyDown={handleKeyDown}
1568
+ {isOpen ? (
1569
+ <div
1570
+ className={cn(
1571
+ creatableSelectTriggerVariants({ state: derivedState }),
1572
+ "cursor-text"
1573
+ )}
1574
+ onClick={() => inputRef.current?.focus()}
1575
+ >
1576
+ <input
1577
+ ref={inputRef}
1578
+ type="text"
1579
+ value={inputValue}
1580
+ onChange={(e) => {
1581
+ const raw = e.target.value
1582
+ const sanitized = sanitizeInput ? sanitizeInput(raw) : raw
1583
+ if (sanitizeInput) {
1584
+ if (raw !== sanitized) onInvalidCharacters?.()
1585
+ else onValidInput?.()
1586
+ }
1587
+ setInputValue(
1588
+ maxLengthPerItem != null
1589
+ ? sanitized.slice(0, maxLengthPerItem)
1590
+ : sanitized
1591
+ )
1592
+ }}
1593
+ maxLength={maxLengthPerItem}
1594
+ onKeyDown={handleKeyDown}
1595
+ disabled={disabled}
1596
+ placeholder={inputPlaceholder}
1597
+ className="flex-1 min-w-0 bg-transparent outline-none text-base text-semantic-text-primary placeholder:text-semantic-text-muted"
1598
+ role="combobox"
1599
+ aria-expanded={isOpen}
1600
+ aria-controls={listboxId}
1601
+ aria-haspopup="listbox"
1602
+ aria-autocomplete="list"
1603
+ />
1604
+ <ChevronRight
1605
+ className="size-5 text-semantic-text-muted shrink-0 opacity-70"
1606
+ aria-hidden
1607
+ />
1608
+ </div>
1609
+ ) : (
1610
+ <button
1611
+ type="button"
1628
1612
  disabled={disabled}
1629
- placeholder={value.length === 0 ? placeholder : ""}
1630
- className="flex-1 min-w-[100px] text-base bg-transparent outline-none text-semantic-text-primary placeholder:text-semantic-text-muted"
1631
- role="combobox"
1632
- aria-expanded={isOpen}
1633
- aria-controls={listboxId}
1613
+ onClick={handleOpen}
1614
+ className={cn(
1615
+ creatableSelectTriggerVariants({ state: derivedState }),
1616
+ "cursor-pointer text-left"
1617
+ )}
1634
1618
  aria-haspopup="listbox"
1635
- />
1636
-
1637
- {/* Chevron */}
1638
- {isOpen ? (
1639
- <ChevronRight className="size-5 text-semantic-text-muted shrink-0 ml-auto" />
1640
- ) : (
1641
- <ChevronDown className="size-5 text-semantic-text-muted shrink-0 ml-auto" />
1642
- )}
1643
- </div>
1619
+ aria-expanded={false}
1620
+ aria-controls={listboxId}
1621
+ >
1622
+ <span
1623
+ className={cn(
1624
+ "flex-1 min-w-0 line-clamp-2 text-base",
1625
+ !selectedSummary && "text-semantic-text-muted"
1626
+ )}
1627
+ >
1628
+ {selectedSummary || placeholder}
1629
+ </span>
1630
+ <ChevronRight
1631
+ className="size-5 text-semantic-text-muted shrink-0 opacity-70"
1632
+ aria-hidden
1633
+ />
1634
+ </button>
1635
+ )}
1644
1636
 
1645
- {/* Dropdown panel \u2014 top-full is bottom of trigger row only (matches Primary Role / CreatableSelect). */}
1637
+ {/* Dropdown panel */}
1646
1638
  {isOpen && (
1647
1639
  <div
1648
1640
  id={listboxId}
1649
1641
  role="listbox"
1650
- className="absolute left-0 top-full z-[9999] mt-1 w-full rounded border border-solid border-semantic-border-layout bg-semantic-bg-primary shadow-md animate-in fade-in-0 zoom-in-95 slide-in-from-top-2 duration-200"
1642
+ className="absolute left-0 top-full z-[9999] mt-1 flex w-full flex-col gap-2.5 overflow-hidden rounded border border-solid border-semantic-border-layout bg-semantic-bg-primary px-4 pb-4 pt-0 shadow-sm animate-in fade-in-0 zoom-in-95 slide-in-from-top-2 duration-200"
1651
1643
  >
1652
- {showHintsRow && (
1653
- <div
1654
- className={cn(
1655
- "flex items-center justify-between gap-2 px-4 py-2",
1656
- filteredPresets.length > 0 &&
1657
- "border-b border-solid border-semantic-border-layout",
1658
- !hasHintCopy && "justify-end"
1659
- )}
1660
- >
1661
- {hasHintCopy ? (
1662
- <div className="flex min-w-0 flex-1 flex-col gap-0.5">
1663
- {createHintText ? (
1664
- <span className="text-sm text-semantic-text-muted">
1665
- {createHintText}
1666
- </span>
1667
- ) : null}
1668
- {maxItems != null ? (
1669
- <span className="text-xs text-semantic-text-muted">
1670
- Max selections allowed: {maxItems}
1671
- </span>
1672
- ) : null}
1673
- </div>
1674
- ) : null}
1675
- <span
1676
- role="button"
1677
- tabIndex={canShowEnterAffordance ? 0 : -1}
1678
- aria-label="Add using Enter key"
1679
- aria-disabled={!canShowEnterAffordance}
1680
- className={cn(
1681
- "inline-flex shrink-0",
1682
- !canShowEnterAffordance && "pointer-events-none"
1644
+ {showHintsSection && (
1645
+ <>
1646
+ <div className={creatableToneHintRowClassName}>
1647
+ {createHintText ? (
1648
+ <span className="text-sm text-semantic-text-muted">
1649
+ {createHintText}
1650
+ </span>
1651
+ ) : (
1652
+ <span className="min-w-0 flex-1" />
1683
1653
  )}
1684
- onMouseDown={(e) => {
1685
- e.preventDefault()
1686
- if (canShowEnterAffordance) addValue(inputValue)
1687
- }}
1688
- onKeyDown={(e) => {
1689
- if (e.key !== "Enter" && e.key !== " ") return
1690
- e.preventDefault()
1691
- if (canShowEnterAffordance) addValue(inputValue)
1692
- }}
1693
- >
1694
- <kbd className={creatableHintEnterKbdClassName}>
1654
+ <kbd className={creatableEnterHintKbdClassName}>
1695
1655
  Enter \u21B5
1696
1656
  </kbd>
1697
- </span>
1698
- </div>
1657
+ </div>
1658
+ {maxItems != null ? (
1659
+ <p className="m-0 text-left text-sm text-semantic-text-muted">
1660
+ Max selections allowed: {maxItems}
1661
+ </p>
1662
+ ) : null}
1663
+ </>
1699
1664
  )}
1700
1665
 
1701
- {/* Preset option chips \u2014 pt-1 px-4 matches CreatableSelect listbox padding above first option */}
1702
1666
  {filteredPresets.length > 0 && (
1703
- <div className="flex flex-wrap gap-1.5 px-4 pt-1 pb-2">
1667
+ <div className="flex flex-wrap gap-1.5">
1704
1668
  {filteredPresets.map((option) => (
1705
1669
  <button
1706
1670
  key={option.value}
@@ -1709,9 +1673,13 @@ const CreatableMultiSelect = React.forwardRef(
1709
1673
  e.preventDefault()
1710
1674
  addValue(option.value)
1711
1675
  }}
1712
- className="inline-flex items-center gap-1.5 whitespace-nowrap rounded bg-semantic-bg-ui px-2.5 py-1.5 text-sm text-semantic-text-primary transition-colors hover:bg-semantic-bg-hover"
1676
+ className="inline-flex items-center gap-2.5 whitespace-nowrap rounded border-0 bg-semantic-bg-ui px-2 py-1 text-sm text-semantic-text-primary transition-colors hover:bg-semantic-bg-hover"
1713
1677
  >
1714
- <Plus className="size-3 shrink-0 text-semantic-text-muted" />
1678
+ <Plus
1679
+ className="size-2.5 shrink-0 text-semantic-text-muted"
1680
+ strokeWidth={2}
1681
+ aria-hidden
1682
+ />
1715
1683
  {option.label}
1716
1684
  </button>
1717
1685
  ))}
@@ -1721,8 +1689,9 @@ const CreatableMultiSelect = React.forwardRef(
1721
1689
  )}
1722
1690
  </div>
1723
1691
 
1724
- {/* Helper row below trigger: when maxLengthPerItem show dynamic hint + counter (Figma); else optional static helperText */}
1725
- {maxLengthPerItem != null ? (
1692
+ {maxLengthPerItem != null &&
1693
+ showPerItemCharacterCounter &&
1694
+ isOpen ? (
1726
1695
  <div className="mt-1.5 flex items-center justify-end gap-2">
1727
1696
  <span className="shrink-0 text-sm text-semantic-text-muted">
1728
1697
  {inputValue.length}/{maxLengthPerItem}
@@ -1770,6 +1739,20 @@ const creatableSelectTriggerVariants = cva(
1770
1739
  }
1771
1740
  )
1772
1741
 
1742
+ /**
1743
+ * Tailwind classes for the "Enter \u21B5" hint in creatable dropdown headers (shared by Primary Role and Tone).
1744
+ */
1745
+ export const creatableEnterHintKbdClassName =
1746
+ "inline-flex items-center gap-0.5 rounded border border-solid border-semantic-border-layout bg-semantic-bg-ui px-1.5 py-0.5 font-sans text-[10px] font-medium text-semantic-text-muted"
1747
+
1748
+ /** Primary Role: hint row above the options list (custom role + Enter kbd). */
1749
+ export const creatablePrimaryRoleHintRowClassName =
1750
+ "flex items-center justify-between px-4 py-2 border-b border-solid border-semantic-border-layout"
1751
+
1752
+ /** Tone / CreatableMultiSelect: hint row inside the padded dropdown panel (-mx-4 full-bleed border). */
1753
+ export const creatableToneHintRowClassName =
1754
+ "-mx-4 flex min-h-[45px] shrink-0 items-center justify-between gap-2.5 border-b border-solid border-semantic-border-layout px-4 py-2.5"
1755
+
1773
1756
  export interface CreatableSelectOption {
1774
1757
  value: string
1775
1758
  label: string
@@ -2039,11 +2022,11 @@ const CreatableSelect = React.forwardRef(
2039
2022
  {open && (
2040
2023
  <div className="absolute left-0 top-full z-[9999] mt-1 w-full rounded border border-solid border-semantic-border-layout bg-semantic-bg-primary shadow-md animate-in fade-in-0 zoom-in-95 slide-in-from-top-2 duration-200">
2041
2024
  {creatableHint ? (
2042
- <div className="flex items-center justify-between px-4 py-2 border-b border-solid border-semantic-border-layout">
2025
+ <div className={creatablePrimaryRoleHintRowClassName}>
2043
2026
  <span className="text-sm text-semantic-text-muted">
2044
2027
  {creatableHint}
2045
2028
  </span>
2046
- <kbd className="inline-flex items-center gap-0.5 rounded border border-solid border-semantic-border-layout bg-semantic-bg-ui px-1.5 py-0.5 font-sans text-[10px] font-medium text-semantic-text-muted">
2029
+ <kbd className={creatableEnterHintKbdClassName}>
2047
2030
  Enter \u21B5
2048
2031
  </kbd>
2049
2032
  </div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myoperator-mcp",
3
- "version": "0.2.197",
3
+ "version": "0.2.199",
4
4
  "description": "MCP server for myOperator UI components - enables AI assistants to access component metadata, examples, and design tokens",
5
5
  "type": "module",
6
6
  "bin": "./dist/index.js",