diff --git a/.github/workflows/publish-canary-release.yaml b/.github/workflows/publish-canary-release.yaml index b046cda1..07c40e58 100644 --- a/.github/workflows/publish-canary-release.yaml +++ b/.github/workflows/publish-canary-release.yaml @@ -3,11 +3,28 @@ name: Publish canary release on: workflow_dispatch: inputs: + package: + description: "Which package(s) to release" + type: choice + options: [filters, action-menu, both] + default: both + required: true + bump_type: + description: "Bump type" + type: choice + options: [patch, minor, major] + default: patch + required: true + npm_tag: + description: "npm dist-tag" + type: choice + options: [canary, latest] + default: canary + required: true dry_run: - description: 'Run in dry-run mode (no actual publishing or git commits)' - required: false - default: false + description: "Run in dry-run mode (no publish or git push)" type: boolean + default: false pull_request: branches: [canary] @@ -16,6 +33,19 @@ jobs: permissions: contents: write runs-on: ubuntu-24.04 + # serialize matrix to avoid concurrent commits/tags racing + strategy: + max-parallel: 1 + matrix: + package: [filters, action-menu] + + # Skip matrix rows not selected by input + if: > + github.event_name == 'pull_request' || + inputs.package == 'both' || + (inputs.package == 'filters' && matrix.package == 'filters') || + (inputs.package == 'action-menu' && matrix.package == 'action-menu') + steps: - name: Checkout uses: actions/checkout@v4 @@ -25,7 +55,7 @@ jobs: - name: Configure Git run: | - git config user.name "${{ github.actor }}" + git config user.name "${{ github.actor }}" git config user.email "${{ github.actor }}@users.noreply.github.com" - name: Setup Bun @@ -47,24 +77,46 @@ jobs: env: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - - name: Build and publish + - name: Build & release ${{ matrix.package }} + working-directory: packages/${{ matrix.package }} env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} run: | - cd packages/filters + # build (your publish.ts runs tests again as well) bun run build - if [[ "${{ github.event_name }}" == "pull_request" ]] || [[ "${{ inputs.dry_run }}" == "true" ]]; then - bun run release:patch:canary --no-git-tag --dry-run - else - bun run release:patch:canary --no-git-tag + + # Decide flags based on event + dry_run + EXTRA_FLAGS="--no-git-tag --tag ${{ inputs.npm_tag }}" + if [[ "${{ github.event_name }}" == "pull_request" || "${{ inputs.dry_run }}" == "true" ]]; then + EXTRA_FLAGS="$EXTRA_FLAGS --dry-run" fi - - name: Commit version changes + # Call your release script directly (uses your custom versioning) + bun scripts/release.ts --bump ${{ inputs.bump_type }} --path . $EXTRA_FLAGS + + - name: Commit version bump for ${{ matrix.package }} + if: github.event_name != 'pull_request' && !inputs.dry_run + working-directory: packages/${{ matrix.package }} + run: | + PKG_NAME=$(jq -r '.name' package.json) + NEXT_VERSION=$(jq -r '.version' package.json) + + git add package.json + git commit -m "release(${PKG_NAME}): v${NEXT_VERSION}" + + - name: Create per-package tag for ${{ matrix.package }} + if: github.event_name != 'pull_request' && !inputs.dry_run + working-directory: packages/${{ matrix.package }} + run: | + PKG_NAME=$(jq -r '.name' package.json) + NEXT_VERSION=$(jq -r '.version' package.json) + # Sanitize tag so it doesn't contain '/' + TAG_NAME="${PKG_NAME}@${NEXT_VERSION}" + + git tag "$TAG_NAME" + git push origin "$TAG_NAME" + + - name: Push branch (after commit) if: github.event_name != 'pull_request' && !inputs.dry_run run: | - NEXT_VERSION="v$(cat packages/filters/package.json | jq ".version" -r)" - git add packages/filters/package.json - git commit --amend --no-edit --no-verify - git tag $NEXT_VERSION - git push origin HEAD --force-with-lease --no-verify - git push origin $NEXT_VERSION --no-verify + git push origin HEAD diff --git a/.prototools b/.prototools index efced021..7e756e0d 100644 --- a/.prototools +++ b/.prototools @@ -1,4 +1,4 @@ -bun = "1.2.4" +bun = "1.2.21" node = "~22" [settings] diff --git a/apps/web/.types/types-meta.json b/apps/web/.types/types-meta.json new file mode 100644 index 00000000..a12227ab --- /dev/null +++ b/apps/web/.types/types-meta.json @@ -0,0 +1,3721 @@ +{ + "@bazza-ui/action-menu": { + "entrypoint": "../../packages/action-menu/src/index.ts", + "types": { + "ActionMenuProps": { + "name": "ActionMenuProps", + "kind": "interface", + "props": [ + { + "name": "open", + "type": "boolean | undefined", + "required": false + }, + { + "name": "defaultOpen", + "type": "boolean | undefined", + "required": false + }, + { + "name": "onOpenChange", + "type": "((open: boolean) => void) | undefined", + "required": false + }, + { + "name": "modal", + "type": "boolean | undefined", + "required": false + }, + { + "name": "responsive", + "type": "Partial | undefined", + "required": false + }, + { + "name": "shellClassNames", + "type": "Partial | undefined", + "required": false, + "description": "Drawer/overlay styles" + }, + { + "name": "shellSlotProps", + "type": "Partial | undefined", + "required": false, + "description": "Drawer-specific slot props" + }, + { + "name": "debug", + "type": "boolean | undefined", + "required": false + }, + { + "name": "children", + "type": "ReactNode", + "required": false + } + ] + }, + "ActionMenuTriggerProps": { + "name": "ActionMenuTriggerProps", + "kind": "interface", + "props": [ + { + "name": "form", + "type": "string | undefined", + "required": false + }, + { + "name": "slot", + "type": "string | undefined", + "required": false + }, + { + "name": "style", + "type": "CSSProperties | undefined", + "required": false + }, + { + "name": "title", + "type": "string | undefined", + "required": false + }, + { + "name": "hidden", + "type": "boolean | undefined", + "required": false + }, + { + "name": "id", + "type": "string | undefined", + "required": false + }, + { + "name": "onSelect", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "key", + "type": "Key | null | undefined", + "required": false + }, + { + "name": "defaultChecked", + "type": "boolean | undefined", + "required": false + }, + { + "name": "defaultValue", + "type": "string | number | readonly string[] | undefined", + "required": false + }, + { + "name": "suppressContentEditableWarning", + "type": "boolean | undefined", + "required": false + }, + { + "name": "suppressHydrationWarning", + "type": "boolean | undefined", + "required": false + }, + { + "name": "accessKey", + "type": "string | undefined", + "required": false + }, + { + "name": "autoCapitalize", + "type": "\"off\" | \"none\" | \"on\" | \"sentences\" | \"words\" | \"characters\" | (string & {}) | undefined", + "required": false + }, + { + "name": "autoFocus", + "type": "boolean | undefined", + "required": false + }, + { + "name": "className", + "type": "string | undefined", + "required": false + }, + { + "name": "contentEditable", + "type": "Booleanish | \"inherit\" | \"plaintext-only\" | undefined", + "required": false + }, + { + "name": "contextMenu", + "type": "string | undefined", + "required": false + }, + { + "name": "dir", + "type": "string | undefined", + "required": false + }, + { + "name": "draggable", + "type": "Booleanish | undefined", + "required": false + }, + { + "name": "enterKeyHint", + "type": "\"search\" | \"enter\" | \"done\" | \"go\" | \"next\" | \"previous\" | \"send\" | undefined", + "required": false + }, + { + "name": "lang", + "type": "string | undefined", + "required": false + }, + { + "name": "nonce", + "type": "string | undefined", + "required": false + }, + { + "name": "spellCheck", + "type": "Booleanish | undefined", + "required": false + }, + { + "name": "tabIndex", + "type": "number | undefined", + "required": false + }, + { + "name": "translate", + "type": "\"yes\" | \"no\" | undefined", + "required": false + }, + { + "name": "radioGroup", + "type": "string | undefined", + "required": false + }, + { + "name": "role", + "type": "AriaRole | undefined", + "required": false + }, + { + "name": "about", + "type": "string | undefined", + "required": false + }, + { + "name": "content", + "type": "string | undefined", + "required": false + }, + { + "name": "datatype", + "type": "string | undefined", + "required": false + }, + { + "name": "inlist", + "type": "any", + "required": false + }, + { + "name": "prefix", + "type": "string | undefined", + "required": false + }, + { + "name": "property", + "type": "string | undefined", + "required": false + }, + { + "name": "rel", + "type": "string | undefined", + "required": false + }, + { + "name": "resource", + "type": "string | undefined", + "required": false + }, + { + "name": "rev", + "type": "string | undefined", + "required": false + }, + { + "name": "typeof", + "type": "string | undefined", + "required": false + }, + { + "name": "vocab", + "type": "string | undefined", + "required": false + }, + { + "name": "autoCorrect", + "type": "string | undefined", + "required": false + }, + { + "name": "autoSave", + "type": "string | undefined", + "required": false + }, + { + "name": "color", + "type": "string | undefined", + "required": false + }, + { + "name": "itemProp", + "type": "string | undefined", + "required": false + }, + { + "name": "itemScope", + "type": "boolean | undefined", + "required": false + }, + { + "name": "itemType", + "type": "string | undefined", + "required": false + }, + { + "name": "itemID", + "type": "string | undefined", + "required": false + }, + { + "name": "itemRef", + "type": "string | undefined", + "required": false + }, + { + "name": "results", + "type": "number | undefined", + "required": false + }, + { + "name": "security", + "type": "string | undefined", + "required": false + }, + { + "name": "unselectable", + "type": "\"off\" | \"on\" | undefined", + "required": false + }, + { + "name": "popover", + "type": "\"\" | \"auto\" | \"manual\" | undefined", + "required": false + }, + { + "name": "popoverTargetAction", + "type": "\"toggle\" | \"show\" | \"hide\" | undefined", + "required": false + }, + { + "name": "popoverTarget", + "type": "string | undefined", + "required": false + }, + { + "name": "inert", + "type": "boolean | undefined", + "required": false + }, + { + "name": "inputMode", + "type": "\"search\" | \"text\" | \"none\" | \"tel\" | \"url\" | \"email\" | \"numeric\" | \"decimal\" | undefined", + "required": false, + "description": "Hints at the type of data that might be entered by the user while editing the element or its contents" + }, + { + "name": "is", + "type": "string | undefined", + "required": false, + "description": "Specify that a standard HTML element should behave like a defined custom built-in element" + }, + { + "name": "exportparts", + "type": "string | undefined", + "required": false + }, + { + "name": "part", + "type": "string | undefined", + "required": false + }, + { + "name": "aria-activedescendant", + "type": "string | undefined", + "required": false, + "description": "Identifies the currently active element when DOM focus is on a composite widget, textbox, group, or application." + }, + { + "name": "aria-atomic", + "type": "Booleanish | undefined", + "required": false, + "description": "Indicates whether assistive technologies will present all, or only parts of, the changed region based on the change notifications defined by the aria-relevant attribute." + }, + { + "name": "aria-autocomplete", + "type": "\"none\" | \"list\" | \"inline\" | \"both\" | undefined", + "required": false, + "description": "Indicates whether inputting text could trigger display of one or more predictions of the user's intended value for an input and specifies how predictions would be\npresented if they are made." + }, + { + "name": "aria-braillelabel", + "type": "string | undefined", + "required": false, + "description": "Defines a string value that labels the current element, which is intended to be converted into Braille." + }, + { + "name": "aria-brailleroledescription", + "type": "string | undefined", + "required": false, + "description": "Defines a human-readable, author-localized abbreviated description for the role of an element, which is intended to be converted into Braille." + }, + { + "name": "aria-busy", + "type": "Booleanish | undefined", + "required": false + }, + { + "name": "aria-checked", + "type": "boolean | \"true\" | \"false\" | \"mixed\" | undefined", + "required": false, + "description": "Indicates the current \"checked\" state of checkboxes, radio buttons, and other widgets." + }, + { + "name": "aria-colcount", + "type": "number | undefined", + "required": false, + "description": "Defines the total number of columns in a table, grid, or treegrid." + }, + { + "name": "aria-colindex", + "type": "number | undefined", + "required": false, + "description": "Defines an element's column index or position with respect to the total number of columns within a table, grid, or treegrid." + }, + { + "name": "aria-colindextext", + "type": "string | undefined", + "required": false, + "description": "Defines a human readable text alternative of aria-colindex." + }, + { + "name": "aria-colspan", + "type": "number | undefined", + "required": false, + "description": "Defines the number of columns spanned by a cell or gridcell within a table, grid, or treegrid." + }, + { + "name": "aria-controls", + "type": "string | undefined", + "required": false, + "description": "Identifies the element (or elements) whose contents or presence are controlled by the current element." + }, + { + "name": "aria-current", + "type": "boolean | \"time\" | \"true\" | \"false\" | \"date\" | \"page\" | \"step\" | \"location\" | undefined", + "required": false, + "description": "Indicates the element that represents the current item within a container or set of related elements." + }, + { + "name": "aria-describedby", + "type": "string | undefined", + "required": false, + "description": "Identifies the element (or elements) that describes the object." + }, + { + "name": "aria-description", + "type": "string | undefined", + "required": false, + "description": "Defines a string value that describes or annotates the current element." + }, + { + "name": "aria-details", + "type": "string | undefined", + "required": false, + "description": "Identifies the element that provides a detailed, extended description for the object." + }, + { + "name": "aria-disabled", + "type": "Booleanish | undefined", + "required": false, + "description": "Indicates that the element is perceivable but disabled, so it is not editable or otherwise operable." + }, + { + "name": "aria-dropeffect", + "type": "\"link\" | \"none\" | \"copy\" | \"execute\" | \"move\" | \"popup\" | undefined", + "required": false, + "description": "Indicates what functions can be performed when a dragged object is released on the drop target." + }, + { + "name": "aria-errormessage", + "type": "string | undefined", + "required": false, + "description": "Identifies the element that provides an error message for the object." + }, + { + "name": "aria-expanded", + "type": "Booleanish | undefined", + "required": false, + "description": "Indicates whether the element, or another grouping element it controls, is currently expanded or collapsed." + }, + { + "name": "aria-flowto", + "type": "string | undefined", + "required": false, + "description": "Identifies the next element (or elements) in an alternate reading order of content which, at the user's discretion,\nallows assistive technology to override the general default of reading in document source order." + }, + { + "name": "aria-grabbed", + "type": "Booleanish | undefined", + "required": false, + "description": "Indicates an element's \"grabbed\" state in a drag-and-drop operation." + }, + { + "name": "aria-haspopup", + "type": "boolean | \"dialog\" | \"menu\" | \"true\" | \"false\" | \"grid\" | \"listbox\" | \"tree\" | undefined", + "required": false, + "description": "Indicates the availability and type of interactive popup element, such as menu or dialog, that can be triggered by an element." + }, + { + "name": "aria-hidden", + "type": "Booleanish | undefined", + "required": false, + "description": "Indicates whether the element is exposed to an accessibility API." + }, + { + "name": "aria-invalid", + "type": "boolean | \"true\" | \"false\" | \"grammar\" | \"spelling\" | undefined", + "required": false, + "description": "Indicates the entered value does not conform to the format expected by the application." + }, + { + "name": "aria-keyshortcuts", + "type": "string | undefined", + "required": false, + "description": "Indicates keyboard shortcuts that an author has implemented to activate or give focus to an element." + }, + { + "name": "aria-label", + "type": "string | undefined", + "required": false, + "description": "Defines a string value that labels the current element." + }, + { + "name": "aria-labelledby", + "type": "string | undefined", + "required": false, + "description": "Identifies the element (or elements) that labels the current element." + }, + { + "name": "aria-level", + "type": "number | undefined", + "required": false, + "description": "Defines the hierarchical level of an element within a structure." + }, + { + "name": "aria-live", + "type": "\"off\" | \"assertive\" | \"polite\" | undefined", + "required": false, + "description": "Indicates that an element will be updated, and describes the types of updates the user agents, assistive technologies, and user can expect from the live region." + }, + { + "name": "aria-modal", + "type": "Booleanish | undefined", + "required": false, + "description": "Indicates whether an element is modal when displayed." + }, + { + "name": "aria-multiline", + "type": "Booleanish | undefined", + "required": false, + "description": "Indicates whether a text box accepts multiple lines of input or only a single line." + }, + { + "name": "aria-multiselectable", + "type": "Booleanish | undefined", + "required": false, + "description": "Indicates that the user may select more than one item from the current selectable descendants." + }, + { + "name": "aria-orientation", + "type": "\"horizontal\" | \"vertical\" | undefined", + "required": false, + "description": "Indicates whether the element's orientation is horizontal, vertical, or unknown/ambiguous." + }, + { + "name": "aria-owns", + "type": "string | undefined", + "required": false, + "description": "Identifies an element (or elements) in order to define a visual, functional, or contextual parent/child relationship\nbetween DOM elements where the DOM hierarchy cannot be used to represent the relationship." + }, + { + "name": "aria-placeholder", + "type": "string | undefined", + "required": false, + "description": "Defines a short hint (a word or short phrase) intended to aid the user with data entry when the control has no value.\nA hint could be a sample value or a brief description of the expected format." + }, + { + "name": "aria-posinset", + "type": "number | undefined", + "required": false, + "description": "Defines an element's number or position in the current set of listitems or treeitems. Not required if all elements in the set are present in the DOM." + }, + { + "name": "aria-pressed", + "type": "boolean | \"true\" | \"false\" | \"mixed\" | undefined", + "required": false, + "description": "Indicates the current \"pressed\" state of toggle buttons." + }, + { + "name": "aria-readonly", + "type": "Booleanish | undefined", + "required": false, + "description": "Indicates that the element is not editable, but is otherwise operable." + }, + { + "name": "aria-relevant", + "type": "\"text\" | \"all\" | \"additions\" | \"additions removals\" | \"additions text\" | \"removals\" | \"removals additions\" | \"removals text\" | \"text additions\" | \"text removals\" | undefined", + "required": false, + "description": "Indicates what notifications the user agent will trigger when the accessibility tree within a live region is modified." + }, + { + "name": "aria-required", + "type": "Booleanish | undefined", + "required": false, + "description": "Indicates that user input is required on the element before a form may be submitted." + }, + { + "name": "aria-roledescription", + "type": "string | undefined", + "required": false, + "description": "Defines a human-readable, author-localized description for the role of an element." + }, + { + "name": "aria-rowcount", + "type": "number | undefined", + "required": false, + "description": "Defines the total number of rows in a table, grid, or treegrid." + }, + { + "name": "aria-rowindex", + "type": "number | undefined", + "required": false, + "description": "Defines an element's row index or position with respect to the total number of rows within a table, grid, or treegrid." + }, + { + "name": "aria-rowindextext", + "type": "string | undefined", + "required": false, + "description": "Defines a human readable text alternative of aria-rowindex." + }, + { + "name": "aria-rowspan", + "type": "number | undefined", + "required": false, + "description": "Defines the number of rows spanned by a cell or gridcell within a table, grid, or treegrid." + }, + { + "name": "aria-selected", + "type": "Booleanish | undefined", + "required": false, + "description": "Indicates the current \"selected\" state of various widgets." + }, + { + "name": "aria-setsize", + "type": "number | undefined", + "required": false, + "description": "Defines the number of items in the current set of listitems or treeitems. Not required if all elements in the set are present in the DOM." + }, + { + "name": "aria-sort", + "type": "\"none\" | \"ascending\" | \"descending\" | \"other\" | undefined", + "required": false, + "description": "Indicates if items in a table or grid are sorted in ascending or descending order." + }, + { + "name": "aria-valuemax", + "type": "number | undefined", + "required": false, + "description": "Defines the maximum allowed value for a range widget." + }, + { + "name": "aria-valuemin", + "type": "number | undefined", + "required": false, + "description": "Defines the minimum allowed value for a range widget." + }, + { + "name": "aria-valuenow", + "type": "number | undefined", + "required": false, + "description": "Defines the current value for a range widget." + }, + { + "name": "aria-valuetext", + "type": "string | undefined", + "required": false, + "description": "Defines the human readable text alternative of aria-valuenow for a range widget." + }, + { + "name": "children", + "type": "ReactNode", + "required": false + }, + { + "name": "dangerouslySetInnerHTML", + "type": "{ __html: string | TrustedHTML; } | undefined", + "required": false + }, + { + "name": "onCopy", + "type": "ClipboardEventHandler | undefined", + "required": false + }, + { + "name": "onCopyCapture", + "type": "ClipboardEventHandler | undefined", + "required": false + }, + { + "name": "onCut", + "type": "ClipboardEventHandler | undefined", + "required": false + }, + { + "name": "onCutCapture", + "type": "ClipboardEventHandler | undefined", + "required": false + }, + { + "name": "onPaste", + "type": "ClipboardEventHandler | undefined", + "required": false + }, + { + "name": "onPasteCapture", + "type": "ClipboardEventHandler | undefined", + "required": false + }, + { + "name": "onCompositionEnd", + "type": "CompositionEventHandler | undefined", + "required": false + }, + { + "name": "onCompositionEndCapture", + "type": "CompositionEventHandler | undefined", + "required": false + }, + { + "name": "onCompositionStart", + "type": "CompositionEventHandler | undefined", + "required": false + }, + { + "name": "onCompositionStartCapture", + "type": "CompositionEventHandler | undefined", + "required": false + }, + { + "name": "onCompositionUpdate", + "type": "CompositionEventHandler | undefined", + "required": false + }, + { + "name": "onCompositionUpdateCapture", + "type": "CompositionEventHandler | undefined", + "required": false + }, + { + "name": "onFocus", + "type": "FocusEventHandler | undefined", + "required": false + }, + { + "name": "onFocusCapture", + "type": "FocusEventHandler | undefined", + "required": false + }, + { + "name": "onBlur", + "type": "FocusEventHandler | undefined", + "required": false + }, + { + "name": "onBlurCapture", + "type": "FocusEventHandler | undefined", + "required": false + }, + { + "name": "onChange", + "type": "FormEventHandler | undefined", + "required": false + }, + { + "name": "onChangeCapture", + "type": "FormEventHandler | undefined", + "required": false + }, + { + "name": "onBeforeInput", + "type": "FormEventHandler | undefined", + "required": false + }, + { + "name": "onBeforeInputCapture", + "type": "FormEventHandler | undefined", + "required": false + }, + { + "name": "onInput", + "type": "FormEventHandler | undefined", + "required": false + }, + { + "name": "onInputCapture", + "type": "FormEventHandler | undefined", + "required": false + }, + { + "name": "onReset", + "type": "FormEventHandler | undefined", + "required": false + }, + { + "name": "onResetCapture", + "type": "FormEventHandler | undefined", + "required": false + }, + { + "name": "onSubmit", + "type": "FormEventHandler | undefined", + "required": false + }, + { + "name": "onSubmitCapture", + "type": "FormEventHandler | undefined", + "required": false + }, + { + "name": "onInvalid", + "type": "FormEventHandler | undefined", + "required": false + }, + { + "name": "onInvalidCapture", + "type": "FormEventHandler | undefined", + "required": false + }, + { + "name": "onLoad", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onLoadCapture", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onError", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onErrorCapture", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onKeyDown", + "type": "KeyboardEventHandler | undefined", + "required": false + }, + { + "name": "onKeyDownCapture", + "type": "KeyboardEventHandler | undefined", + "required": false + }, + { + "name": "onKeyPress", + "type": "KeyboardEventHandler | undefined", + "required": false + }, + { + "name": "onKeyPressCapture", + "type": "KeyboardEventHandler | undefined", + "required": false + }, + { + "name": "onKeyUp", + "type": "KeyboardEventHandler | undefined", + "required": false + }, + { + "name": "onKeyUpCapture", + "type": "KeyboardEventHandler | undefined", + "required": false + }, + { + "name": "onAbort", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onAbortCapture", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onCanPlay", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onCanPlayCapture", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onCanPlayThrough", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onCanPlayThroughCapture", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onDurationChange", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onDurationChangeCapture", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onEmptied", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onEmptiedCapture", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onEncrypted", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onEncryptedCapture", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onEnded", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onEndedCapture", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onLoadedData", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onLoadedDataCapture", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onLoadedMetadata", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onLoadedMetadataCapture", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onLoadStart", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onLoadStartCapture", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onPause", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onPauseCapture", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onPlay", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onPlayCapture", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onPlaying", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onPlayingCapture", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onProgress", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onProgressCapture", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onRateChange", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onRateChangeCapture", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onResize", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onResizeCapture", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onSeeked", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onSeekedCapture", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onSeeking", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onSeekingCapture", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onStalled", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onStalledCapture", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onSuspend", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onSuspendCapture", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onTimeUpdate", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onTimeUpdateCapture", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onVolumeChange", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onVolumeChangeCapture", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onWaiting", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onWaitingCapture", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onAuxClick", + "type": "MouseEventHandler | undefined", + "required": false + }, + { + "name": "onAuxClickCapture", + "type": "MouseEventHandler | undefined", + "required": false + }, + { + "name": "onClick", + "type": "MouseEventHandler | undefined", + "required": false + }, + { + "name": "onClickCapture", + "type": "MouseEventHandler | undefined", + "required": false + }, + { + "name": "onContextMenu", + "type": "MouseEventHandler | undefined", + "required": false + }, + { + "name": "onContextMenuCapture", + "type": "MouseEventHandler | undefined", + "required": false + }, + { + "name": "onDoubleClick", + "type": "MouseEventHandler | undefined", + "required": false + }, + { + "name": "onDoubleClickCapture", + "type": "MouseEventHandler | undefined", + "required": false + }, + { + "name": "onDrag", + "type": "DragEventHandler | undefined", + "required": false + }, + { + "name": "onDragCapture", + "type": "DragEventHandler | undefined", + "required": false + }, + { + "name": "onDragEnd", + "type": "DragEventHandler | undefined", + "required": false + }, + { + "name": "onDragEndCapture", + "type": "DragEventHandler | undefined", + "required": false + }, + { + "name": "onDragEnter", + "type": "DragEventHandler | undefined", + "required": false + }, + { + "name": "onDragEnterCapture", + "type": "DragEventHandler | undefined", + "required": false + }, + { + "name": "onDragExit", + "type": "DragEventHandler | undefined", + "required": false + }, + { + "name": "onDragExitCapture", + "type": "DragEventHandler | undefined", + "required": false + }, + { + "name": "onDragLeave", + "type": "DragEventHandler | undefined", + "required": false + }, + { + "name": "onDragLeaveCapture", + "type": "DragEventHandler | undefined", + "required": false + }, + { + "name": "onDragOver", + "type": "DragEventHandler | undefined", + "required": false + }, + { + "name": "onDragOverCapture", + "type": "DragEventHandler | undefined", + "required": false + }, + { + "name": "onDragStart", + "type": "DragEventHandler | undefined", + "required": false + }, + { + "name": "onDragStartCapture", + "type": "DragEventHandler | undefined", + "required": false + }, + { + "name": "onDrop", + "type": "DragEventHandler | undefined", + "required": false + }, + { + "name": "onDropCapture", + "type": "DragEventHandler | undefined", + "required": false + }, + { + "name": "onMouseDown", + "type": "MouseEventHandler | undefined", + "required": false + }, + { + "name": "onMouseDownCapture", + "type": "MouseEventHandler | undefined", + "required": false + }, + { + "name": "onMouseEnter", + "type": "MouseEventHandler | undefined", + "required": false + }, + { + "name": "onMouseLeave", + "type": "MouseEventHandler | undefined", + "required": false + }, + { + "name": "onMouseMove", + "type": "MouseEventHandler | undefined", + "required": false + }, + { + "name": "onMouseMoveCapture", + "type": "MouseEventHandler | undefined", + "required": false + }, + { + "name": "onMouseOut", + "type": "MouseEventHandler | undefined", + "required": false + }, + { + "name": "onMouseOutCapture", + "type": "MouseEventHandler | undefined", + "required": false + }, + { + "name": "onMouseOver", + "type": "MouseEventHandler | undefined", + "required": false + }, + { + "name": "onMouseOverCapture", + "type": "MouseEventHandler | undefined", + "required": false + }, + { + "name": "onMouseUp", + "type": "MouseEventHandler | undefined", + "required": false + }, + { + "name": "onMouseUpCapture", + "type": "MouseEventHandler | undefined", + "required": false + }, + { + "name": "onSelectCapture", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onTouchCancel", + "type": "TouchEventHandler | undefined", + "required": false + }, + { + "name": "onTouchCancelCapture", + "type": "TouchEventHandler | undefined", + "required": false + }, + { + "name": "onTouchEnd", + "type": "TouchEventHandler | undefined", + "required": false + }, + { + "name": "onTouchEndCapture", + "type": "TouchEventHandler | undefined", + "required": false + }, + { + "name": "onTouchMove", + "type": "TouchEventHandler | undefined", + "required": false + }, + { + "name": "onTouchMoveCapture", + "type": "TouchEventHandler | undefined", + "required": false + }, + { + "name": "onTouchStart", + "type": "TouchEventHandler | undefined", + "required": false + }, + { + "name": "onTouchStartCapture", + "type": "TouchEventHandler | undefined", + "required": false + }, + { + "name": "onPointerDown", + "type": "PointerEventHandler | undefined", + "required": false + }, + { + "name": "onPointerDownCapture", + "type": "PointerEventHandler | undefined", + "required": false + }, + { + "name": "onPointerMove", + "type": "PointerEventHandler | undefined", + "required": false + }, + { + "name": "onPointerMoveCapture", + "type": "PointerEventHandler | undefined", + "required": false + }, + { + "name": "onPointerUp", + "type": "PointerEventHandler | undefined", + "required": false + }, + { + "name": "onPointerUpCapture", + "type": "PointerEventHandler | undefined", + "required": false + }, + { + "name": "onPointerCancel", + "type": "PointerEventHandler | undefined", + "required": false + }, + { + "name": "onPointerCancelCapture", + "type": "PointerEventHandler | undefined", + "required": false + }, + { + "name": "onPointerEnter", + "type": "PointerEventHandler | undefined", + "required": false + }, + { + "name": "onPointerLeave", + "type": "PointerEventHandler | undefined", + "required": false + }, + { + "name": "onPointerOver", + "type": "PointerEventHandler | undefined", + "required": false + }, + { + "name": "onPointerOverCapture", + "type": "PointerEventHandler | undefined", + "required": false + }, + { + "name": "onPointerOut", + "type": "PointerEventHandler | undefined", + "required": false + }, + { + "name": "onPointerOutCapture", + "type": "PointerEventHandler | undefined", + "required": false + }, + { + "name": "onGotPointerCapture", + "type": "PointerEventHandler | undefined", + "required": false + }, + { + "name": "onGotPointerCaptureCapture", + "type": "PointerEventHandler | undefined", + "required": false + }, + { + "name": "onLostPointerCapture", + "type": "PointerEventHandler | undefined", + "required": false + }, + { + "name": "onLostPointerCaptureCapture", + "type": "PointerEventHandler | undefined", + "required": false + }, + { + "name": "onScroll", + "type": "UIEventHandler | undefined", + "required": false + }, + { + "name": "onScrollCapture", + "type": "UIEventHandler | undefined", + "required": false + }, + { + "name": "onScrollEnd", + "type": "UIEventHandler | undefined", + "required": false + }, + { + "name": "onScrollEndCapture", + "type": "UIEventHandler | undefined", + "required": false + }, + { + "name": "onWheel", + "type": "WheelEventHandler | undefined", + "required": false + }, + { + "name": "onWheelCapture", + "type": "WheelEventHandler | undefined", + "required": false + }, + { + "name": "onAnimationStart", + "type": "AnimationEventHandler | undefined", + "required": false + }, + { + "name": "onAnimationStartCapture", + "type": "AnimationEventHandler | undefined", + "required": false + }, + { + "name": "onAnimationEnd", + "type": "AnimationEventHandler | undefined", + "required": false + }, + { + "name": "onAnimationEndCapture", + "type": "AnimationEventHandler | undefined", + "required": false + }, + { + "name": "onAnimationIteration", + "type": "AnimationEventHandler | undefined", + "required": false + }, + { + "name": "onAnimationIterationCapture", + "type": "AnimationEventHandler | undefined", + "required": false + }, + { + "name": "onToggle", + "type": "ToggleEventHandler | undefined", + "required": false + }, + { + "name": "onBeforeToggle", + "type": "ToggleEventHandler | undefined", + "required": false + }, + { + "name": "onTransitionCancel", + "type": "TransitionEventHandler | undefined", + "required": false + }, + { + "name": "onTransitionCancelCapture", + "type": "TransitionEventHandler | undefined", + "required": false + }, + { + "name": "onTransitionEnd", + "type": "TransitionEventHandler | undefined", + "required": false + }, + { + "name": "onTransitionEndCapture", + "type": "TransitionEventHandler | undefined", + "required": false + }, + { + "name": "onTransitionRun", + "type": "TransitionEventHandler | undefined", + "required": false + }, + { + "name": "onTransitionRunCapture", + "type": "TransitionEventHandler | undefined", + "required": false + }, + { + "name": "onTransitionStart", + "type": "TransitionEventHandler | undefined", + "required": false + }, + { + "name": "onTransitionStartCapture", + "type": "TransitionEventHandler | undefined", + "required": false + }, + { + "name": "asChild", + "type": "boolean | undefined", + "required": false + }, + { + "name": "disabled", + "type": "boolean | undefined", + "required": false + }, + { + "name": "formAction", + "type": "string | ((formData: FormData) => void | Promise) | undefined", + "required": false + }, + { + "name": "formEncType", + "type": "string | undefined", + "required": false + }, + { + "name": "formMethod", + "type": "string | undefined", + "required": false + }, + { + "name": "formNoValidate", + "type": "boolean | undefined", + "required": false + }, + { + "name": "formTarget", + "type": "string | undefined", + "required": false + }, + { + "name": "name", + "type": "string | undefined", + "required": false + }, + { + "name": "type", + "type": "\"button\" | \"submit\" | \"reset\" | undefined", + "required": false + }, + { + "name": "value", + "type": "string | number | readonly string[] | undefined", + "required": false + } + ] + }, + "ActionMenuPositionerProps": { + "name": "ActionMenuPositionerProps", + "kind": "interface", + "props": [ + { + "name": "children", + "type": "ReactElement>", + "required": true + }, + { + "name": "side", + "type": "\"left\" | \"right\" | \"top\" | \"bottom\" | undefined", + "required": false + }, + { + "name": "align", + "type": "\"center\" | \"start\" | \"end\" | undefined", + "required": false + }, + { + "name": "sideOffset", + "type": "number | undefined", + "required": false + }, + { + "name": "alignOffset", + "type": "number | undefined", + "required": false + }, + { + "name": "avoidCollisions", + "type": "boolean | undefined", + "required": false + }, + { + "name": "collisionPadding", + "type": "number | Partial> | undefined", + "required": false + }, + { + "name": "alignToFirstItem", + "type": "false | \"on-open\" | \"always\" | undefined", + "required": false + } + ] + }, + "ActionMenuSurfaceProps": { + "name": "ActionMenuSurfaceProps", + "kind": "interface", + "typeParams": [ + { + "name": "T", + "default": "unknown" + } + ], + "props": [ + { + "name": "menu", + "type": "MenuDef | Menu", + "required": true + }, + { + "name": "slots", + "type": "Partial> | undefined", + "required": false + }, + { + "name": "surfaceSlotProps", + "type": "Partial | undefined", + "required": false + }, + { + "name": "vimBindings", + "type": "boolean | undefined", + "required": false + }, + { + "name": "dir", + "type": "Direction | undefined", + "required": false + }, + { + "name": "defaults", + "type": "Partial> | undefined", + "required": false + }, + { + "name": "surfaceClassNames", + "type": "Partial | undefined", + "required": false + }, + { + "name": "value", + "type": "string | undefined", + "required": false + }, + { + "name": "defaultValue", + "type": "string | undefined", + "required": false + }, + { + "name": "onValueChange", + "type": "((value: string) => void) | undefined", + "required": false + }, + { + "name": "onOpenAutoFocus", + "type": "boolean | undefined", + "required": false + }, + { + "name": "onCloseAutoClear", + "type": "number | boolean | undefined", + "required": false + }, + { + "name": "surfaceIdProp", + "type": "string | undefined", + "required": false + }, + { + "name": "suppressHoverOpenOnMount", + "type": "boolean | undefined", + "required": false + }, + { + "name": "slot", + "type": "string | undefined", + "required": false + }, + { + "name": "style", + "type": "CSSProperties | undefined", + "required": false + }, + { + "name": "title", + "type": "string | undefined", + "required": false + }, + { + "name": "hidden", + "type": "boolean | undefined", + "required": false + }, + { + "name": "id", + "type": "string | undefined", + "required": false + }, + { + "name": "onSelect", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "key", + "type": "Key | null | undefined", + "required": false + }, + { + "name": "defaultChecked", + "type": "boolean | undefined", + "required": false + }, + { + "name": "suppressContentEditableWarning", + "type": "boolean | undefined", + "required": false + }, + { + "name": "suppressHydrationWarning", + "type": "boolean | undefined", + "required": false + }, + { + "name": "accessKey", + "type": "string | undefined", + "required": false + }, + { + "name": "autoCapitalize", + "type": "\"off\" | \"none\" | \"on\" | \"sentences\" | \"words\" | \"characters\" | (string & {}) | undefined", + "required": false + }, + { + "name": "autoFocus", + "type": "boolean | undefined", + "required": false + }, + { + "name": "className", + "type": "string | undefined", + "required": false + }, + { + "name": "contentEditable", + "type": "Booleanish | \"inherit\" | \"plaintext-only\" | undefined", + "required": false + }, + { + "name": "contextMenu", + "type": "string | undefined", + "required": false + }, + { + "name": "draggable", + "type": "Booleanish | undefined", + "required": false + }, + { + "name": "enterKeyHint", + "type": "\"search\" | \"enter\" | \"done\" | \"go\" | \"next\" | \"previous\" | \"send\" | undefined", + "required": false + }, + { + "name": "lang", + "type": "string | undefined", + "required": false + }, + { + "name": "nonce", + "type": "string | undefined", + "required": false + }, + { + "name": "spellCheck", + "type": "Booleanish | undefined", + "required": false + }, + { + "name": "tabIndex", + "type": "number | undefined", + "required": false + }, + { + "name": "translate", + "type": "\"yes\" | \"no\" | undefined", + "required": false + }, + { + "name": "radioGroup", + "type": "string | undefined", + "required": false + }, + { + "name": "role", + "type": "AriaRole | undefined", + "required": false + }, + { + "name": "about", + "type": "string | undefined", + "required": false + }, + { + "name": "content", + "type": "string | undefined", + "required": false + }, + { + "name": "datatype", + "type": "string | undefined", + "required": false + }, + { + "name": "inlist", + "type": "any", + "required": false + }, + { + "name": "prefix", + "type": "string | undefined", + "required": false + }, + { + "name": "property", + "type": "string | undefined", + "required": false + }, + { + "name": "rel", + "type": "string | undefined", + "required": false + }, + { + "name": "resource", + "type": "string | undefined", + "required": false + }, + { + "name": "rev", + "type": "string | undefined", + "required": false + }, + { + "name": "typeof", + "type": "string | undefined", + "required": false + }, + { + "name": "vocab", + "type": "string | undefined", + "required": false + }, + { + "name": "autoCorrect", + "type": "string | undefined", + "required": false + }, + { + "name": "autoSave", + "type": "string | undefined", + "required": false + }, + { + "name": "color", + "type": "string | undefined", + "required": false + }, + { + "name": "itemProp", + "type": "string | undefined", + "required": false + }, + { + "name": "itemScope", + "type": "boolean | undefined", + "required": false + }, + { + "name": "itemType", + "type": "string | undefined", + "required": false + }, + { + "name": "itemID", + "type": "string | undefined", + "required": false + }, + { + "name": "itemRef", + "type": "string | undefined", + "required": false + }, + { + "name": "results", + "type": "number | undefined", + "required": false + }, + { + "name": "security", + "type": "string | undefined", + "required": false + }, + { + "name": "unselectable", + "type": "\"off\" | \"on\" | undefined", + "required": false + }, + { + "name": "popover", + "type": "\"\" | \"auto\" | \"manual\" | undefined", + "required": false + }, + { + "name": "popoverTargetAction", + "type": "\"toggle\" | \"show\" | \"hide\" | undefined", + "required": false + }, + { + "name": "popoverTarget", + "type": "string | undefined", + "required": false + }, + { + "name": "inert", + "type": "boolean | undefined", + "required": false + }, + { + "name": "inputMode", + "type": "\"search\" | \"text\" | \"none\" | \"tel\" | \"url\" | \"email\" | \"numeric\" | \"decimal\" | undefined", + "required": false, + "description": "Hints at the type of data that might be entered by the user while editing the element or its contents" + }, + { + "name": "is", + "type": "string | undefined", + "required": false, + "description": "Specify that a standard HTML element should behave like a defined custom built-in element" + }, + { + "name": "exportparts", + "type": "string | undefined", + "required": false + }, + { + "name": "part", + "type": "string | undefined", + "required": false + }, + { + "name": "aria-activedescendant", + "type": "string | undefined", + "required": false, + "description": "Identifies the currently active element when DOM focus is on a composite widget, textbox, group, or application." + }, + { + "name": "aria-atomic", + "type": "Booleanish | undefined", + "required": false, + "description": "Indicates whether assistive technologies will present all, or only parts of, the changed region based on the change notifications defined by the aria-relevant attribute." + }, + { + "name": "aria-autocomplete", + "type": "\"none\" | \"list\" | \"inline\" | \"both\" | undefined", + "required": false, + "description": "Indicates whether inputting text could trigger display of one or more predictions of the user's intended value for an input and specifies how predictions would be\npresented if they are made." + }, + { + "name": "aria-braillelabel", + "type": "string | undefined", + "required": false, + "description": "Defines a string value that labels the current element, which is intended to be converted into Braille." + }, + { + "name": "aria-brailleroledescription", + "type": "string | undefined", + "required": false, + "description": "Defines a human-readable, author-localized abbreviated description for the role of an element, which is intended to be converted into Braille." + }, + { + "name": "aria-busy", + "type": "Booleanish | undefined", + "required": false + }, + { + "name": "aria-checked", + "type": "boolean | \"true\" | \"false\" | \"mixed\" | undefined", + "required": false, + "description": "Indicates the current \"checked\" state of checkboxes, radio buttons, and other widgets." + }, + { + "name": "aria-colcount", + "type": "number | undefined", + "required": false, + "description": "Defines the total number of columns in a table, grid, or treegrid." + }, + { + "name": "aria-colindex", + "type": "number | undefined", + "required": false, + "description": "Defines an element's column index or position with respect to the total number of columns within a table, grid, or treegrid." + }, + { + "name": "aria-colindextext", + "type": "string | undefined", + "required": false, + "description": "Defines a human readable text alternative of aria-colindex." + }, + { + "name": "aria-colspan", + "type": "number | undefined", + "required": false, + "description": "Defines the number of columns spanned by a cell or gridcell within a table, grid, or treegrid." + }, + { + "name": "aria-controls", + "type": "string | undefined", + "required": false, + "description": "Identifies the element (or elements) whose contents or presence are controlled by the current element." + }, + { + "name": "aria-current", + "type": "boolean | \"time\" | \"true\" | \"false\" | \"date\" | \"page\" | \"step\" | \"location\" | undefined", + "required": false, + "description": "Indicates the element that represents the current item within a container or set of related elements." + }, + { + "name": "aria-describedby", + "type": "string | undefined", + "required": false, + "description": "Identifies the element (or elements) that describes the object." + }, + { + "name": "aria-description", + "type": "string | undefined", + "required": false, + "description": "Defines a string value that describes or annotates the current element." + }, + { + "name": "aria-details", + "type": "string | undefined", + "required": false, + "description": "Identifies the element that provides a detailed, extended description for the object." + }, + { + "name": "aria-disabled", + "type": "Booleanish | undefined", + "required": false, + "description": "Indicates that the element is perceivable but disabled, so it is not editable or otherwise operable." + }, + { + "name": "aria-dropeffect", + "type": "\"link\" | \"none\" | \"copy\" | \"execute\" | \"move\" | \"popup\" | undefined", + "required": false, + "description": "Indicates what functions can be performed when a dragged object is released on the drop target." + }, + { + "name": "aria-errormessage", + "type": "string | undefined", + "required": false, + "description": "Identifies the element that provides an error message for the object." + }, + { + "name": "aria-expanded", + "type": "Booleanish | undefined", + "required": false, + "description": "Indicates whether the element, or another grouping element it controls, is currently expanded or collapsed." + }, + { + "name": "aria-flowto", + "type": "string | undefined", + "required": false, + "description": "Identifies the next element (or elements) in an alternate reading order of content which, at the user's discretion,\nallows assistive technology to override the general default of reading in document source order." + }, + { + "name": "aria-grabbed", + "type": "Booleanish | undefined", + "required": false, + "description": "Indicates an element's \"grabbed\" state in a drag-and-drop operation." + }, + { + "name": "aria-haspopup", + "type": "boolean | \"dialog\" | \"menu\" | \"true\" | \"false\" | \"grid\" | \"listbox\" | \"tree\" | undefined", + "required": false, + "description": "Indicates the availability and type of interactive popup element, such as menu or dialog, that can be triggered by an element." + }, + { + "name": "aria-hidden", + "type": "Booleanish | undefined", + "required": false, + "description": "Indicates whether the element is exposed to an accessibility API." + }, + { + "name": "aria-invalid", + "type": "boolean | \"true\" | \"false\" | \"grammar\" | \"spelling\" | undefined", + "required": false, + "description": "Indicates the entered value does not conform to the format expected by the application." + }, + { + "name": "aria-keyshortcuts", + "type": "string | undefined", + "required": false, + "description": "Indicates keyboard shortcuts that an author has implemented to activate or give focus to an element." + }, + { + "name": "aria-label", + "type": "string | undefined", + "required": false, + "description": "Defines a string value that labels the current element." + }, + { + "name": "aria-labelledby", + "type": "string | undefined", + "required": false, + "description": "Identifies the element (or elements) that labels the current element." + }, + { + "name": "aria-level", + "type": "number | undefined", + "required": false, + "description": "Defines the hierarchical level of an element within a structure." + }, + { + "name": "aria-live", + "type": "\"off\" | \"assertive\" | \"polite\" | undefined", + "required": false, + "description": "Indicates that an element will be updated, and describes the types of updates the user agents, assistive technologies, and user can expect from the live region." + }, + { + "name": "aria-modal", + "type": "Booleanish | undefined", + "required": false, + "description": "Indicates whether an element is modal when displayed." + }, + { + "name": "aria-multiline", + "type": "Booleanish | undefined", + "required": false, + "description": "Indicates whether a text box accepts multiple lines of input or only a single line." + }, + { + "name": "aria-multiselectable", + "type": "Booleanish | undefined", + "required": false, + "description": "Indicates that the user may select more than one item from the current selectable descendants." + }, + { + "name": "aria-orientation", + "type": "\"horizontal\" | \"vertical\" | undefined", + "required": false, + "description": "Indicates whether the element's orientation is horizontal, vertical, or unknown/ambiguous." + }, + { + "name": "aria-owns", + "type": "string | undefined", + "required": false, + "description": "Identifies an element (or elements) in order to define a visual, functional, or contextual parent/child relationship\nbetween DOM elements where the DOM hierarchy cannot be used to represent the relationship." + }, + { + "name": "aria-placeholder", + "type": "string | undefined", + "required": false, + "description": "Defines a short hint (a word or short phrase) intended to aid the user with data entry when the control has no value.\nA hint could be a sample value or a brief description of the expected format." + }, + { + "name": "aria-posinset", + "type": "number | undefined", + "required": false, + "description": "Defines an element's number or position in the current set of listitems or treeitems. Not required if all elements in the set are present in the DOM." + }, + { + "name": "aria-pressed", + "type": "boolean | \"true\" | \"false\" | \"mixed\" | undefined", + "required": false, + "description": "Indicates the current \"pressed\" state of toggle buttons." + }, + { + "name": "aria-readonly", + "type": "Booleanish | undefined", + "required": false, + "description": "Indicates that the element is not editable, but is otherwise operable." + }, + { + "name": "aria-relevant", + "type": "\"text\" | \"all\" | \"additions\" | \"additions removals\" | \"additions text\" | \"removals\" | \"removals additions\" | \"removals text\" | \"text additions\" | \"text removals\" | undefined", + "required": false, + "description": "Indicates what notifications the user agent will trigger when the accessibility tree within a live region is modified." + }, + { + "name": "aria-required", + "type": "Booleanish | undefined", + "required": false, + "description": "Indicates that user input is required on the element before a form may be submitted." + }, + { + "name": "aria-roledescription", + "type": "string | undefined", + "required": false, + "description": "Defines a human-readable, author-localized description for the role of an element." + }, + { + "name": "aria-rowcount", + "type": "number | undefined", + "required": false, + "description": "Defines the total number of rows in a table, grid, or treegrid." + }, + { + "name": "aria-rowindex", + "type": "number | undefined", + "required": false, + "description": "Defines an element's row index or position with respect to the total number of rows within a table, grid, or treegrid." + }, + { + "name": "aria-rowindextext", + "type": "string | undefined", + "required": false, + "description": "Defines a human readable text alternative of aria-rowindex." + }, + { + "name": "aria-rowspan", + "type": "number | undefined", + "required": false, + "description": "Defines the number of rows spanned by a cell or gridcell within a table, grid, or treegrid." + }, + { + "name": "aria-selected", + "type": "Booleanish | undefined", + "required": false, + "description": "Indicates the current \"selected\" state of various widgets." + }, + { + "name": "aria-setsize", + "type": "number | undefined", + "required": false, + "description": "Defines the number of items in the current set of listitems or treeitems. Not required if all elements in the set are present in the DOM." + }, + { + "name": "aria-sort", + "type": "\"none\" | \"ascending\" | \"descending\" | \"other\" | undefined", + "required": false, + "description": "Indicates if items in a table or grid are sorted in ascending or descending order." + }, + { + "name": "aria-valuemax", + "type": "number | undefined", + "required": false, + "description": "Defines the maximum allowed value for a range widget." + }, + { + "name": "aria-valuemin", + "type": "number | undefined", + "required": false, + "description": "Defines the minimum allowed value for a range widget." + }, + { + "name": "aria-valuenow", + "type": "number | undefined", + "required": false, + "description": "Defines the current value for a range widget." + }, + { + "name": "aria-valuetext", + "type": "string | undefined", + "required": false, + "description": "Defines the human readable text alternative of aria-valuenow for a range widget." + }, + { + "name": "dangerouslySetInnerHTML", + "type": "{ __html: string | TrustedHTML; } | undefined", + "required": false + }, + { + "name": "onCopy", + "type": "ClipboardEventHandler | undefined", + "required": false + }, + { + "name": "onCopyCapture", + "type": "ClipboardEventHandler | undefined", + "required": false + }, + { + "name": "onCut", + "type": "ClipboardEventHandler | undefined", + "required": false + }, + { + "name": "onCutCapture", + "type": "ClipboardEventHandler | undefined", + "required": false + }, + { + "name": "onPaste", + "type": "ClipboardEventHandler | undefined", + "required": false + }, + { + "name": "onPasteCapture", + "type": "ClipboardEventHandler | undefined", + "required": false + }, + { + "name": "onCompositionEnd", + "type": "CompositionEventHandler | undefined", + "required": false + }, + { + "name": "onCompositionEndCapture", + "type": "CompositionEventHandler | undefined", + "required": false + }, + { + "name": "onCompositionStart", + "type": "CompositionEventHandler | undefined", + "required": false + }, + { + "name": "onCompositionStartCapture", + "type": "CompositionEventHandler | undefined", + "required": false + }, + { + "name": "onCompositionUpdate", + "type": "CompositionEventHandler | undefined", + "required": false + }, + { + "name": "onCompositionUpdateCapture", + "type": "CompositionEventHandler | undefined", + "required": false + }, + { + "name": "onFocus", + "type": "FocusEventHandler | undefined", + "required": false + }, + { + "name": "onFocusCapture", + "type": "FocusEventHandler | undefined", + "required": false + }, + { + "name": "onBlur", + "type": "FocusEventHandler | undefined", + "required": false + }, + { + "name": "onBlurCapture", + "type": "FocusEventHandler | undefined", + "required": false + }, + { + "name": "onChange", + "type": "FormEventHandler | undefined", + "required": false + }, + { + "name": "onChangeCapture", + "type": "FormEventHandler | undefined", + "required": false + }, + { + "name": "onBeforeInput", + "type": "FormEventHandler | undefined", + "required": false + }, + { + "name": "onBeforeInputCapture", + "type": "FormEventHandler | undefined", + "required": false + }, + { + "name": "onInput", + "type": "FormEventHandler | undefined", + "required": false + }, + { + "name": "onInputCapture", + "type": "FormEventHandler | undefined", + "required": false + }, + { + "name": "onReset", + "type": "FormEventHandler | undefined", + "required": false + }, + { + "name": "onResetCapture", + "type": "FormEventHandler | undefined", + "required": false + }, + { + "name": "onSubmit", + "type": "FormEventHandler | undefined", + "required": false + }, + { + "name": "onSubmitCapture", + "type": "FormEventHandler | undefined", + "required": false + }, + { + "name": "onInvalid", + "type": "FormEventHandler | undefined", + "required": false + }, + { + "name": "onInvalidCapture", + "type": "FormEventHandler | undefined", + "required": false + }, + { + "name": "onLoad", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onLoadCapture", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onError", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onErrorCapture", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onKeyDown", + "type": "KeyboardEventHandler | undefined", + "required": false + }, + { + "name": "onKeyDownCapture", + "type": "KeyboardEventHandler | undefined", + "required": false + }, + { + "name": "onKeyPress", + "type": "KeyboardEventHandler | undefined", + "required": false + }, + { + "name": "onKeyPressCapture", + "type": "KeyboardEventHandler | undefined", + "required": false + }, + { + "name": "onKeyUp", + "type": "KeyboardEventHandler | undefined", + "required": false + }, + { + "name": "onKeyUpCapture", + "type": "KeyboardEventHandler | undefined", + "required": false + }, + { + "name": "onAbort", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onAbortCapture", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onCanPlay", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onCanPlayCapture", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onCanPlayThrough", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onCanPlayThroughCapture", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onDurationChange", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onDurationChangeCapture", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onEmptied", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onEmptiedCapture", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onEncrypted", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onEncryptedCapture", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onEnded", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onEndedCapture", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onLoadedData", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onLoadedDataCapture", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onLoadedMetadata", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onLoadedMetadataCapture", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onLoadStart", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onLoadStartCapture", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onPause", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onPauseCapture", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onPlay", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onPlayCapture", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onPlaying", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onPlayingCapture", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onProgress", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onProgressCapture", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onRateChange", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onRateChangeCapture", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onResize", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onResizeCapture", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onSeeked", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onSeekedCapture", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onSeeking", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onSeekingCapture", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onStalled", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onStalledCapture", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onSuspend", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onSuspendCapture", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onTimeUpdate", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onTimeUpdateCapture", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onVolumeChange", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onVolumeChangeCapture", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onWaiting", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onWaitingCapture", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onAuxClick", + "type": "MouseEventHandler | undefined", + "required": false + }, + { + "name": "onAuxClickCapture", + "type": "MouseEventHandler | undefined", + "required": false + }, + { + "name": "onClick", + "type": "MouseEventHandler | undefined", + "required": false + }, + { + "name": "onClickCapture", + "type": "MouseEventHandler | undefined", + "required": false + }, + { + "name": "onContextMenu", + "type": "MouseEventHandler | undefined", + "required": false + }, + { + "name": "onContextMenuCapture", + "type": "MouseEventHandler | undefined", + "required": false + }, + { + "name": "onDoubleClick", + "type": "MouseEventHandler | undefined", + "required": false + }, + { + "name": "onDoubleClickCapture", + "type": "MouseEventHandler | undefined", + "required": false + }, + { + "name": "onDrag", + "type": "DragEventHandler | undefined", + "required": false + }, + { + "name": "onDragCapture", + "type": "DragEventHandler | undefined", + "required": false + }, + { + "name": "onDragEnd", + "type": "DragEventHandler | undefined", + "required": false + }, + { + "name": "onDragEndCapture", + "type": "DragEventHandler | undefined", + "required": false + }, + { + "name": "onDragEnter", + "type": "DragEventHandler | undefined", + "required": false + }, + { + "name": "onDragEnterCapture", + "type": "DragEventHandler | undefined", + "required": false + }, + { + "name": "onDragExit", + "type": "DragEventHandler | undefined", + "required": false + }, + { + "name": "onDragExitCapture", + "type": "DragEventHandler | undefined", + "required": false + }, + { + "name": "onDragLeave", + "type": "DragEventHandler | undefined", + "required": false + }, + { + "name": "onDragLeaveCapture", + "type": "DragEventHandler | undefined", + "required": false + }, + { + "name": "onDragOver", + "type": "DragEventHandler | undefined", + "required": false + }, + { + "name": "onDragOverCapture", + "type": "DragEventHandler | undefined", + "required": false + }, + { + "name": "onDragStart", + "type": "DragEventHandler | undefined", + "required": false + }, + { + "name": "onDragStartCapture", + "type": "DragEventHandler | undefined", + "required": false + }, + { + "name": "onDrop", + "type": "DragEventHandler | undefined", + "required": false + }, + { + "name": "onDropCapture", + "type": "DragEventHandler | undefined", + "required": false + }, + { + "name": "onMouseDown", + "type": "MouseEventHandler | undefined", + "required": false + }, + { + "name": "onMouseDownCapture", + "type": "MouseEventHandler | undefined", + "required": false + }, + { + "name": "onMouseEnter", + "type": "MouseEventHandler | undefined", + "required": false + }, + { + "name": "onMouseLeave", + "type": "MouseEventHandler | undefined", + "required": false + }, + { + "name": "onMouseMove", + "type": "MouseEventHandler | undefined", + "required": false + }, + { + "name": "onMouseMoveCapture", + "type": "MouseEventHandler | undefined", + "required": false + }, + { + "name": "onMouseOut", + "type": "MouseEventHandler | undefined", + "required": false + }, + { + "name": "onMouseOutCapture", + "type": "MouseEventHandler | undefined", + "required": false + }, + { + "name": "onMouseOver", + "type": "MouseEventHandler | undefined", + "required": false + }, + { + "name": "onMouseOverCapture", + "type": "MouseEventHandler | undefined", + "required": false + }, + { + "name": "onMouseUp", + "type": "MouseEventHandler | undefined", + "required": false + }, + { + "name": "onMouseUpCapture", + "type": "MouseEventHandler | undefined", + "required": false + }, + { + "name": "onSelectCapture", + "type": "ReactEventHandler | undefined", + "required": false + }, + { + "name": "onTouchCancel", + "type": "TouchEventHandler | undefined", + "required": false + }, + { + "name": "onTouchCancelCapture", + "type": "TouchEventHandler | undefined", + "required": false + }, + { + "name": "onTouchEnd", + "type": "TouchEventHandler | undefined", + "required": false + }, + { + "name": "onTouchEndCapture", + "type": "TouchEventHandler | undefined", + "required": false + }, + { + "name": "onTouchMove", + "type": "TouchEventHandler | undefined", + "required": false + }, + { + "name": "onTouchMoveCapture", + "type": "TouchEventHandler | undefined", + "required": false + }, + { + "name": "onTouchStart", + "type": "TouchEventHandler | undefined", + "required": false + }, + { + "name": "onTouchStartCapture", + "type": "TouchEventHandler | undefined", + "required": false + }, + { + "name": "onPointerDown", + "type": "PointerEventHandler | undefined", + "required": false + }, + { + "name": "onPointerDownCapture", + "type": "PointerEventHandler | undefined", + "required": false + }, + { + "name": "onPointerMove", + "type": "PointerEventHandler | undefined", + "required": false + }, + { + "name": "onPointerMoveCapture", + "type": "PointerEventHandler | undefined", + "required": false + }, + { + "name": "onPointerUp", + "type": "PointerEventHandler | undefined", + "required": false + }, + { + "name": "onPointerUpCapture", + "type": "PointerEventHandler | undefined", + "required": false + }, + { + "name": "onPointerCancel", + "type": "PointerEventHandler | undefined", + "required": false + }, + { + "name": "onPointerCancelCapture", + "type": "PointerEventHandler | undefined", + "required": false + }, + { + "name": "onPointerEnter", + "type": "PointerEventHandler | undefined", + "required": false + }, + { + "name": "onPointerLeave", + "type": "PointerEventHandler | undefined", + "required": false + }, + { + "name": "onPointerOver", + "type": "PointerEventHandler | undefined", + "required": false + }, + { + "name": "onPointerOverCapture", + "type": "PointerEventHandler | undefined", + "required": false + }, + { + "name": "onPointerOut", + "type": "PointerEventHandler | undefined", + "required": false + }, + { + "name": "onPointerOutCapture", + "type": "PointerEventHandler | undefined", + "required": false + }, + { + "name": "onGotPointerCapture", + "type": "PointerEventHandler | undefined", + "required": false + }, + { + "name": "onGotPointerCaptureCapture", + "type": "PointerEventHandler | undefined", + "required": false + }, + { + "name": "onLostPointerCapture", + "type": "PointerEventHandler | undefined", + "required": false + }, + { + "name": "onLostPointerCaptureCapture", + "type": "PointerEventHandler | undefined", + "required": false + }, + { + "name": "onScroll", + "type": "UIEventHandler | undefined", + "required": false + }, + { + "name": "onScrollCapture", + "type": "UIEventHandler | undefined", + "required": false + }, + { + "name": "onScrollEnd", + "type": "UIEventHandler | undefined", + "required": false + }, + { + "name": "onScrollEndCapture", + "type": "UIEventHandler | undefined", + "required": false + }, + { + "name": "onWheel", + "type": "WheelEventHandler | undefined", + "required": false + }, + { + "name": "onWheelCapture", + "type": "WheelEventHandler | undefined", + "required": false + }, + { + "name": "onAnimationStart", + "type": "AnimationEventHandler | undefined", + "required": false + }, + { + "name": "onAnimationStartCapture", + "type": "AnimationEventHandler | undefined", + "required": false + }, + { + "name": "onAnimationEnd", + "type": "AnimationEventHandler | undefined", + "required": false + }, + { + "name": "onAnimationEndCapture", + "type": "AnimationEventHandler | undefined", + "required": false + }, + { + "name": "onAnimationIteration", + "type": "AnimationEventHandler | undefined", + "required": false + }, + { + "name": "onAnimationIterationCapture", + "type": "AnimationEventHandler | undefined", + "required": false + }, + { + "name": "onToggle", + "type": "ToggleEventHandler | undefined", + "required": false + }, + { + "name": "onBeforeToggle", + "type": "ToggleEventHandler | undefined", + "required": false + }, + { + "name": "onTransitionCancel", + "type": "TransitionEventHandler | undefined", + "required": false + }, + { + "name": "onTransitionCancelCapture", + "type": "TransitionEventHandler | undefined", + "required": false + }, + { + "name": "onTransitionEnd", + "type": "TransitionEventHandler | undefined", + "required": false + }, + { + "name": "onTransitionEndCapture", + "type": "TransitionEventHandler | undefined", + "required": false + }, + { + "name": "onTransitionRun", + "type": "TransitionEventHandler | undefined", + "required": false + }, + { + "name": "onTransitionRunCapture", + "type": "TransitionEventHandler | undefined", + "required": false + }, + { + "name": "onTransitionStart", + "type": "TransitionEventHandler | undefined", + "required": false + }, + { + "name": "onTransitionStartCapture", + "type": "TransitionEventHandler | undefined", + "required": false + }, + { + "name": "asChild", + "type": "boolean | undefined", + "required": false + } + ] + }, + "MenuDef": { + "name": "MenuDef", + "kind": "typealias", + "typeParams": [ + { + "name": "T", + "default": "unknown" + } + ], + "definition": "{\n id: string;\n title?: string;\n inputPlaceholder?: string;\n hideSearchUntilActive?: boolean;\n nodes?: NodeDef[];\n defaults?: MenuNodeDefaults;\n ui?: {\n slots?: Partial>;\n slotProps?: Partial;\n classNames?: Partial;\n };\n}", + "props": [ + { + "name": "id", + "type": "string", + "required": true + }, + { + "name": "title", + "type": "string | undefined", + "required": false + }, + { + "name": "inputPlaceholder", + "type": "string | undefined", + "required": false + }, + { + "name": "hideSearchUntilActive", + "type": "boolean | undefined", + "required": false + }, + { + "name": "nodes", + "type": "NodeDef[] | undefined", + "required": false + }, + { + "name": "defaults", + "type": "MenuNodeDefaults | undefined", + "required": false + }, + { + "name": "ui", + "type": "{ slots?: Partial> | undefined; slotProps?: Partial | undefined; classNames?: Partial | undefined; } | undefined", + "required": false + } + ] + }, + "GroupDef": { + "name": "GroupDef", + "kind": "typealias", + "typeParams": [ + { + "name": "T", + "default": "unknown" + } + ], + "definition": "BaseDef<'group'> & {\n nodes: (ItemDef | SubmenuDef)[];\n heading?: string;\n}", + "props": [ + { + "name": "kind", + "type": "\"group\"", + "required": true, + "description": "The kind of node." + }, + { + "name": "id", + "type": "string", + "required": true, + "description": "Unique id for this node." + }, + { + "name": "hidden", + "type": "boolean | undefined", + "required": false + }, + { + "name": "nodes", + "type": "(ItemDef | SubmenuDef)[]", + "required": true + }, + { + "name": "heading", + "type": "string | undefined", + "required": false + } + ] + }, + "ItemDef": { + "name": "ItemDef", + "kind": "typealias", + "typeParams": [ + { + "name": "T", + "default": "unknown" + } + ], + "definition": "BaseDef<'item'> & Searchable & {\n icon?: Iconish;\n /** Arman is a bitch. */\n data?: T;\n onSelect?: (args: {\n node: Omit, 'onSelect'>;\n search?: SearchContext;\n }) => void;\n closeOnSelect?: boolean;\n}", + "props": [ + { + "name": "kind", + "type": "\"item\"", + "required": true, + "description": "The kind of node." + }, + { + "name": "id", + "type": "string", + "required": true, + "description": "Unique id for this node." + }, + { + "name": "hidden", + "type": "boolean | undefined", + "required": false + }, + { + "name": "label", + "type": "string | undefined", + "required": false, + "description": "A human-readable label for the searchable item." + }, + { + "name": "keywords", + "type": "string[] | undefined", + "required": false, + "description": "A list of aliases for the node, used when searching/filtering." + }, + { + "name": "icon", + "type": "Iconish", + "required": false + }, + { + "name": "data", + "type": "T | undefined", + "required": false, + "description": "Arman is a bitch." + }, + { + "name": "onSelect", + "type": "((args: { node: Omit, \"onSelect\">; search?: SearchContext | undefined; }) => void) | undefined", + "required": false + }, + { + "name": "closeOnSelect", + "type": "boolean | undefined", + "required": false + } + ] + }, + "SubmenuDef": { + "name": "SubmenuDef", + "kind": "typealias", + "typeParams": [ + { + "name": "T", + "default": "unknown" + }, + { + "name": "TChild", + "default": "unknown" + } + ], + "definition": "BaseDef<'submenu'> & Searchable & {\n nodes: NodeDef[];\n data?: T;\n icon?: Iconish;\n title?: string;\n inputPlaceholder?: string;\n hideSearchUntilActive?: boolean;\n defaults?: MenuNodeDefaults;\n ui?: {\n slots?: Partial>;\n slotProps?: Partial;\n classNames?: Partial;\n };\n}", + "props": [ + { + "name": "kind", + "type": "\"submenu\"", + "required": true, + "description": "The kind of node." + }, + { + "name": "id", + "type": "string", + "required": true, + "description": "Unique id for this node." + }, + { + "name": "hidden", + "type": "boolean | undefined", + "required": false + }, + { + "name": "label", + "type": "string | undefined", + "required": false, + "description": "A human-readable label for the searchable item." + }, + { + "name": "keywords", + "type": "string[] | undefined", + "required": false, + "description": "A list of aliases for the node, used when searching/filtering." + }, + { + "name": "nodes", + "type": "NodeDef[]", + "required": true + }, + { + "name": "data", + "type": "T | undefined", + "required": false + }, + { + "name": "icon", + "type": "Iconish", + "required": false + }, + { + "name": "title", + "type": "string | undefined", + "required": false + }, + { + "name": "inputPlaceholder", + "type": "string | undefined", + "required": false + }, + { + "name": "hideSearchUntilActive", + "type": "boolean | undefined", + "required": false + }, + { + "name": "defaults", + "type": "MenuNodeDefaults | undefined", + "required": false + }, + { + "name": "ui", + "type": "{ slots?: Partial> | undefined; slotProps?: Partial | undefined; classNames?: Partial<...> | undefined; } | undefined", + "required": false + } + ] + }, + "Menu": { + "name": "Menu", + "kind": "typealias", + "typeParams": [ + { + "name": "T", + "default": "unknown" + } + ], + "definition": "Omit, 'nodes'> & {\n nodes: Node[];\n surfaceId: string;\n depth: number;\n}", + "props": [ + { + "name": "title", + "type": "string | undefined", + "required": false + }, + { + "name": "id", + "type": "string", + "required": true + }, + { + "name": "inputPlaceholder", + "type": "string | undefined", + "required": false + }, + { + "name": "hideSearchUntilActive", + "type": "boolean | undefined", + "required": false + }, + { + "name": "defaults", + "type": "MenuNodeDefaults | undefined", + "required": false + }, + { + "name": "ui", + "type": "{ slots?: Partial> | undefined; slotProps?: Partial | undefined; classNames?: Partial | undefined; } | undefined", + "required": false + }, + { + "name": "nodes", + "type": "Node[]", + "required": true + }, + { + "name": "surfaceId", + "type": "string", + "required": true + }, + { + "name": "depth", + "type": "number", + "required": true + } + ] + }, + "GroupNode": { + "name": "GroupNode", + "kind": "typealias", + "typeParams": [ + { + "name": "T", + "default": "unknown" + } + ], + "definition": "BaseNode<'group', GroupDef> & {\n heading?: string;\n nodes: (ItemNode | SubmenuNode)[];\n}", + "props": [ + { + "name": "kind", + "type": "\"group\"", + "required": true, + "description": "The kind of node." + }, + { + "name": "id", + "type": "string", + "required": true, + "description": "Unique id for this node." + }, + { + "name": "hidden", + "type": "boolean | undefined", + "required": false + }, + { + "name": "parent", + "type": "Menu", + "required": true, + "description": "Owning menu surface at runtime." + }, + { + "name": "def", + "type": "GroupDef", + "required": true, + "description": "Original author definition for this node." + }, + { + "name": "heading", + "type": "string | undefined", + "required": false + }, + { + "name": "nodes", + "type": "(ItemNode | SubmenuNode)[]", + "required": true + } + ] + }, + "ItemNode": { + "name": "ItemNode", + "kind": "typealias", + "typeParams": [ + { + "name": "T", + "default": "unknown" + } + ], + "definition": "BaseNode<'item', ItemDef> & Omit, 'kind' | 'hidden'>", + "props": [ + { + "name": "kind", + "type": "\"item\"", + "required": true, + "description": "The kind of node." + }, + { + "name": "id", + "type": "string", + "required": true, + "description": "Unique id for this node." + }, + { + "name": "hidden", + "type": "boolean | undefined", + "required": false + }, + { + "name": "parent", + "type": "Menu", + "required": true, + "description": "Owning menu surface at runtime." + }, + { + "name": "def", + "type": "ItemDef", + "required": true, + "description": "Original author definition for this node." + }, + { + "name": "data", + "type": "T | undefined", + "required": false, + "description": "Arman is a bitch." + }, + { + "name": "label", + "type": "string | undefined", + "required": false, + "description": "A human-readable label for the searchable item." + }, + { + "name": "keywords", + "type": "string[] | undefined", + "required": false, + "description": "A list of aliases for the node, used when searching/filtering." + }, + { + "name": "icon", + "type": "Iconish", + "required": false + }, + { + "name": "onSelect", + "type": "((args: { node: Omit, \"onSelect\">; search?: SearchContext | undefined; }) => void) | undefined", + "required": false + }, + { + "name": "closeOnSelect", + "type": "boolean | undefined", + "required": false + } + ] + }, + "SubmenuNode": { + "name": "SubmenuNode", + "kind": "typealias", + "typeParams": [ + { + "name": "T", + "default": "unknown" + }, + { + "name": "TChild", + "default": "unknown" + } + ], + "doc": "NOTE: Submenu node exposes its runtime child menu as `child`", + "definition": "BaseNode<'submenu', SubmenuDef> & Omit, 'kind' | 'hidden' | 'nodes'> & {\n child: Menu;\n nodes: Node[];\n}", + "props": [ + { + "name": "kind", + "type": "\"submenu\"", + "required": true, + "description": "The kind of node." + }, + { + "name": "id", + "type": "string", + "required": true, + "description": "Unique id for this node." + }, + { + "name": "hidden", + "type": "boolean | undefined", + "required": false + }, + { + "name": "parent", + "type": "Menu", + "required": true, + "description": "Owning menu surface at runtime." + }, + { + "name": "def", + "type": "SubmenuDef", + "required": true, + "description": "Original author definition for this node." + }, + { + "name": "data", + "type": "T | undefined", + "required": false + }, + { + "name": "label", + "type": "string | undefined", + "required": false, + "description": "A human-readable label for the searchable item." + }, + { + "name": "title", + "type": "string | undefined", + "required": false + }, + { + "name": "keywords", + "type": "string[] | undefined", + "required": false, + "description": "A list of aliases for the node, used when searching/filtering." + }, + { + "name": "icon", + "type": "Iconish", + "required": false + }, + { + "name": "inputPlaceholder", + "type": "string | undefined", + "required": false + }, + { + "name": "hideSearchUntilActive", + "type": "boolean | undefined", + "required": false + }, + { + "name": "defaults", + "type": "MenuNodeDefaults | undefined", + "required": false + }, + { + "name": "ui", + "type": "{ slots?: Partial> | undefined; slotProps?: Partial | undefined; classNames?: Partial<...> | undefined; } | undefined", + "required": false + }, + { + "name": "child", + "type": "Menu", + "required": true + }, + { + "name": "nodes", + "type": "Node[]", + "required": true + } + ] + }, + "MenuNodeKind": { + "name": "MenuNodeKind", + "kind": "typealias", + "definition": "'item' | 'group' | 'submenu'" + }, + "CreateActionMenuResult": { + "name": "CreateActionMenuResult", + "kind": "typealias", + "typeParams": [ + { + "name": "T", + "default": "unknown" + } + ], + "definition": "{\n Root: React.FC;\n Trigger: typeof Trigger;\n Positioner: typeof Positioner;\n Surface: React.ForwardRefExoticComponent & React.RefAttributes>;\n}", + "props": [ + { + "name": "Root", + "type": "FC", + "required": true + }, + { + "name": "Trigger", + "type": "ForwardRefExoticComponent>", + "required": true + }, + { + "name": "Positioner", + "type": "FC", + "required": true + }, + { + "name": "Surface", + "type": "ForwardRefExoticComponent & RefAttributes>", + "required": true + } + ] + } + } + } +} diff --git a/apps/web/app/demos/action-menu/icons.tsx b/apps/web/app/demos/action-menu/icons.tsx new file mode 100644 index 00000000..646eb99d --- /dev/null +++ b/apps/web/app/demos/action-menu/icons.tsx @@ -0,0 +1,407 @@ +/** biome-ignore-all lint/correctness/useUniqueElementIds: not needed */ + +import { cn } from '@/lib/utils' + +export const StatusIcon = () => ( + +) + +export const Status = { + Icebox: () => ( + + + + + ), + Backlog: () => ( + + + + + ), + Todo: () => ( + + + + + ), + InProgress: () => ( + + + + + ), + Done: () => ( + + + + + + ), +} + +export const ProjectStatusIcon = () => ( + +) + +export const ProjectStatus = { + Failed: () => ( + + + + + + + + + + ), + Backlog: () => ( + + + + + + + + + + ), + Planned: () => ( + + + + + + + + + + ), + InProgress: () => ( + + + + + + + + + + ), + Completed: () => ( + + + + + + + + + + + ), + Canceled: () => ( + + + + + + + + + + + ), +} + +export const ProjectPropertiesIcon = () => ( + +) diff --git a/apps/web/app/demos/action-menu/menu.tsx b/apps/web/app/demos/action-menu/menu.tsx new file mode 100644 index 00000000..2fe366b3 --- /dev/null +++ b/apps/web/app/demos/action-menu/menu.tsx @@ -0,0 +1,147 @@ +'use client' + +import type { MenuDef, SubmenuDef } from '@bazza-ui/action-menu' +import { Button } from '@/components/ui/button' +import { ActionMenu } from '@/registry/action-menu' +import { + ProjectPropertiesIcon, + ProjectStatus, + ProjectStatusIcon, + Status, + StatusIcon, +} from './icons' + +const FilterIcon = () => ( + +) + +export const ActionMenuCompoennt = () => ( + + + + + + + + +) + +const statusMenu: SubmenuDef = { + kind: 'submenu', + id: 'status', + icon: , + label: 'Status', + title: 'Status', + inputPlaceholder: 'Status...', + nodes: [ + { + kind: 'item', + id: 'icebox', + label: 'Icebox', + icon: , + }, + { + kind: 'item', + id: 'backlog', + label: 'Backlog', + icon: , + }, + { + kind: 'item', + id: 'todo', + label: 'Todo', + icon: , + }, + { + kind: 'item', + id: 'in-progress', + label: 'In Progress', + icon: , + }, + { + kind: 'item', + id: 'done', + label: 'Done', + icon: , + }, + ], +} + +const projectStatusMenu: SubmenuDef = { + kind: 'submenu', + id: 'project-status', + icon: , + title: 'Project status', + label: 'Project status', + inputPlaceholder: 'Project status...', + hideSearchUntilActive: true, + nodes: [ + { + kind: 'item', + id: 'failed', + label: 'Failed', + icon: , + }, + { + kind: 'item', + id: 'backlog', + label: 'Backlog', + icon: , + }, + { + kind: 'item', + id: 'planned', + label: 'Planned', + icon: , + }, + { + kind: 'item', + id: 'in-progress', + label: 'In Progress', + icon: , + }, + { + kind: 'item', + id: 'completed', + label: 'Completed', + icon: , + }, + { + kind: 'item', + id: 'canceled', + label: 'Canceled', + icon: , + }, + ], +} + +const projectPropertiesMenu: SubmenuDef = { + kind: 'submenu', + id: 'project-properties', + icon: , + title: 'Project properties', + label: 'Project properties', + inputPlaceholder: 'Project properties...', + nodes: [projectStatusMenu], +} + +export const menuData: MenuDef = { + id: 'issue-properties', + nodes: [statusMenu, projectPropertiesMenu], +} diff --git a/apps/web/app/demos/action-menu/page.tsx b/apps/web/app/demos/action-menu/page.tsx new file mode 100644 index 00000000..d876d37b --- /dev/null +++ b/apps/web/app/demos/action-menu/page.tsx @@ -0,0 +1,36 @@ +import ComponentCode from '@/components/component-code' +import { NavBar } from '@/components/nav-bar' +import { ActionMenuCompoennt } from './menu' + +export default function Page() { + return ( +
+
+
+ +
+
+
+
+
+

+ Action Menu +

+
+
+
+ +
+
+
+ {/* Content here! */} + +
+
+
+
+
+
+
+ ) +} diff --git a/apps/web/app/demos/client/tst-static/_/data-table.tsx b/apps/web/app/demos/client/tst-static/_/data-table.tsx index 01824c45..419932eb 100644 --- a/apps/web/app/demos/client/tst-static/_/data-table.tsx +++ b/apps/web/app/demos/client/tst-static/_/data-table.tsx @@ -1,3 +1,4 @@ +import { flexRender, type Table as TanStackTable } from '@tanstack/react-table' import { Button } from '@/components/ui/button' import { Table, @@ -7,16 +8,20 @@ import { TableHeader, TableRow, } from '@/components/ui/table' -import { type Table as TanStackTable, flexRender } from '@tanstack/react-table' +import { format } from './utils' export function DataTable({ table }: { table: TanStackTable }) { + const selectedRows = format(table.getFilteredSelectedRowModel().rows.length) + const totalAvailableRows = format(table.getFilteredRowModel().rows.length) + const totalRows = format(table.getCoreRowModel().rows.length) + return ( <> -
- +
+
{table.getHeaderGroups().map((headerGroup) => ( - + {headerGroup.headers.map((header) => { return ( @@ -65,10 +70,9 @@ export function DataTable({ table }: { table: TanStackTable }) {
- {table.getFilteredSelectedRowModel().rows.length} of{' '} - {table.getFilteredRowModel().rows.length} row(s) selected.{' '} + {selectedRows} of {totalAvailableRows} row(s) selected.{' '} - Total row count: {table.getCoreRowModel().rows.length} + Total row count: {totalRows}
diff --git a/apps/web/app/demos/client/tst-static/_/data.ts b/apps/web/app/demos/client/tst-static/_/data.ts index eaec6f27..3ab2fddf 100644 --- a/apps/web/app/demos/client/tst-static/_/data.ts +++ b/apps/web/app/demos/client/tst-static/_/data.ts @@ -120,11 +120,11 @@ export const ISSUE_STATUSES: IssueStatus[] = [ ] as const export const ISSUE_LABELS: IssueLabel[] = [ - { - id: '550e8401-e29b-41d4-a716-446655440000', - name: 'A super, duper long label for testing overflow behaviour and truncating', - color: 'red', - }, + // { + // id: '550e8401-e29b-41d4-a716-446655440000', + // name: 'A super, duper long label for testing overflow behaviour and truncating', + // color: 'red', + // }, { id: '550e8400-e29b-41d4-a716-446655440000', name: 'Bug', color: 'red' }, { id: '6ba7b810-9dad-11d1-80b4-00c04fd430c8', @@ -553,7 +553,7 @@ export function generateSampleIssue(): Issue { const title = generateIssueTitle() const description = lorem(4, 8) - const labelsCount = randomInteger(0, 5) + const labelsCount = randomInteger(0, 1) const labels = labelsCount > 0 ? (sample(ISSUE_LABELS, labelsCount) as IssueLabel[]) @@ -602,5 +602,5 @@ export function generateIssues(count: number) { } export const ISSUES = generateIssues( - process.env.NODE_ENV === 'production' ? 30000 : 5000, + process.env.NODE_ENV === 'production' ? 100_000 : 30_000, ) diff --git a/apps/web/app/demos/client/tst-static/_/filters.tsx b/apps/web/app/demos/client/tst-static/_/filters.tsx index a4734cae..ec86cd47 100644 --- a/apps/web/app/demos/client/tst-static/_/filters.tsx +++ b/apps/web/app/demos/client/tst-static/_/filters.tsx @@ -23,16 +23,16 @@ declare module '@bazza-ui/filters' { const dtf = createColumnConfigHelper() export const columnsConfig = [ - dtf - .text() - .id('title') - .accessor((row) => row.title) - .displayName('Title') - .icon(Heading1Icon) - .meta({ - foo: 'bar', - }) - .build(), + // dtf + // .text() + // .id('title') + // .accessor((row) => row.title) + // .displayName('Title') + // .icon(Heading1Icon) + // .meta({ + // foo: 'bar', + // }) + // .build(), dtf .option() .accessor((row) => row.status.id) @@ -87,28 +87,28 @@ export const columnsConfig = [ })) .orderFn(['count', 'desc'], ['label', 'asc']) .build(), - dtf - .number() - .accessor((row) => row.estimatedHours) - .id('estimatedHours') - .displayName('Estimated hours') - .icon(ClockIcon) - .min(0) - .max(100) - .build(), - dtf - .date() - .accessor((row) => row.startDate) - .id('startDate') - .displayName('Start Date') - .icon(CalendarArrowUpIcon) - .build(), - dtf - .boolean() - .id('isUrgent') - .accessor((row) => row.isUrgent) - .displayName('Urgent issues') - .toggledStateName('urgent') - .icon(CircleAlertIcon) - .build(), + // dtf + // .number() + // .accessor((row) => row.estimatedHours) + // .id('estimatedHours') + // .displayName('Estimated hours') + // .icon(ClockIcon) + // .min(0) + // .max(100) + // .build(), + // dtf + // .date() + // .accessor((row) => row.startDate) + // .id('startDate') + // .displayName('Start Date') + // .icon(CalendarArrowUpIcon) + // .build(), + // dtf + // .boolean() + // .id('isUrgent') + // .accessor((row) => row.isUrgent) + // .displayName('Urgent issues') + // .toggledStateName('urgent') + // .icon(CircleAlertIcon) + // .build(), ] as const diff --git a/apps/web/app/demos/client/tst-static/_/utils.ts b/apps/web/app/demos/client/tst-static/_/utils.ts index 4a85c166..14d3567f 100644 --- a/apps/web/app/demos/client/tst-static/_/utils.ts +++ b/apps/web/app/demos/client/tst-static/_/utils.ts @@ -15,3 +15,7 @@ export function isAnyOf(value: T, array: T[]) { export async function sleep(ms: number) { return new Promise((resolve) => setTimeout(resolve, ms)) } + +export function format(value: number) { + return new Intl.NumberFormat().format(value) +} diff --git a/apps/web/app/docs/[slug]/layout.tsx b/apps/web/app/docs/[slug]/layout.tsx index 7cb5f145..df9b1f29 100644 --- a/apps/web/app/docs/[slug]/layout.tsx +++ b/apps/web/app/docs/[slug]/layout.tsx @@ -9,12 +9,10 @@ export default function RootLayout({ }>) { return ( -
+
-
- {children} -
+
{children}
) diff --git a/apps/web/app/docs/[slug]/page.tsx b/apps/web/app/docs/[slug]/page.tsx index 9f6875c6..8accc2e6 100644 --- a/apps/web/app/docs/[slug]/page.tsx +++ b/apps/web/app/docs/[slug]/page.tsx @@ -2,30 +2,23 @@ export const dynamic = 'force-static' import { promises as fs } from 'node:fs' import path from 'node:path' -import { components } from '@/components/mdx' import { compileMDX } from 'next-mdx-remote/rsc' -import rehypeAutolinkHeadings from 'rehype-autolink-headings' import rehypeCallouts from 'rehype-callouts' -import rehypePrettyCode from 'rehype-pretty-code' import type { Options as RehypePrettyCodeOptions } from 'rehype-pretty-code' +import rehypePrettyCode from 'rehype-pretty-code' import rehypeSlug from 'rehype-slug' import remarkGfm from 'remark-gfm' +import { components } from '@/components/mdx' import 'rehype-callouts/theme/github' +import { transformerNotationDiff } from '@shikijs/transformers' +import type { Metadata } from 'next' +import { visit } from 'unist-util-visit' import { DashboardTableOfContents } from '@/components/toc' import { Badge } from '@/components/ui/badge' -import { - Breadcrumb, - BreadcrumbItem, - BreadcrumbList, - BreadcrumbPage, - BreadcrumbSeparator, -} from '@/components/ui/breadcrumb' import { SidebarTrigger } from '@/components/ui/sidebar' import { rehypeNpmCommand } from '@/lib/rehype-npm-command' +import { remarkTypeTable } from '@/lib/remark-type-table' import { getTableOfContents } from '@/lib/toc' -import { transformerNotationDiff } from '@shikijs/transformers' -import type { Metadata } from 'next' -import { visit } from 'unist-util-visit' export async function generateMetadata({ params, @@ -106,7 +99,7 @@ export default async function Page({ options: { parseFrontmatter: true, mdxOptions: { - remarkPlugins: [remarkGfm], + remarkPlugins: [remarkGfm, remarkTypeTable], rehypePlugins: [ rehypeSlug, rehypeCallouts, @@ -150,7 +143,6 @@ export default async function Page({ }) }, rehypeNpmCommand, - rehypeAutolinkHeadings, ], }, }, @@ -170,21 +162,10 @@ export default async function Page({ const toc = await getTableOfContents(rawContent) return ( -
+
- - - Docs - - {metadata.section} - - - {metadata.title} - - -
@@ -208,7 +189,7 @@ export default async function Page({
{content}
-
+
{toc && }
diff --git a/apps/web/app/globals.css b/apps/web/app/globals.css index 936de798..f9bc3d1d 100644 --- a/apps/web/app/globals.css +++ b/apps/web/app/globals.css @@ -67,7 +67,7 @@ --foreground: oklch(0.985 0 0); --card: oklch(0.145 0 0); --card-foreground: oklch(0.985 0 0); - --popover: oklch(0.145 0 0); + --popover: oklch(20.5% 0 0); --popover-foreground: oklch(0.985 0 0); --primary: oklch(0.985 0 0); --primary-foreground: oklch(0.205 0 0); @@ -87,7 +87,7 @@ --chart-3: oklch(0.769 0.188 70.08); --chart-4: oklch(0.627 0.265 303.9); --chart-5: oklch(0.645 0.246 16.439); - --sidebar: oklch(0.205 0 0); + --sidebar: oklch(0.05 0 0); --sidebar-foreground: oklch(0.985 0 0); --sidebar-primary: oklch(0.488 0.243 264.376); --sidebar-primary-foreground: oklch(0.985 0 0); @@ -141,7 +141,12 @@ * { cursor: default; } - + + a, + a > * { + cursor: pointer; + } + /* Opt text-based inputs out of "forced" default cursor */ input[type="text"], input[type="password"], @@ -157,7 +162,7 @@ * { @apply border-border outline-ring/50; } - + code:not(h1 code, h2 code, h3 code, h4 code, .callout code) { @apply not-dark:font-[450]; } @@ -192,7 +197,7 @@ .callout { @apply rounded-sm !py-4; - + .callout-title-icon svg { @apply size-4 translate-y-[2px]; } @@ -208,7 +213,7 @@ &[data-callout="note"] { @apply bg-blue-500/10; } - + &[data-callout="important"] { @apply bg-purple-500/10; } @@ -223,7 +228,9 @@ } html.dark .shiki, - html.dark .shiki span { + html.dark .shiki span, + html.dark .shiki-inline, + html.dark .shiki-inline span { color: var(--shiki-dark) !important; background-color: transparent !important; font-style: var(--shiki-dark-font-style) !important; @@ -296,10 +303,8 @@ color: var(--shiki-light); background-color: var(--shiki-light-bg); } - } - html.dark { code[data-theme]:not([data-highlighted-line]), code[data-theme]:not([data-highlighted-line]) span { @@ -319,7 +324,7 @@ @apply rounded-tl-none; } } - + figcaption[data-rehype-pretty-code-caption] { @apply text-sm text-muted-foreground text-center mt-3; } @@ -347,12 +352,14 @@ margin-left: calc(var(--spacing) * -6); margin-top: calc(var(--spacing) * -0.5); height: calc(var(--spacing) * 6); - content: ''; + content: ""; } } [data-line].diff ~ [data-highlighted-line]::before, - [data-highlighted-line] ~ [data-line].diff + [data-highlighted-line]::before { + [data-highlighted-line] + ~ [data-line].diff + + [data-highlighted-line]::before { margin-left: calc(var(--spacing) * -8); } @@ -369,7 +376,7 @@ margin-left: calc(var(--spacing) * -8); margin-top: calc(var(--spacing) * -0.5); height: calc(var(--spacing) * 6); - content: ''; + content: ""; } &:after { @@ -378,13 +385,13 @@ color: var(--color-green-500); font-size: var(--text-base); line-height: 1.3; - content: '+'; + content: "+"; } } [data-line].diff.remove { @apply bg-red-500/15; - + span { @apply opacity-60; } @@ -395,7 +402,7 @@ margin-left: calc(var(--spacing) * -8); margin-top: calc(var(--spacing) * -0.5); height: calc(var(--spacing) * 6); - content: ''; + content: ""; } &:after { @@ -404,7 +411,7 @@ color: var(--color-red-500); font-size: var(--text-base); line-height: 1.3; - content: '-'; + content: "-"; } } @@ -465,5 +472,98 @@ } @utility max-w-screen-* { - max-width: --value(--breakpoint- *); + max-width: --value(--breakpoint-*); +} + +/* optional: some tokens for quick presets */ +@theme { + --grid-xxs: 8px; + --grid-xs: 12px; + --grid-sm: 16px; + --grid-md: 24px; + --grid-lg: 32px; +} + +/* 1px crisp XY grid made from two gradients */ +@utility bg-grid { + /* knobs */ + --grid-size: var(--grid-md); /* cell size (spacing) */ + --grid-line-w: 1px; /* line thickness */ + --grid-color: color-mix(in oklch, currentColor 30%, transparent); + + background-image: + linear-gradient( + to right, + var(--grid-color) var(--grid-line-w), + transparent 0 + ), + linear-gradient( + to bottom, + var(--grid-color) var(--grid-line-w), + transparent 0 + ); + background-size: var(--grid-size) var(--grid-size); + background-position: 0 0; + background-repeat: repeat; +} + +/* optional: “major” lines every N cells (thicker/fainter secondary net) */ +@utility bg-grid-major { + --grid-major-step: 4; /* every N cells */ + --grid-major-w: 1px; + --grid-major-color: color-mix(in oklch, currentColor 55%, transparent); + + background-image: + /* majors on top */ + linear-gradient( + to right, + var(--grid-major-color) var(--grid-major-w), + transparent 0 + ), + linear-gradient( + to bottom, + var(--grid-major-color) var(--grid-major-w), + transparent 0 + ), + /* base grid */ + linear-gradient( + to right, + var(--grid-color) var(--grid-line-w), + transparent 0 + ), + linear-gradient( + to bottom, + var(--grid-color) var(--grid-line-w), + transparent 0 + ); + + background-size: + calc(var(--grid-size) * var(--grid-major-step)) + calc(var(--grid-size) * var(--grid-major-step)), + calc(var(--grid-size) * var(--grid-major-step)) + calc(var(--grid-size) * var(--grid-major-step)), + var(--grid-size) var(--grid-size), + var(--grid-size) var(--grid-size); + background-repeat: repeat; +} + +/* This utility extends the hover/click area on all sides */ +@utility hover-expand-* { + position: relative; + + --hover-radius: --spacing(--value(integer) * -1); + --hover-radius: calc(--value([*]) * -1); + + &::before { + content: ""; + position: absolute; + top: var(--hover-radius); + left: var(--hover-radius); + right: var(--hover-radius); + bottom: var(--hover-radius); + /* By not setting background or borders, this pseudo-element is invisible, + but because it's part of the element's subtree, hovering over it + triggers the parent's :hover state. */ + pointer-events: auto; + } } diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx index 3f5020c3..771267ae 100644 --- a/apps/web/app/layout.tsx +++ b/apps/web/app/layout.tsx @@ -1,19 +1,18 @@ import type { Metadata } from 'next' import './globals.css' +import type { Viewport } from 'next' +import { NuqsAdapter } from 'nuqs/adapters/next/app' import OneDollarStatsScript from '@/app/stats' import ThemeColorUpdater from '@/components/theme-color-updater' +import { Toaster } from '@/components/ui/sonner' import { META_THEME_COLORS } from '@/lib/config' import { env } from '@/lib/env' import { berkeleyMono, inter } from '@/lib/fonts' import { ThemeProvider } from '@/providers/theme-provider' -import type { Viewport } from 'next' -import Head from 'next/head' -import Script from 'next/script' -import { NuqsAdapter } from 'nuqs/adapters/next/app' const title = 'bazza/ui — Hand-crafted, modern React components' const description = - 'A collection of beautiful, modern React components. Open source. Open code. Free to use.' + 'A collection of powerful, modern React components. Open source. Open code. Free to use.' export const metadata: Metadata = { title: { @@ -21,8 +20,7 @@ export const metadata: Metadata = { template: '%s — bazza/ui', }, metadataBase: new URL(env.NEXT_PUBLIC_APP_URL), - description: - 'A collection of beautiful, modern React components. Open source. Open code. Free to use.', + description, keywords: [ 'React', 'shadcn/ui', @@ -110,6 +108,7 @@ export default function RootLayout({
+ diff --git a/apps/web/app/og/route.tsx b/apps/web/app/og/route.tsx index 552b7380..f1cde453 100644 --- a/apps/web/app/og/route.tsx +++ b/apps/web/app/og/route.tsx @@ -1,3 +1,4 @@ +/** biome-ignore-all lint/performance/noImgElement: */ import { readFile } from 'node:fs/promises' import { join } from 'node:path' import { ImageResponse } from 'next/og' @@ -39,15 +40,26 @@ export async function GET(request: Request) { const logoSrc = Uint8Array.from(logoData).buffer return new ImageResponse( -
-
+
+
+
+
+
+
+
+
+
+
- {title} - +

bazza/ui/ ui - @@ -72,7 +73,7 @@ export function AppSidebar() { {item.title} @@ -87,11 +88,22 @@ export function AppSidebar() { Components + + + + Action Menu + + + Data Table Filter diff --git a/apps/web/components/code-block.tsx b/apps/web/components/code-block.tsx index e3d21d52..e193a3d2 100644 --- a/apps/web/components/code-block.tsx +++ b/apps/web/components/code-block.tsx @@ -13,7 +13,6 @@ export const CodeBlockWrapper = ({ className, children, loading, - ...props }: CodeBlockWrapperProps) => { return (
(null) @@ -47,5 +48,9 @@ export function CodeBlock({ void highlight(code, lang).then(setNodes) }, [code, lang]) - return {nodes} + return ( + + {nodes} + + ) } diff --git a/apps/web/components/code-inline.tsx b/apps/web/components/code-inline.tsx new file mode 100644 index 00000000..1bf7538b --- /dev/null +++ b/apps/web/components/code-inline.tsx @@ -0,0 +1,20 @@ +// apps/web/components/CodeInline.tsx +import { highlightInline } from '@/lib/highlighter' +import { cn } from '@/lib/utils' + +type Props = { + code: string + lang?: 'ts' | 'tsx' | 'js' | 'jsx' | 'json' | 'bash' // extend as needed + className?: string +} + +export default async function CodeInline({ + code, + lang = 'ts', + className, +}: Props) { + // If empty, render nothing-ish to keep tables tidy + if (!code) return + const el = await highlightInline(code, lang) + return {el} +} diff --git a/apps/web/components/component-code.tsx b/apps/web/components/component-code.tsx new file mode 100644 index 00000000..4351215d --- /dev/null +++ b/apps/web/components/component-code.tsx @@ -0,0 +1,15 @@ +import fs from 'node:fs/promises' +import path from 'node:path' +import { CodeBlock } from './code-block' + +export default async function ComponentCode({ src }: { src: string }) { + const file = await fs.readFile(path.join(process.cwd(), src), 'utf-8') + const code = file + return ( + + ) +} diff --git a/apps/web/components/component-frame.tsx b/apps/web/components/component-frame.tsx new file mode 100644 index 00000000..ac1c303f --- /dev/null +++ b/apps/web/components/component-frame.tsx @@ -0,0 +1,81 @@ +import { CodeXmlIcon, FrameIcon } from 'lucide-react' +import { cn } from '@/lib/utils' +import ComponentCode from './component-code' +import { ScrollArea, ScrollBar } from './ui/scroll-area' +import { Tabs, TabsContent, TabsList, TabsTrigger } from './ui/tabs' + +export const ComponentFrame = ({ + className, + containerClassName, + previewClassName, + children, + caption, + src, + ...props +}: React.ComponentProps<'div'> & { + containerClassName?: string + previewClassName?: string + caption?: React.ReactNode + src?: string +}) => { + const tabsList = ( + + + + Preview + + + + Code + + + ) + + return ( +
+ + {src && tabsList} +
+ +
+ {children} +
+
+
+ + {src && ( + + + + + + + + )} +
+ + {caption && ( +
+ {caption} +
+ )} +
+ ) +} diff --git a/apps/web/components/components-list.tsx b/apps/web/components/components-list.tsx new file mode 100644 index 00000000..0637f2a9 --- /dev/null +++ b/apps/web/components/components-list.tsx @@ -0,0 +1,51 @@ +import { ResponsiveImage } from './responsive-image' + +export const ComponentsList = () => { + return ( +
+
+
+
+ +
+
+

+ Data table filter +

+

+ Powerful filtering library with modern components, for your next + data table. +

+
+
+
+
+
+
+ +
+
+

+ Action menu +

+

+ Composable menu with deep search and multi-level submenus. +

+
+
+
+
+ ) +} diff --git a/apps/web/components/examples/action-menu/basic.tsx b/apps/web/components/examples/action-menu/basic.tsx new file mode 100644 index 00000000..3f60319c --- /dev/null +++ b/apps/web/components/examples/action-menu/basic.tsx @@ -0,0 +1,62 @@ +'use client' + +import { toast } from 'sonner' +import { Button } from '@/components/ui/button' +import { ActionMenu } from '@/registry/action-menu' + +export function ActionMenu_Basic() { + return ( + + + + + + { + toast(`${node.icon} ${node.label}`) + }, + }, + }, + nodes: [ + { + kind: 'item', + id: 'Apple', + label: 'Apple', + icon: '🍎', + }, + { + kind: 'item', + id: 'Banana', + label: 'Banana', + icon: '🍌', + }, + { + kind: 'item', + id: 'Orange', + label: 'Orange', + icon: '🍊', + }, + { + kind: 'item', + id: 'Pineapple', + label: 'Pineapple', + icon: '🍍', + }, + { + kind: 'item', + id: 'Strawberry', + label: 'Strawberry', + icon: '🍓', + }, + ], + }} + /> + + + ) +} diff --git a/apps/web/components/examples/action-menu/header-footer.tsx b/apps/web/components/examples/action-menu/header-footer.tsx new file mode 100644 index 00000000..0b53f568 --- /dev/null +++ b/apps/web/components/examples/action-menu/header-footer.tsx @@ -0,0 +1,910 @@ +/** biome-ignore-all lint/correctness/noNestedComponentDefinitions: */ + +'use client' + +import type { GroupDef, ItemDef, MenuDef } from '@bazza-ui/action-menu' +import { ListXIcon } from 'lucide-react' +import { useState } from 'react' +import { Button } from '@/components/ui/button' +import { Checkbox } from '@/components/ui/checkbox' +import { cn } from '@/lib/utils' +import { ActionMenu } from '@/registry/action-menu' + +function getMenuItems(menu: MenuDef): ItemDef[] { + const items = + menu.nodes?.filter((node) => !node.hidden && node.kind === 'item') ?? [] + const groups = (menu.nodes?.filter( + (node) => !node.hidden && node.kind === 'group', + ) ?? []) as GroupDef[] + const groupItems = groups?.flatMap((group) => + group.nodes.filter((n) => n.kind === 'item'), + ) + + return [...items, ...groupItems] as ItemDef[] +} + +export function ActionMenu_HeaderFooter() { + const [selectedItems, setSelectedItems] = useState([]) + + function deselectAll() { + setSelectedItems([]) + } + + return ( + + + + + + { + return ( +
+ {menu.title} + + {getMenuItems(menu).length} items + +
+ ) + }, + Footer: () => { + if (selectedItems.length === 0) return null + return ( +
+ +
+ ) + }, + Item: ({ node, bind }) => { + const props = bind.getRowProps({ + className: 'flex items-center gap-2', + }) + + return ( +
+ + {node.icon as string} + {node.label} +
+ ) + }, + }} + menu={{ + id: 'root', + title: 'Fruits', + defaults: { + item: { + onSelect: ({ node }) => { + const checked = selectedItems.includes(node.id) + setSelectedItems((prev) => { + return checked + ? prev.filter((id) => id !== node.id) + : prev.concat(node.id) + }) + }, + }, + }, + nodes: [ + { + kind: 'group', + id: 'fruits', + heading: 'Fruits', + nodes: [ + { + kind: 'item', + id: 'fruit-apple-red', + label: 'Apple', + icon: '🍎', + }, + { + kind: 'item', + id: 'fruit-apple-green', + label: 'Green Apple', + icon: '🍏', + }, + { kind: 'item', id: 'fruit-pear', label: 'Pear', icon: '🍐' }, + { + kind: 'item', + id: 'fruit-tangerine', + label: 'Tangerine', + icon: '🍊', + }, + { + kind: 'item', + id: 'fruit-lemon', + label: 'Lemon', + icon: '🍋', + }, + { + kind: 'item', + id: 'fruit-banana', + label: 'Banana', + icon: '🍌', + }, + { + kind: 'item', + id: 'fruit-watermelon', + label: 'Watermelon', + icon: '🍉', + }, + { + kind: 'item', + id: 'fruit-grapes', + label: 'Grapes', + icon: '🍇', + }, + { + kind: 'item', + id: 'fruit-strawberry', + label: 'Strawberry', + icon: '🍓', + }, + { + kind: 'item', + id: 'fruit-blueberries', + label: 'Blueberries', + icon: '🫐', + }, + { + kind: 'item', + id: 'fruit-cherries', + label: 'Cherries', + icon: '🍒', + }, + { + kind: 'item', + id: 'fruit-peach', + label: 'Peach', + icon: '🍑', + }, + { + kind: 'item', + id: 'fruit-mango', + label: 'Mango', + icon: '🥭', + }, + { + kind: 'item', + id: 'fruit-pineapple', + label: 'Pineapple', + icon: '🍍', + }, + { + kind: 'item', + id: 'fruit-coconut', + label: 'Coconut', + icon: '🥥', + }, + { kind: 'item', id: 'fruit-kiwi', label: 'Kiwi', icon: '🥝' }, + { + kind: 'item', + id: 'fruit-melon', + label: 'Melon', + icon: '🍈', + }, + { + kind: 'item', + id: 'fruit-tomato', + label: 'Tomato', + icon: '🍅', + }, + { + kind: 'item', + id: 'fruit-olive', + label: 'Olive', + icon: '🫒', + }, + ], + }, + { + kind: 'group', + id: 'vegetables', + heading: 'Vegetables', + nodes: [ + { + kind: 'item', + id: 'veg-eggplant', + label: 'Eggplant', + icon: '🍆', + }, + { + kind: 'item', + id: 'veg-avocado', + label: 'Avocado', + icon: '🥑', + }, + { + kind: 'item', + id: 'veg-broccoli', + label: 'Broccoli', + icon: '🥦', + }, + { + kind: 'item', + id: 'veg-leafy-green', + label: 'Leafy Green', + icon: '🥬', + }, + { + kind: 'item', + id: 'veg-cucumber', + label: 'Cucumber', + icon: '🥒', + }, + { + kind: 'item', + id: 'veg-hot-pepper', + label: 'Hot Pepper', + icon: '🌶', + }, + { + kind: 'item', + id: 'veg-bell-pepper', + label: 'Bell Pepper', + icon: '🫑', + }, + { + kind: 'item', + id: 'veg-garlic', + label: 'Garlic', + icon: '🧄', + }, + { kind: 'item', id: 'veg-onion', label: 'Onion', icon: '🧅' }, + { + kind: 'item', + id: 'veg-carrot', + label: 'Carrot', + icon: '🥕', + }, + { + kind: 'item', + id: 'veg-corn', + label: 'Ear of Corn', + icon: '🌽', + }, + { + kind: 'item', + id: 'veg-potato', + label: 'Potato', + icon: '🥔', + }, + { + kind: 'item', + id: 'veg-mushroom', + label: 'Mushroom', + icon: '🍄', + }, + { + kind: 'item', + id: 'veg-sweet-potato', + label: 'Roasted Sweet Potato', + icon: '🍠', + }, + { kind: 'item', id: 'veg-beans', label: 'Beans', icon: '🫘' }, + { + kind: 'item', + id: 'veg-peas', + label: 'Pea Pod', + icon: '🫛', + }, + { + kind: 'item', + id: 'veg-ginger', + label: 'Ginger Root', + icon: '🫚', + }, + ], + }, + { + kind: 'group', + id: 'meats-protein', + heading: 'Meats & Protein', + nodes: [ + { + kind: 'item', + id: 'protein-bacon', + label: 'Bacon', + icon: '🥓', + }, + { + kind: 'item', + id: 'protein-cut-of-meat', + label: 'Cut of Meat', + icon: '🥩', + }, + { + kind: 'item', + id: 'protein-poultry-leg', + label: 'Poultry Leg', + icon: '🍗', + }, + { + kind: 'item', + id: 'protein-meat-on-bone', + label: 'Meat on Bone', + icon: '🍖', + }, + { kind: 'item', id: 'protein-egg', label: 'Egg', icon: '🥚' }, + { + kind: 'item', + id: 'protein-fried-egg', + label: 'Fried Egg', + icon: '🍳', + }, + ], + }, + { + kind: 'group', + id: 'seafood', + heading: 'Seafood', + nodes: [ + { + kind: 'item', + id: 'seafood-fish', + label: 'Fish', + icon: '🐟', + }, + { + kind: 'item', + id: 'seafood-tropical-fish', + label: 'Tropical Fish', + icon: '🐠', + }, + { + kind: 'item', + id: 'seafood-shrimp', + label: 'Shrimp', + icon: '🦐', + }, + { + kind: 'item', + id: 'seafood-lobster', + label: 'Lobster', + icon: '🦞', + }, + { + kind: 'item', + id: 'seafood-crab', + label: 'Crab', + icon: '🦀', + }, + { + kind: 'item', + id: 'seafood-squid', + label: 'Squid', + icon: '🦑', + }, + { + kind: 'item', + id: 'seafood-oyster', + label: 'Oyster', + icon: '🦪', + }, + { + kind: 'item', + id: 'seafood-octopus', + label: 'Octopus', + icon: '🐙', + }, + ], + }, + { + kind: 'group', + id: 'bakery-grains', + heading: 'Bakery & Grains', + nodes: [ + { + kind: 'item', + id: 'bakery-bread', + label: 'Bread', + icon: '🍞', + }, + { + kind: 'item', + id: 'bakery-croissant', + label: 'Croissant', + icon: '🥐', + }, + { + kind: 'item', + id: 'bakery-baguette', + label: 'Baguette', + icon: '🥖', + }, + { + kind: 'item', + id: 'bakery-flatbread', + label: 'Flatbread', + icon: '🫓', + }, + { + kind: 'item', + id: 'bakery-bagel', + label: 'Bagel', + icon: '🥯', + }, + { + kind: 'item', + id: 'bakery-pancakes', + label: 'Pancakes', + icon: '🥞', + }, + { + kind: 'item', + id: 'bakery-waffle', + label: 'Waffle', + icon: '🧇', + }, + ], + }, + { + kind: 'group', + id: 'meals-prepared', + heading: 'Meals & Prepared', + nodes: [ + { + kind: 'item', + id: 'meal-hamburger', + label: 'Hamburger', + icon: '🍔', + }, + { + kind: 'item', + id: 'meal-hot-dog', + label: 'Hot Dog', + icon: '🌭', + }, + { + kind: 'item', + id: 'meal-fries', + label: 'French Fries', + icon: '🍟', + }, + { + kind: 'item', + id: 'meal-pizza', + label: 'Pizza', + icon: '🍕', + }, + { + kind: 'item', + id: 'meal-sandwich', + label: 'Sandwich', + icon: '🥪', + }, + { + kind: 'item', + id: 'meal-stuffed-flatbread', + label: 'Stuffed Flatbread', + icon: '🥙', + }, + { kind: 'item', id: 'meal-taco', label: 'Taco', icon: '🌮' }, + { + kind: 'item', + id: 'meal-burrito', + label: 'Burrito', + icon: '🌯', + }, + { + kind: 'item', + id: 'meal-green-salad', + label: 'Green Salad', + icon: '🥗', + }, + { + kind: 'item', + id: 'meal-shallow-pan', + label: 'Shallow Pan of Food', + icon: '🥘', + }, + { + kind: 'item', + id: 'meal-pot-of-food', + label: 'Pot of Food', + icon: '🍲', + }, + { + kind: 'item', + id: 'meal-curry-rice', + label: 'Curry Rice', + icon: '🍛', + }, + { + kind: 'item', + id: 'meal-spaghetti', + label: 'Spaghetti', + icon: '🍝', + }, + { + kind: 'item', + id: 'meal-steaming-bowl', + label: 'Steaming Bowl (Ramen)', + icon: '🍜', + }, + { + kind: 'item', + id: 'meal-sushi', + label: 'Sushi', + icon: '🍣', + }, + { + kind: 'item', + id: 'meal-bento', + label: 'Bento Box', + icon: '🍱', + }, + { + kind: 'item', + id: 'meal-fried-shrimp', + label: 'Fried Shrimp', + icon: '🍤', + }, + { kind: 'item', id: 'meal-oden', label: 'Oden', icon: '🍢' }, + { + kind: 'item', + id: 'meal-fish-cake', + label: 'Fish Cake with Swirl', + icon: '🍥', + }, + { + kind: 'item', + id: 'meal-dumpling', + label: 'Dumpling', + icon: '🥟', + }, + { + kind: 'item', + id: 'meal-fortune-cookie', + label: 'Fortune Cookie', + icon: '🥠', + }, + { + kind: 'item', + id: 'meal-takeout', + label: 'Takeout Box', + icon: '🥡', + }, + { + kind: 'item', + id: 'meal-tamale', + label: 'Tamale', + icon: '🫔', + }, + { + kind: 'item', + id: 'meal-fondue', + label: 'Fondue', + icon: '🫕', + }, + { + kind: 'item', + id: 'meal-falafel', + label: 'Falafel', + icon: '🧆', + }, + { + kind: 'item', + id: 'meal-bowl-with-spoon', + label: 'Bowl with Spoon', + icon: '🥣', + }, + ], + }, + { + kind: 'group', + id: 'sweets-desserts', + heading: 'Sweets & Desserts', + nodes: [ + { + kind: 'item', + id: 'sweet-soft-ice-cream', + label: 'Soft Ice Cream', + icon: '🍦', + }, + { + kind: 'item', + id: 'sweet-ice-cream', + label: 'Ice Cream', + icon: '🍨', + }, + { + kind: 'item', + id: 'sweet-shaved-ice', + label: 'Shaved Ice', + icon: '🍧', + }, + { + kind: 'item', + id: 'sweet-shortcake', + label: 'Shortcake', + icon: '🍰', + }, + { + kind: 'item', + id: 'sweet-birthday-cake', + label: 'Birthday Cake', + icon: '🎂', + }, + { + kind: 'item', + id: 'sweet-cupcake', + label: 'Cupcake', + icon: '🧁', + }, + { kind: 'item', id: 'sweet-pie', label: 'Pie', icon: '🥧' }, + { + kind: 'item', + id: 'sweet-custard', + label: 'Custard', + icon: '🍮', + }, + { + kind: 'item', + id: 'sweet-chocolate', + label: 'Chocolate Bar', + icon: '🍫', + }, + { + kind: 'item', + id: 'sweet-candy', + label: 'Candy', + icon: '🍬', + }, + { + kind: 'item', + id: 'sweet-lollipop', + label: 'Lollipop', + icon: '🍭', + }, + { + kind: 'item', + id: 'sweet-doughnut', + label: 'Doughnut', + icon: '🍩', + }, + { + kind: 'item', + id: 'sweet-cookie', + label: 'Cookie', + icon: '🍪', + }, + ], + }, + { + kind: 'group', + id: 'snacks-nuts', + heading: 'Snacks & Nuts', + nodes: [ + { + kind: 'item', + id: 'snack-popcorn', + label: 'Popcorn', + icon: '🍿', + }, + { + kind: 'item', + id: 'snack-pretzel', + label: 'Pretzel', + icon: '🥨', + }, + { + kind: 'item', + id: 'snack-peanuts', + label: 'Peanuts', + icon: '🥜', + }, + { + kind: 'item', + id: 'snack-chestnut', + label: 'Chestnut', + icon: '🌰', + }, + ], + }, + { + kind: 'group', + id: 'dairy-eggs', + heading: 'Dairy & Eggs', + nodes: [ + { + kind: 'item', + id: 'dairy-cheese', + label: 'Cheese', + icon: '🧀', + }, + { + kind: 'item', + id: 'dairy-butter', + label: 'Butter', + icon: '🧈', + }, + { kind: 'item', id: 'dairy-egg', label: 'Egg', icon: '🥚' }, + { + kind: 'item', + id: 'dairy-honey', + label: 'Honey', + icon: '🍯', + }, + { + kind: 'item', + id: 'dairy-jar', + label: 'Jar (Jam/Preserve)', + icon: '🫙', + }, + ], + }, + { + kind: 'group', + id: 'condiments-staples', + heading: 'Condiments & Staples', + nodes: [ + { + kind: 'item', + id: 'condiment-salt', + label: 'Salt', + icon: '🧂', + }, + { + kind: 'item', + id: 'condiment-pouring-liquid', + label: 'Pouring Liquid (Oil/Sauce)', + icon: '🫗', + }, + { + kind: 'item', + id: 'condiment-garlic', + label: 'Garlic', + icon: '🧄', + }, + { + kind: 'item', + id: 'condiment-onion', + label: 'Onion', + icon: '🧅', + }, + { + kind: 'item', + id: 'condiment-ginger', + label: 'Ginger Root', + icon: '🫚', + }, + { + kind: 'item', + id: 'condiment-chili', + label: 'Chili Pepper', + icon: '🌶', + }, + { + kind: 'item', + id: 'condiment-olive', + label: 'Olive', + icon: '🫒', + }, + ], + }, + { + kind: 'group', + id: 'drinks', + heading: 'Drinks', + nodes: [ + { + kind: 'item', + id: 'drink-hot-beverage', + label: 'Hot Beverage', + icon: '☕', + }, + { + kind: 'item', + id: 'drink-teacup', + label: 'Teacup Without Handle', + icon: '🍵', + }, + { + kind: 'item', + id: 'drink-teapot', + label: 'Teapot', + icon: '🫖', + }, + { + kind: 'item', + id: 'drink-bubble-tea', + label: 'Bubble Tea', + icon: '🧋', + }, + { + kind: 'item', + id: 'drink-beverage-box', + label: 'Beverage Box', + icon: '🧃', + }, + { + kind: 'item', + id: 'drink-straw-cup', + label: 'Cup with Straw', + icon: '🥤', + }, + { + kind: 'item', + id: 'drink-milk', + label: 'Glass of Milk', + icon: '🥛', + }, + { + kind: 'item', + id: 'drink-baby-bottle', + label: 'Baby Bottle', + icon: '🍼', + }, + { + kind: 'item', + id: 'drink-ice-cube', + label: 'Ice', + icon: '🧊', + }, + { + kind: 'item', + id: 'drink-beer', + label: 'Beer Mug', + icon: '🍺', + }, + { + kind: 'item', + id: 'drink-beers', + label: 'Clinking Beer Mugs', + icon: '🍻', + }, + { + kind: 'item', + id: 'drink-wine', + label: 'Wine Glass', + icon: '🍷', + }, + { + kind: 'item', + id: 'drink-tumbler', + label: 'Tumbler Glass', + icon: '🥃', + }, + { + kind: 'item', + id: 'drink-cocktail', + label: 'Cocktail', + icon: '🍸', + }, + { + kind: 'item', + id: 'drink-tropical', + label: 'Tropical Drink', + icon: '🍹', + }, + { kind: 'item', id: 'drink-sake', label: 'Sake', icon: '🍶' }, + { + kind: 'item', + id: 'drink-champagne', + label: 'Bottle with Popping Cork', + icon: '🍾', + }, + { kind: 'item', id: 'drink-mate', label: 'Mate', icon: '🧉' }, + ], + }, + ], + }} + /> +
+
+ ) +} diff --git a/apps/web/components/examples/action-menu/index.tsx b/apps/web/components/examples/action-menu/index.tsx new file mode 100644 index 00000000..35a6c637 --- /dev/null +++ b/apps/web/components/examples/action-menu/index.tsx @@ -0,0 +1,16 @@ +import { ActionMenu_Basic } from './basic' +import { ActionMenu_HeaderFooter } from './header-footer' +import { ActionMenu_KitchenSink01 } from './kitchen-sink-01' +import { ActionMenu_Notion } from './notion' +import { ActionMenu_Submenus } from './submenus' +import { ActionMenu_SubmenusDeep } from './submenus-deep' + +export const ActionMenu = { + Basic: ActionMenu_Basic, + Submenus: ActionMenu_Submenus, + SubmenusDeep: ActionMenu_SubmenusDeep, + HeaderFooter: ActionMenu_HeaderFooter, + Notion: ActionMenu_Notion, + /***** Kitchen sink examples *****/ + KitchenSink01: ActionMenu_KitchenSink01, +} diff --git a/apps/web/components/examples/action-menu/kitchen-sink-01.tsx b/apps/web/components/examples/action-menu/kitchen-sink-01.tsx new file mode 100644 index 00000000..27f33d52 --- /dev/null +++ b/apps/web/components/examples/action-menu/kitchen-sink-01.tsx @@ -0,0 +1,225 @@ +'use client' + +import { + type MenuDef, + renderIcon, + type SubmenuDef, + type SubmenuNode, +} from '@bazza-ui/action-menu' +import { toast } from 'sonner' +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar' +import { Button } from '@/components/ui/button' +import { ActionMenu } from '@/registry/action-menu' +import { + AssigneeIcon, + ProjectPropertiesIcon, + ProjectStatus, + ProjectStatusIcon, + Status, + StatusIcon, +} from './shared/icons' + +export function ActionMenu_KitchenSink01() { + return ( + + + + + + + + + ) +} + +const FilterIcon = () => ( + +) + +const statusMenu: SubmenuDef = { + kind: 'submenu', + id: 'status', + icon: , + label: 'Status', + title: 'Status', + inputPlaceholder: 'Status...', + nodes: [ + { + kind: 'item', + id: 'icebox', + label: 'Icebox', + icon: , + }, + { + kind: 'item', + id: 'backlog', + label: 'Backlog', + icon: , + }, + { + kind: 'item', + id: 'todo', + label: 'Todo', + icon: , + }, + { + kind: 'item', + id: 'in-progress', + label: 'In Progress', + icon: , + }, + { + kind: 'item', + id: 'done', + label: 'Done', + icon: , + }, + ], +} + +const assigneeMenu: SubmenuDef = { + kind: 'submenu', + id: 'assignee', + icon: , + label: 'Assignee', + title: 'Assignee', + inputPlaceholder: 'Assignee...', + nodes: [ + { + kind: 'item', + id: '@kianbazza', + label: 'Kian Bazza', + icon: ( + + + KB + + ), + }, + { + kind: 'item', + id: '@shadcn', + label: 'shadcn', + icon: ( + + + CN + + ), + }, + { + kind: 'item', + id: '@rauchg', + label: 'Guillermo Rauch', + icon: ( + + + RG + + ), + }, + { + kind: 'item', + id: '@t3dotgg', + label: 'Theo Browne', + icon: ( + + + TB + + ), + }, + ], +} + +const projectStatusMenu: SubmenuDef = { + kind: 'submenu', + id: 'project-status', + icon: , + title: 'Project status', + label: 'Project status', + inputPlaceholder: 'Project status...', + hideSearchUntilActive: true, + nodes: [ + { + kind: 'item', + id: 'failed', + label: 'Failed', + icon: , + }, + { + kind: 'item', + id: 'backlog', + label: 'Backlog', + icon: , + }, + { + kind: 'item', + id: 'planned', + label: 'Planned', + icon: , + }, + { + kind: 'item', + id: 'in-progress', + label: 'In Progress', + icon: , + }, + { + kind: 'item', + id: 'completed', + label: 'Completed', + icon: , + }, + { + kind: 'item', + id: 'canceled', + label: 'Canceled', + icon: , + }, + ], +} + +const projectPropertiesMenu: SubmenuDef = { + kind: 'submenu', + id: 'project-properties', + icon: , + title: 'Project properties', + label: 'Project properties', + inputPlaceholder: 'Project properties...', + nodes: [projectStatusMenu], +} + +export const menuData: MenuDef = { + id: 'issue-properties', + defaults: { + item: { + closeOnSelect: true, + onSelect: ({ node }) => { + toast(`Changed ${node.parent.title?.toLowerCase()} to ${node.label}.`, { + icon: renderIcon(node.icon), + }) + }, + }, + }, + nodes: [statusMenu, assigneeMenu, projectPropertiesMenu], +} diff --git a/apps/web/components/examples/action-menu/notion.tsx b/apps/web/components/examples/action-menu/notion.tsx new file mode 100644 index 00000000..7c593e86 --- /dev/null +++ b/apps/web/components/examples/action-menu/notion.tsx @@ -0,0 +1,249 @@ +/** biome-ignore-all lint/correctness/noNestedComponentDefinitions: allowed */ +/** biome-ignore-all lint/performance/noImgElement: allowed */ +'use client' + +import { type MenuDef, renderIcon } from '@bazza-ui/action-menu' +import { + Heading1Icon, + Heading2Icon, + Heading3Icon, + ListIcon, + ListOrderedIcon, + ListTodoIcon, + TableIcon, + TypeIcon, +} from 'lucide-react' +import { toast } from 'sonner' +import { Button } from '@/components/ui/button' +import { + HoverCard, + HoverCardContent, + HoverCardTrigger, +} from '@/components/ui/hover-card' +import { ActionMenu } from '@/registry/action-menu' + +const MicrophoneTextIcon = (props: React.ComponentProps<'svg'>) => ( + +) + +const AIBlockIcon = (props: React.ComponentProps<'svg'>) => ( + +) + +export function ActionMenu_Notion() { + return ( + + + + + + { + toast(`${node.label} selected.`) + }, + }, + }} + surfaceClassNames={{ + list: 'data-[mode=dropdown]:min-w-[300px]', + }} + slots={{ + Item: ({ node, mode, bind }) => { + const props = bind.getRowProps({ + className: 'w-full justify-between gap-16', + }) + + const Icon = renderIcon(node.icon, 'size-4 shrink-0') + + const ItemRow = ( + + ) + + if (mode !== 'dropdown' || node.data?.description === undefined) + return ItemRow + + return ( + + {ItemRow} + + {node.data?.imageUrl && ( +
+ {node.label} +
+ )} + {node.data?.description} +
+
+ ) + }, + }} + menu={ + { + id: 'root', + hideSearchUntilActive: true, + nodes: [ + { + id: 'suggested', + kind: 'group', + heading: 'Suggested', + nodes: [ + { + kind: 'item', + id: 'ai-meeting-notes', + label: 'AI Meeting Notes', + icon: ( + + ), + data: { + description: 'Turn meetings into organized notes.', + tag: 'Beta', + }, + }, + { + kind: 'item', + id: 'ai-block', + label: 'AI Block', + icon: , + data: { + description: 'Generate content from any instruction.', + tag: 'New', + }, + }, + ], + }, + { + id: 'basic-blocks', + kind: 'group', + heading: 'Basic blocks', + nodes: [ + { + kind: 'item', + id: 'text', + label: 'Text', + icon: TypeIcon, + data: { + description: 'Just start writing with plain text.', + imageUrl: + 'https://www.notion.so/images/tooltips/blocks/text/en-US.87e040db.png', + }, + }, + { + kind: 'item', + id: 'heading-1', + label: 'Heading 1', + icon: Heading1Icon, + data: { + description: 'Big section heading.', + imageUrl: + 'https://www.notion.so/images/tooltips/blocks/header/en-US.2f63ac1a.png', + kbd: '#', + }, + }, + { + kind: 'item', + id: 'heading-2', + label: 'Heading 2', + icon: Heading2Icon, + data: { + description: 'Medium section heading.', + imageUrl: + 'https://notion.so/images/tooltips/blocks/sub-header/en-US.4ad81c48.png', + kbd: '##', + }, + }, + { + kind: 'item', + id: 'heading-3', + label: 'Heading 3', + icon: Heading3Icon, + data: { + description: 'Small section heading.', + imageUrl: + 'https://notion.so/images/tooltips/blocks/subsubheader/en-US.4dec63f8.png', + kbd: '###', + }, + }, + { + kind: 'item', + id: 'bulleted-list', + label: 'Bulleted list', + icon: ListIcon, + data: { + description: 'Create a simple bulleted list.', + imageUrl: + 'https://notion.so/images/tooltips/blocks/bulleted-list/en-US.f5ded41e.png', + kbd: '-', + }, + }, + { + kind: 'item', + id: 'numbered-list', + label: 'Numbered list', + icon: ListOrderedIcon, + data: { + description: 'Create a list with numbering.', + imageUrl: + 'https://notion.so/images/tooltips/blocks/numbered-list/en-US.58fef67f.png', + kbd: '1.', + }, + }, + { + kind: 'item', + id: 'todo-list', + label: 'To-do list', + icon: ListTodoIcon, + data: { + description: 'Track tasks with a to-do list.', + imageUrl: + 'https://notion.so/images/tooltips/blocks/to-do-list/en-US.52a514f9.png', + kbd: '[]', + }, + }, + { + kind: 'item', + id: 'table', + label: 'Table', + icon: TableIcon, + data: { + description: 'Add simple tabular content to your page.', + imageUrl: + 'https://notion.so/images/tooltips/blocks/simple-table/en-US.da4792c9.png', + }, + }, + ], + }, + ], + } satisfies MenuDef + } + /> +
+
+ ) +} diff --git a/apps/web/components/examples/action-menu/shared/icons.tsx b/apps/web/components/examples/action-menu/shared/icons.tsx new file mode 100644 index 00000000..04a69cb6 --- /dev/null +++ b/apps/web/components/examples/action-menu/shared/icons.tsx @@ -0,0 +1,427 @@ +/** biome-ignore-all lint/correctness/useUniqueElementIds: not needed */ + +import { cn } from '@/lib/utils' + +export const StatusIcon = () => ( + +) + +export const Status = { + Icebox: () => ( + + + + + ), + Backlog: () => ( + + + + + ), + Todo: () => ( + + + + + ), + InProgress: () => ( + + + + + ), + Done: () => ( + + + + + + ), +} + +export const ProjectStatusIcon = () => ( + +) + +export const ProjectStatus = { + Failed: () => ( + + + + + + + + + + ), + Backlog: () => ( + + + + + + + + + + ), + Planned: () => ( + + + + + + + + + + ), + InProgress: () => ( + + + + + + + + + + ), + Completed: () => ( + + + + + + + + + + + ), + Canceled: () => ( + + + + + + + + + + + ), +} + +export const ProjectPropertiesIcon = () => ( + +) + +export const AssigneeIcon = () => ( + +) diff --git a/apps/web/components/examples/action-menu/submenus-deep.tsx b/apps/web/components/examples/action-menu/submenus-deep.tsx new file mode 100644 index 00000000..da078fd8 --- /dev/null +++ b/apps/web/components/examples/action-menu/submenus-deep.tsx @@ -0,0 +1,324 @@ +'use client' + +import { + BookOpen, + Bug, + FileText, + FileType, + FileType2, + FolderOpen, + Image, + Keyboard, + Languages, + Lightbulb, + MenuIcon, + Moon, + Palette, + Presentation, + Redo, + Replace, + Ruler, + Search, + Shapes, + SpellCheck, + Sun, + Table, + Undo, + X, + ZoomIn, + ZoomOut, +} from 'lucide-react' +import { toast } from 'sonner' +import { Button } from '@/components/ui/button' +import { ActionMenu } from '@/registry/action-menu' + +export function ActionMenu_SubmenusDeep() { + return ( + + + + + + { + toast(`Selected ${node.label} from ${node.parent.title}.`) + }, + }, + }, + nodes: [ + { + kind: 'submenu', + id: 'file', + label: 'File', + title: 'File', + nodes: [ + { + kind: 'submenu', + id: 'new', + label: 'New', + title: 'New', + nodes: [ + { + kind: 'item', + id: 'new-doc', + label: 'Document', + icon: FileText, + }, + { + kind: 'item', + id: 'new-sheet', + label: 'Spreadsheet', + icon: Table, + }, + { + kind: 'item', + id: 'new-slide', + label: 'Presentation', + icon: Presentation, + }, + ], + }, + { + kind: 'submenu', + id: 'export', + label: 'Export', + title: 'Export', + nodes: [ + { + kind: 'item', + id: 'export-pdf', + label: 'PDF', + icon: FileType, + }, + { + kind: 'item', + id: 'export-docx', + label: 'Word (.docx)', + icon: FileType2, + }, + { + kind: 'submenu', + id: 'export-image', + label: 'Image', + title: 'Image', + nodes: [ + { + kind: 'item', + id: 'export-png', + label: 'PNG', + icon: Image, + }, + { + kind: 'item', + id: 'export-jpg', + label: 'JPG', + icon: Image, + }, + { + kind: 'submenu', + id: 'export-vector', + label: 'Vector', + title: 'Vector', + nodes: [ + { + kind: 'item', + id: 'export-svg', + label: 'SVG', + icon: Shapes, + }, + { + kind: 'item', + id: 'export-eps', + label: 'EPS', + icon: Ruler, + }, + ], + }, + ], + }, + ], + }, + { kind: 'item', id: 'close', label: 'Close', icon: X }, + ], + }, + { + kind: 'submenu', + id: 'edit', + label: 'Edit', + title: 'Edit', + nodes: [ + { kind: 'item', id: 'undo', label: 'Undo', icon: Undo }, + { kind: 'item', id: 'redo', label: 'Redo', icon: Redo }, + { + kind: 'submenu', + id: 'find-replace', + label: 'Find & Replace', + title: 'Find & Replace', + nodes: [ + { kind: 'item', id: 'find', label: 'Find', icon: Search }, + { + kind: 'item', + id: 'replace', + label: 'Replace', + icon: Replace, + }, + ], + }, + ], + }, + { + kind: 'submenu', + id: 'view', + label: 'View', + title: 'View', + nodes: [ + { + kind: 'item', + id: 'zoom-in', + label: 'Zoom In', + icon: ZoomIn, + }, + { + kind: 'item', + id: 'zoom-out', + label: 'Zoom Out', + icon: ZoomOut, + }, + { + kind: 'submenu', + id: 'themes', + label: 'Themes', + title: 'Themes', + nodes: [ + { + kind: 'item', + id: 'light-theme', + label: 'Light', + icon: Sun, + }, + { + kind: 'item', + id: 'dark-theme', + label: 'Dark', + icon: Moon, + }, + { + kind: 'submenu', + id: 'custom-theme', + label: 'Custom', + title: 'Custom', + nodes: [ + { + kind: 'item', + id: 'color-picker', + label: 'Pick Colors', + icon: Palette, + }, + { + kind: 'item', + id: 'import-theme', + label: 'Import Theme', + icon: FolderOpen, + }, + ], + }, + ], + }, + ], + }, + { + kind: 'submenu', + id: 'tools', + label: 'Tools', + title: 'Tools', + nodes: [ + { + kind: 'item', + id: 'spellcheck', + label: 'Spelling & Grammar', + icon: SpellCheck, + }, + { + kind: 'submenu', + id: 'translate', + label: 'Translate', + title: 'Translate', + nodes: [ + { + kind: 'item', + id: 'to-english', + label: 'English', + icon: Languages, + }, + { + kind: 'item', + id: 'to-french', + label: 'French', + icon: Languages, + }, + { + kind: 'item', + id: 'to-spanish', + label: 'Spanish', + icon: Languages, + }, + ], + }, + ], + }, + { + kind: 'submenu', + id: 'help', + label: 'Help', + title: 'Help', + nodes: [ + { + kind: 'item', + id: 'docs', + label: 'Documentation', + icon: BookOpen, + }, + { + kind: 'item', + id: 'keyboard-shortcuts', + label: 'Keyboard Shortcuts', + icon: Keyboard, + }, + { + kind: 'submenu', + id: 'feedback', + label: 'Feedback', + title: 'Feedback', + nodes: [ + { + kind: 'item', + id: 'report-bug', + label: 'Report a Bug', + icon: Bug, + }, + { + kind: 'item', + id: 'feature-request', + label: 'Request a Feature', + icon: Lightbulb, + }, + ], + }, + ], + }, + ], + }} + /> + + + ) +} diff --git a/apps/web/components/examples/action-menu/submenus.tsx b/apps/web/components/examples/action-menu/submenus.tsx new file mode 100644 index 00000000..8d82a8dc --- /dev/null +++ b/apps/web/components/examples/action-menu/submenus.tsx @@ -0,0 +1,131 @@ +'use client' + +import { toast } from 'sonner' +import { Button } from '@/components/ui/button' +import { ActionMenu } from '@/registry/action-menu' + +export function ActionMenu_Submenus() { + return ( + + + + + + { + toast(`${node.icon} ${node.label}`) + }, + }, + }, + nodes: [ + { + kind: 'submenu', + id: 'fruits', + label: 'Fruits', + nodes: [ + { + kind: 'item', + id: 'Apple', + label: 'Apple', + icon: '🍎', + }, + { + kind: 'item', + id: 'Banana', + label: 'Banana', + icon: '🍌', + }, + { + kind: 'item', + id: 'Orange', + label: 'Orange', + icon: '🍊', + }, + { + kind: 'item', + id: 'Pineapple', + label: 'Pineapple', + icon: '🍍', + }, + { + kind: 'item', + id: 'Strawberry', + label: 'Strawberry', + icon: '🍓', + }, + ], + }, + { + kind: 'submenu', + id: 'vegetables', + label: 'Vegetables', + nodes: [ + { + kind: 'item', + id: 'Carrot', + label: 'Carrot', + icon: '🥕', + }, + { + kind: 'item', + id: 'Broccoli', + label: 'Broccoli', + icon: '🥦', + }, + { + kind: 'item', + id: 'Cauliflower', + label: 'Cauliflower', + icon: '🥐', + }, + { + kind: 'item', + id: 'Tomato', + label: 'Tomato', + icon: '🍅', + }, + ], + }, + { + kind: 'submenu', + id: 'meats', + label: 'Meats', + nodes: [ + { + kind: 'item', + id: 'Chicken', + label: 'Chicken', + icon: '🐔', + }, + { + kind: 'item', + id: 'Beef', + label: 'Beef', + icon: '🐮', + }, + { + kind: 'item', + id: 'Pork', + label: 'Pork', + icon: '🐷', + }, + { + kind: 'item', + id: 'Lamb', + label: 'Lamb', + icon: '🐶', + }, + ], + }, + ], + }} + /> + + + ) +} diff --git a/apps/web/components/examples/index.tsx b/apps/web/components/examples/index.tsx new file mode 100644 index 00000000..536872ed --- /dev/null +++ b/apps/web/components/examples/index.tsx @@ -0,0 +1,5 @@ +import { ActionMenu } from './action-menu' + +export const Examples = { + ActionMenu, +} diff --git a/apps/web/components/mdx.tsx b/apps/web/components/mdx.tsx index 2a6487c4..a8c674d4 100644 --- a/apps/web/components/mdx.tsx +++ b/apps/web/components/mdx.tsx @@ -1,61 +1,124 @@ +import { LinkIcon } from 'lucide-react' +import type { MDXComponents } from 'mdx/types' +import Image from 'next/image' import { IssuesTableWrapper } from '@/app/demos/client/tst-static/_/issues-table-wrapper' import { TypeTable } from '@/components/type-table' import { cn } from '@/lib/utils' import type { NpmCommands } from '@/types/unist' -import type { MDXComponents } from 'mdx/types' -import Image from 'next/image' import { CodeBlockCommand } from './code-block-command' +import CodeInline from './code-inline' import CollapsibleCodeBlock from './collapsible-code-block' +import ComponentCode from './component-code' +import { ComponentFrame } from './component-frame' +import { ComponentsList } from './components-list' +import { Examples } from './examples' +import PropRow from './prop-row' +import { PropsTable } from './props-table' import { ResponsiveImage } from './responsive-image' -export const components: Readonly = { - h1: (props) => ( -

- ), - h2: (props) => ( -

( + + {children} + code]:text-2xl', - props.className, + 'size-4 text-muted-foreground/50 inline align-middle group-hover:text-muted-foreground pointer-events-none', + iconClassName, )} - {...props} /> + +) + +export const components: Readonly = { + h1: (props) => ( +

), - h3: (props) => ( + h2: ({ children, className, ...props }) => { + return ( +

code]:text-2xl', + className, + )} + {...props} + > + {props.id ? ( + {children} + ) : ( + children + )} +

+ ) + }, + h3: ({ children, className, ...props }) => (

a]:no-underline flex items-center gap-2', '[&>code]:text-xl', - props.className, + className, )} {...props} - /> + > + {props.id ? ( + + {children} + + ) : ( + children + )} +

), - h4: (props) => ( + h4: ({ children, className, ...props }) => (

a]:no-underline flex items-center gap-2', '[&>code]:text-lg', - props.className, + className, )} {...props} - /> + > + {props.id ? ( + + {children} + + ) : ( + children + )} +

), - h5: (props) => ( + h5: ({ children, className, ...props }) => (
code]:text-base', - props.className, + className, )} {...props} - /> + > + {props.id ? ( + + {children} + + ) : ( + children + )} +
), h6: (props) =>
, p: (props) =>

, a: (props) => ( - + ), u: (props) => , strong: ({ className, ...props }: React.HTMLAttributes) => ( @@ -103,12 +166,9 @@ export const components: Readonly = {


), table: ({ className, ...props }: React.HTMLAttributes) => ( -
+

@@ -122,7 +182,7 @@ export const components: Readonly = { th: ({ className, ...props }: React.HTMLAttributes) => ( A subset of Linear's filter menu, recreated with the bazza/ui action menu.} +> + + + +The action menu is a primitive component for building menu interfaces. + +At its core, it combines the best features of the dropdown and command menus available today, while adding deep search capabilities. + +## Features + +- Data-first API with headless, slot-based rendering. +- Supports submenus with [intent zones](https://linear.app/now/invisible-details) for easy, fluid pointer navigation. +- Deep search across submenus; descendants are searchable from ancestors. +- Fully typed with generics for your own menu data, customizable per submenu. +- Full keyboard navigation with vim-style keybindings built-in. +- Responsive design; renders a dropdown on desktop and a [drawer](https://vaul.emilkowal.ski) on mobile. +- Supports modal and non-modal modes. +- Customize side, alignment, offsets, collision handling. +- Focus is fully managed; follows the `aria-activedescendant` pattern. + +## Installation + +Install the unstyled, primitive component as an npm package: + +```bash +npm install @bazza-ui/action-menu +``` + +Want something styled with sensible defaults? Install from our registry with one command. + +- Installs the `@bazza-ui/action-menu` package +- Creates the `@/components/ui/action-menu.tsx` file with a styled `ActionMenu` component + +```bash +npx shadcn@latest add https://ui.bazza.dev/r/action-menu +``` + +## Anatomy + +```tsx + + + + + + +``` + +### Root + +Provides the shell (dropdown or drawer), the open/close state, and cross-surface focus ownership. + +You can control visibility with `defaultOpen` for uncontrolled state, or `open` and `onOpenChange` for controlled state. + +You can determine whether pointer events outside the menu are blocked by setting `modal` (defaults to `true`). + +### Trigger + +A button that toggles the menu. + +In dropdown mode, it acts as the `Popper` anchor, and in drawer mode it renders as a Vaul `Drawer.Trigger`. + +### Positioner + +Positions a surface in dropdown mode and is used automatically for submenus. + +It is a no-op for the root surface in drawer mode. + +### Surface + +Contains the search input, menu groups and rows, as well as an optional header and footer. + +You render one surface for the root menu. Submenus automatically render their own surfaces. + +## Data + +This component uses a data-first API. You define your menu as a `MenuDef` object and pass it to the root `ActionMenu.Surface` component: + +```tsx {17} +const menu: MenuDef = { + nodes: [ + { kind: 'item', id: 'Apple', label: 'Apple', icon: '🍎' }, + { kind: 'item', id: 'Banana', label: 'Banana', icon: '🍌' }, + { kind: 'item', id: 'Orange', label: 'Orange', icon: '🍊' }, + { kind: 'item', id: 'Pineapple', label: 'Pineapple', icon: '🍍' }, + { kind: 'item', id: 'Strawberry', label: 'Strawberry', icon: '🍓' }, + ], +} + +const FruitsMenu = () => { + return ( + + Open + + + + + ) +} +``` + +## Nodes + +Each node in the `MenuDef` describes what to render and how it should behave. + +The library ships three kinds of nodes — item, group, and submenu. + +Their shapes are defined by the `ItemDef`, `GroupDef`, and `SubmenuDef` types. + +### Item + +```ts +{ + kind: 'item', + id: 'done', + label: 'Done', + icon: , + onSelect: ({ node, search }) => { + setStatus('done') + toast(`Changed status to ${node.label}.`) + } + closeOnSelect: true +} +``` + +The atomic unit of the menu. It is a single row which performs an action when selected. + +Each item must have a unique `id` among other sibling items. It does **not** have to be unique across submenus. + +The action is defined by the `onSelect` handler, which gives you access to: + +- The `node` instance, typed as `ItemNode`. +- The `search` context, if the item is appearing in a filtered menu. + +#### API reference + +##### ItemDef + + + +##### ItemNode + + + +### Group + +Groups items and submenus together with a `heading`. + +##### API reference + + + +### Submenu + +A submenu of actions that the user can run. + +##### API reference + + + +## Factory + +_In progress._ + +## Examples + +### Basic + + + + + +### Submenus + + + + + +### Nested submenus + + + + + +### Header + footer + + + + + +### Notion + + + + + +
diff --git a/apps/web/content/docs/components.mdx b/apps/web/content/docs/components.mdx new file mode 100644 index 00000000..917e500b --- /dev/null +++ b/apps/web/content/docs/components.mdx @@ -0,0 +1,6 @@ +--- +title: Components +summary: Browse the bazza/ui components. +--- + + diff --git a/apps/web/content/docs/data-table-filter.mdx b/apps/web/content/docs/data-table-filter.mdx index ae39b56d..d2f68fda 100644 --- a/apps/web/content/docs/data-table-filter.mdx +++ b/apps/web/content/docs/data-table-filter.mdx @@ -5,9 +5,12 @@ summary: A powerful data table filter component. Library-agnostic. Supports clie badge: alpha --- -
+ -
+ ## Introduction @@ -1023,65 +1026,3 @@ We can break down the `PropertyFilterValueController` as an example: /> The `PropertyFilterOperatorController` has a similar composition and can be inferred from the above description and image. */} - -## Changelog - -### 2025.04.12 - -We've squashed a pesky bug where inferred column options would show duplicate entries in the filter menu. - -We've updated the implementation of `uniq()` to use deep equality checks, instead of the previous referencial equality checks via `new Set()`. - -- Issue: https://github.com/kianbazza/ui/issues/49 -- PR: https://github.com/kianbazza/ui/pull/51 - -### 2025.04.01 - -> [!DANGER] This is a breaking change. - -This adds support for filtering columns where the column value is not strictly a property of the original data. This was not possible before, due to the limitation of `defineMeta`'s first argument, which only accepted a direct property on the initial data type. - -You can now filter columns where the value is: - -- a deeply nested property (i.e. `user.name`) -- accessed using a function (i.e. `row => row.user.name.split(' ')[0]`) - -To accomplish this, we've decided to change the interface for the `defineMeta` helper function. The first property is now an **accessor function**, instead of an accessor key. - -See the example below for how to migrate: - -```ts -type Issue = { - status: string - user: { - name: string - } -} -``` - -```ts {15-22} -const columns = [ - /* ... */ - columnHelper.accessor('status', { - meta: defineMeta( - 'status', // [!code --] - row => row.status, // [!code ++] - { - type: 'option', - icon: CircleDotDashedIcon, - options: ISSUE_STATUSES, - } - ), - }), - columnHelper.accessor(row => row.user.name, { - meta: defineMeta( - row => row.user.name, - { - type: 'option', - icon: AvatarIcon, - /* ... */ - } - ), - }), -] -``` diff --git a/apps/web/hooks/use-mobile.ts b/apps/web/hooks/use-mobile.ts index 4331d5c5..eee1ce94 100644 --- a/apps/web/hooks/use-mobile.ts +++ b/apps/web/hooks/use-mobile.ts @@ -1,6 +1,6 @@ import * as React from 'react' -const MOBILE_BREAKPOINT = 768 +const MOBILE_BREAKPOINT = 1024 export function useIsMobile() { const [isMobile, setIsMobile] = React.useState(undefined) diff --git a/apps/web/lib/highlighter.ts b/apps/web/lib/highlighter.ts index 49740e69..2c3f2764 100644 --- a/apps/web/lib/highlighter.ts +++ b/apps/web/lib/highlighter.ts @@ -2,6 +2,7 @@ import { transformerNotationDiff, transformerNotationHighlight, } from '@shikijs/transformers' +import type { Element, Root } from 'hast' import { toJsxRuntime } from 'hast-util-to-jsx-runtime' import type { JSX } from 'react' import { Fragment } from 'react' @@ -28,3 +29,45 @@ export async function highlight(code: string, lang: BundledLanguage) { jsxs, }) as JSX.Element } + +export async function highlightInline(code: string, lang: BundledLanguage) { + const hast = (await codeToHast(code, { + lang, + themes: { light: 'github-light', dark: 'github-dark' }, + transformers: [transformerNotationDiff(), transformerNotationHighlight()], + colorReplacements: { + '#24292e': 'oklch(0.205 0 0)', + }, + })) as Root + + // Expect:
[tokens]
+ const pre = hast.children?.[0] as Element | undefined + const codeEl = + pre?.type === 'element' + ? pre.children?.find( + (n): n is Element => n.type === 'element' && n.tagName === 'code', + ) + : undefined + + // Synthesize a standalone (inline-safe). + const inlineCode: Element = codeEl ?? { + type: 'element', + tagName: 'code', + properties: { className: ['shiki-inline'] }, + children: [], + } + + // Add an inline-specific class for styling if you like. + const className = new Set([ + 'shiki-inline', + ...(Array.isArray(inlineCode.properties?.className) + ? (inlineCode.properties!.className as string[]) + : []), + ]) + inlineCode.properties = { + ...(inlineCode.properties ?? {}), + className: Array.from(className), + } + + return toJsxRuntime(inlineCode, { Fragment, jsx, jsxs }) +} diff --git a/apps/web/lib/remark-type-table.ts b/apps/web/lib/remark-type-table.ts new file mode 100644 index 00000000..39151e4b --- /dev/null +++ b/apps/web/lib/remark-type-table.ts @@ -0,0 +1,145 @@ +// docs/plugins/remark-type-table.ts (or wherever you keep it) +import { readFileSync } from 'node:fs' +import { join } from 'node:path' +import type { Root } from 'mdast' +import type { MdxJsxFlowElement } from 'mdast-util-mdx-jsx' +import type { Plugin } from 'unified' +import { visit } from 'unist-util-visit' + +type PropMeta = { + name: string + type: string + required: boolean + default?: string + description?: string +} + +type TypeMeta = { + name: string + kind: 'interface' | 'typealias' | 'enum' + typeParams?: Array<{ name: string; constraint?: string; default?: string }> + doc?: string + props?: PropMeta[] + definition?: string +} + +type PackageMeta = { + entrypoint: string + types: Record +} + +type TypesIndex = Record + +const getTypes = (): TypesIndex => { + try { + const p = join(process.cwd(), '.types', 'types-meta.json') + return JSON.parse(readFileSync(p, 'utf-8')) + } catch (e) { + console.error( + '[remark-type-table] failed to read .types/types-meta.json', + e, + ) + return {} + } +} + +function getAttrString( + node: MdxJsxFlowElement, + name: string, +): string | undefined { + const attr = node.attributes?.find( + (a: any) => a.type === 'mdxJsxAttribute' && a.name === name, + ) as any + return typeof attr?.value === 'string' ? attr.value : undefined +} + +export const remarkTypeTable: Plugin<[], Root> = () => { + const typesIndex = getTypes() + + return (tree) => { + visit(tree, 'mdxJsxFlowElement', (node: MdxJsxFlowElement, idx, parent) => { + if (node.name !== 'TypeTable') return + if (!parent || typeof idx !== 'number') return + + const pkgName = getAttrString(node, 'pkg') + const typeName = getAttrString(node, 'type') + + if (!pkgName || !typeName) return + + const pkg = typesIndex[pkgName] + const meta: TypeMeta | undefined = pkg?.types?.[typeName] + if (!meta) { + // Replace with a tiny warning paragraph so the page still builds. + parent.children.splice(idx, 1, { + type: 'paragraph', + children: [ + { type: 'text', value: 'Type ' }, + { type: 'inlineCode', value: `${pkgName}:${typeName}` }, + { type: 'text', value: ' not found.' }, + ], + } as any) + return + } + + const rows = (meta.props ?? []).map((p) => { + const attributes: any[] = [ + { type: 'mdxJsxAttribute', name: 'name', value: p.name }, + { type: 'mdxJsxAttribute', name: 'type', value: p.type }, + ] + + if (p.required) { + attributes.push({ + type: 'mdxJsxAttribute', + name: 'required', + }) + } + + if (p.default !== undefined) { + attributes.push({ + type: 'mdxJsxAttribute', + name: 'defaultValue', + value: p.default, + }) + } + + if (p.description) { + attributes.push({ + type: 'mdxJsxAttribute', + name: 'description', + value: p.description, + }) + } + + const row = { + type: 'mdxJsxFlowElement', + name: 'PropRow', + attributes, + children: [], + } + + return row + }) as any[] + + // Container for rows (handy for styling layout) + const container: MdxJsxFlowElement = { + type: 'mdxJsxFlowElement', + name: 'PropsTable', + attributes: [ + // { + // type: 'mdxJsxAttribute', + // name: 'name', + // value: meta.name, + // }, + ], + children: rows, + } + + const nodes: any[] = [] + // if (maybeDoc) nodes.push(maybeDoc) + // if (maybeDef) nodes.push(maybeDef) + if (rows) nodes.push(container) + + parent.children.splice(idx, 1, ...nodes) + }) + } +} diff --git a/apps/web/next.config.ts b/apps/web/next.config.ts index 20488c15..3beed9dc 100644 --- a/apps/web/next.config.ts +++ b/apps/web/next.config.ts @@ -44,6 +44,11 @@ const nextConfig: NextConfig = { destination: '/r/data-table-filter-tst.json', permanent: false, }, + { + source: '/r/action-menu', + destination: '/r/action-menu.json', + permanent: false, + }, { source: '/changelog', destination: '/changelog/latest', diff --git a/apps/web/package.json b/apps/web/package.json index bebdb6bc..f0486ed2 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -4,7 +4,8 @@ "private": true, "scripts": { "dev": "next dev --turbopack", - "build": "next build", + "gen-types": "tsx scripts/build-types-meta.ts --out .types/types-meta.json --tsconfig tsconfig.docs.json --pkg @bazza-ui/action-menu=../../packages/action-menu/src/index.ts", + "build": "next build --turbopack", "vercel:install": "./vercel-submodule.sh && bun install", "start": "next start", "lint": "next lint", @@ -16,20 +17,24 @@ "registry:build": "shadcn build && biome format --write" }, "dependencies": { + "@bazza-ui/action-menu": "workspace:*", "@bazza-ui/filters": "workspace:*", "@ndaidong/txtgen": "^4.0.1", "@radix-ui/react-avatar": "^1.1.3", "@radix-ui/react-checkbox": "^1.3.1", + "@radix-ui/react-collapsible": "^1.1.12", "@radix-ui/react-dialog": "^1.1.13", "@radix-ui/react-dropdown-menu": "^2.1.14", + "@radix-ui/react-hover-card": "^1.1.15", "@radix-ui/react-label": "^2.1.6", "@radix-ui/react-popover": "^1.1.13", + "@radix-ui/react-scroll-area": "^1.2.10", "@radix-ui/react-select": "^2.1.6", "@radix-ui/react-separator": "^1.1.6", "@radix-ui/react-slider": "^1.3.4", "@radix-ui/react-slot": "^1.2.2", "@radix-ui/react-switch": "^1.1.3", - "@radix-ui/react-tabs": "^1.1.11", + "@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-tooltip": "^1.1.8", "@t3-oss/env-nextjs": "^0.12.0", "@tanstack/react-query": "^5.72.2", @@ -41,15 +46,16 @@ "jotai": "^2.12.2", "lucide-react": "^0.482.0", "nanoid": "^5.1.5", - "next": "^15.4.5", + "next": "^15.5.2", "next-mdx-remote": "^5.0.0", - "next-themes": "^0.4.4", + "next-themes": "^0.4.6", "nuqs": "^2.4.1", "react": "^19.1.0", "react-day-picker": "8.10.1", "react-dom": "^19.1.0", "remeda": "^2.26.1", "shadcn": "2.4.0-canary.13", + "sonner": "^2.0.7", "tailwind-merge": "^3.0.2", "tailwindcss-animate": "^1.0.7", "vaul": "^1.1.2", @@ -65,6 +71,7 @@ "@types/react": "^19.1.1", "@types/react-dom": "^19.1.2", "@types/unist": "^3.0.3", + "chokidar": "^4.0.3", "husky": "^9.1.7", "jsdom": "^26.0.0", "mdast-util-toc": "^7.1.0", @@ -79,6 +86,8 @@ "remark-gfm": "^4.0.1", "shiki": "^3.2.1", "tailwindcss": "^4.1.0", + "ts-morph": "^27.0.0", + "tsx": "^4.20.5", "typescript": "^5", "unist-builder": "^3.0.0", "unist-util-visit": "^5.0.0" diff --git a/apps/web/public/action-menu/dark.png b/apps/web/public/action-menu/dark.png new file mode 100644 index 00000000..49392321 Binary files /dev/null and b/apps/web/public/action-menu/dark.png differ diff --git a/apps/web/public/action-menu/light.png b/apps/web/public/action-menu/light.png new file mode 100644 index 00000000..6c712833 Binary files /dev/null and b/apps/web/public/action-menu/light.png differ diff --git a/apps/web/public/r/action-menu.json b/apps/web/public/r/action-menu.json new file mode 100644 index 00000000..57d5beb6 --- /dev/null +++ b/apps/web/public/r/action-menu.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "action-menu", + "type": "registry:component", + "title": "Action menu", + "dependencies": ["@bazza-ui/action-menu@canary", "lucide-react"], + "files": [ + { + "path": "registry/action-menu/index.tsx", + "content": "import { createActionMenu, renderIcon } from '@bazza-ui/action-menu'\nimport { ChevronRightIcon } from 'lucide-react'\nimport { Fragment } from 'react'\nimport { cn } from '@/lib/utils'\n\nconst TriangleRightIcon = ({\n ...props\n}: React.HTMLAttributes) => {\n return (\n \n \n \n )\n}\n\nexport const LabelWithBreadcrumbs = ({\n label,\n breadcrumbs,\n}: {\n label: string\n breadcrumbs?: string[]\n}) => (\n
\n {breadcrumbs?.map((crumb, idx) => (\n \n {crumb}\n \n \n ))}\n {label}\n
\n)\n\nexport const ActionMenu = createActionMenu({\n defaults: {\n content: {\n onCloseAutoClear: 300,\n },\n },\n shell: {\n classNames: {\n overlay: cn(\n 'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/40 h-full w-full',\n ),\n drawerContent: cn(\n 'group/drawer-content border rounded-lg bg-popover fixed z-50 flex h-auto flex-col min-h-0 overflow-hidden shadow-lg',\n 'data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80svh] data-[vaul-drawer-direction=top]:rounded-lg',\n 'data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-4 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80svh] data-[vaul-drawer-direction=bottom]:rounded-lg data-[vaul-drawer-direction=bottom]:mx-4',\n ),\n drawerHandle: cn(\n 'bg-muted mx-auto mt-4 mb-2 hidden h-2 w-[100px] shrink-0 rounded-full group-data-[vaul-drawer-direction=bottom]/drawer-content:block',\n ),\n },\n slotProps: {\n drawerRoot: {\n shouldScaleBackground: true,\n },\n },\n },\n surface: {\n slots: {\n Item: ({ node, bind, search }) => {\n const props = bind.getRowProps({\n className: 'group/row',\n })\n\n return (\n
\n {node.icon && (\n
\n {renderIcon(\n node.icon,\n 'size-4 shrink-0 text-muted-foreground group-data-[focused=true]/row:text-primary',\n )}\n
\n )}\n \n
\n )\n },\n SubmenuTrigger: ({ node, bind, search }) => {\n const props = bind.getRowProps({})\n\n return (\n
\n
\n {node.icon && (\n
\n {renderIcon(\n node.icon,\n 'size-4 shrink-0 text-muted-foreground group-data-[focused=true]:text-primary',\n )}\n
\n )}\n \n
\n \n
\n )\n },\n Empty: () => (\n
\n No matching options.\n
\n ),\n },\n classNames: {\n content: cn(\n 'data-[mode=dropdown]:border bg-popover rounded-lg z-50 text-sm shadow-md origin-(--radix-popper-transform-origin) flex flex-col h-full w-full min-h-0 overflow-hidden',\n 'data-[root-menu]:data-[state=open]:animate-in data-[root-menu]:data-[state=closed]:animate-out data-[root-menu]:data-[state=closed]:fade-out-0 data-[root-menu]:data-[state=open]:fade-in-0 data-[root-menu]:data-[state=closed]:zoom-out-95 data-[root-menu]:data-[state=open]:zoom-in-95 data-[root-menu]:data-[side=bottom]:slide-in-from-top-2 data-[root-menu]:data-[side=left]:slide-in-from-right-2 data-[root-menu]:data-[side=right]:slide-in-from-left-2 data-[root-menu]:data-[side=top]:slide-in-from-bottom-2',\n ),\n\n list: cn(\n 'p-1 flex flex-col w-full scroll-py-1 overflow-x-hidden overflow-y-auto outline-none',\n 'data-[mode=dropdown]:min-w-[200px] data-[mode=dropdown]:max-w-[500px] data-[mode=dropdown]:max-h-[300px]',\n 'data-[mode=drawer]:flex-1 data-[mode=drawer]:max-w-full',\n 'data-[mode=drawer]:[&_[data-action-menu-group-heading]]:px-5',\n ),\n input: cn(\n 'outline-hidden disabled:cursor-not-allowed disabled:opacity-50 min-h-9 max-h-9 px-4 placeholder-muted-foreground/70 focus-visible:placeholder-muted-foreground placeholder:transition-[color] placeholder:duration-50 placeholder:ease-in-out border-b caret-blue-500',\n 'data-[mode=drawer]:px-6',\n ),\n group: cn('not-last:mb-2'),\n groupHeading: cn('text-xs font-medium text-muted-foreground px-3 my-1'),\n item: cn(\n 'group flex items-center gap-2 rounded-md px-3 py-1.5 text-sm select-none',\n 'data-[focused=true]:bg-accent data-[focused=true]:text-accent-foreground',\n 'data-[mode=drawer]:px-5',\n ),\n subtrigger: cn(\n \"group w-full flex items-center justify-between data-[focused=true]:bg-accent data-[focused=true]:text-accent-foreground relative cursor-default gap-4 rounded-md py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground\",\n 'data-[mode=dropdown]:px-3',\n 'data-[mode=drawer]:px-5',\n 'relative',\n ),\n },\n },\n})\n", + "type": "registry:file", + "target": "components/ui/action-menu.tsx" + } + ] +} diff --git a/apps/web/public/r/data-table-filter.json b/apps/web/public/r/data-table-filter.json index f30acd2a..b0eb9c1c 100644 --- a/apps/web/public/r/data-table-filter.json +++ b/apps/web/public/r/data-table-filter.json @@ -66,7 +66,7 @@ }, { "path": "registry/data-table-filter/components/filter-value.tsx", - "content": "import {\n type Column,\n type ColumnDataType,\n type ColumnOptionExtended,\n createNumberRange,\n type DataTableFilterActions,\n type FilterModel,\n type FilterOperators,\n type FilterStrategy,\n filterTypeOperatorDetails,\n type Locale,\n type MinMaxReturn,\n numberFilterOperators,\n t,\n take,\n} from '@bazza-ui/filters'\nimport { format, isEqual } from 'date-fns'\nimport { Ellipsis } from 'lucide-react'\nimport {\n cloneElement,\n isValidElement,\n memo,\n useCallback,\n useEffect,\n useMemo,\n useState,\n} from 'react'\nimport type { DateRange } from 'react-day-picker'\nimport { Button } from '@/components/ui/button'\nimport { Calendar } from '@/components/ui/calendar'\nimport { Checkbox } from '@/components/ui/checkbox'\nimport {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n CommandSeparator,\n} from '@/components/ui/command'\nimport {\n Popover,\n PopoverAnchor,\n PopoverContent,\n PopoverTrigger,\n} from '@/components/ui/popover'\nimport { Slider } from '@/components/ui/slider'\nimport { Switch } from '@/components/ui/switch'\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'\nimport { cn } from '@/lib/utils'\nimport { useDebounceCallback } from '../hooks/use-debounce-callback'\nimport { DebouncedInput } from '../ui/debounced-input'\n\ninterface FilterValueProps {\n filter: FilterModel\n column: Column\n actions: DataTableFilterActions\n strategy: FilterStrategy\n locale?: Locale\n entityName?: string\n}\n\nexport const FilterValue = memo(__FilterValue) as typeof __FilterValue\n\nfunction __FilterValue({\n filter,\n column,\n actions,\n strategy,\n locale,\n entityName,\n}: FilterValueProps) {\n // Don't open the value controller for boolean columns\n // We can toggle the filter operator instead\n function handleClick(e: React.MouseEvent) {\n if (column.type === 'boolean') e.preventDefault()\n }\n\n return (\n \n \n \n \n \n \n \n \n \n \n \n )\n}\n\ninterface FilterValueDisplayProps {\n filter: FilterModel\n column: Column\n actions: DataTableFilterActions\n locale?: Locale\n entityName?: string\n}\n\nexport function FilterValueDisplay({\n filter,\n column,\n actions,\n locale = 'en',\n entityName,\n}: FilterValueDisplayProps) {\n switch (column.type) {\n case 'option':\n return (\n }\n column={column as Column}\n actions={actions}\n locale={locale}\n />\n )\n case 'multiOption':\n return (\n }\n column={column as Column}\n actions={actions}\n locale={locale}\n />\n )\n case 'date':\n return (\n }\n column={column as Column}\n actions={actions}\n locale={locale}\n />\n )\n case 'text':\n return (\n }\n column={column as Column}\n actions={actions}\n locale={locale}\n />\n )\n case 'number':\n return (\n }\n column={column as Column}\n actions={actions}\n locale={locale}\n />\n )\n case 'boolean':\n return (\n }\n column={column as Column}\n actions={actions}\n locale={locale}\n entityName={entityName}\n />\n )\n default:\n return null\n }\n}\n\nexport function FilterValueOptionDisplay({\n filter,\n column,\n actions,\n locale = 'en',\n}: FilterValueDisplayProps) {\n const options = useMemo(() => column.getOptions(), [column])\n const selected = options.filter((o) => filter?.values.includes(o.value))\n\n // We display the selected options based on how many are selected\n //\n // If there is only one option selected, we display its icon and label\n //\n // If there are multiple options selected, we display:\n // 1) up to 3 icons of the selected options\n // 2) the number of selected options\n if (selected.length === 1 && selected[0]) {\n const { label, icon: Icon } = selected[0]\n const hasIcon = !!Icon\n return (\n \n {hasIcon &&\n (isValidElement(Icon) ? (\n Icon\n ) : (\n \n ))}\n {label}\n \n )\n }\n const name = column.displayName.toLowerCase()\n // TODO: Better pluralization for different languages\n const pluralName = name.endsWith('s') ? `${name}es` : `${name}s`\n\n const hasOptionIcons = !options?.some((o) => !o.icon)\n\n return (\n
\n {hasOptionIcons &&\n take(selected, 3).map(({ value, icon }) => {\n const Icon = icon!\n return isValidElement(Icon) ? (\n Icon\n ) : (\n \n )\n })}\n \n {selected.length} {pluralName}\n \n
\n )\n}\n\nexport function FilterValueMultiOptionDisplay({\n filter,\n column,\n actions,\n locale = 'en',\n}: FilterValueDisplayProps) {\n const options = useMemo(() => column.getOptions(), [column])\n const selected = options.filter((o) => filter.values.includes(o.value))\n\n if (selected.length === 1 && selected[0]) {\n const { label, icon: Icon } = selected[0]\n const hasIcon = !!Icon\n return (\n \n {hasIcon &&\n (isValidElement(Icon) ? (\n Icon\n ) : (\n \n ))}\n\n {label}\n \n )\n }\n\n const name = column.displayName.toLowerCase()\n\n const hasOptionIcons = !options?.some((o) => !o.icon)\n\n return (\n
\n {hasOptionIcons && (\n
\n {take(selected, 3).map(({ value, icon }) => {\n const Icon = icon!\n return isValidElement(Icon) ? (\n cloneElement(Icon, { key: value })\n ) : (\n \n )\n })}\n
\n )}\n \n {selected.length} {name}\n \n
\n )\n}\n\nfunction formatDateRange(start: Date, end: Date) {\n const sameMonth = start.getMonth() === end.getMonth()\n const sameYear = start.getFullYear() === end.getFullYear()\n\n if (sameMonth && sameYear) {\n return `${format(start, 'MMM d')} - ${format(end, 'd, yyyy')}`\n }\n\n if (sameYear) {\n return `${format(start, 'MMM d')} - ${format(end, 'MMM d, yyyy')}`\n }\n\n return `${format(start, 'MMM d, yyyy')} - ${format(end, 'MMM d, yyyy')}`\n}\n\nexport function FilterValueDateDisplay({\n filter,\n column,\n actions,\n locale = 'en',\n}: FilterValueDisplayProps) {\n if (!filter) return null\n if (filter.values.length === 0) return \n if (filter.values.length === 1 && filter.values[0]) {\n const value = filter.values[0]\n\n const formattedDateStr = format(value, 'MMM d, yyyy')\n\n return {formattedDateStr}\n }\n if (filter.values.length === 2 && filter.values[0] && filter.values[1]) {\n const formattedRangeStr = formatDateRange(\n filter.values[0],\n filter.values[1],\n )\n\n return {formattedRangeStr}\n }\n\n return null\n}\n\nexport function FilterValueTextDisplay({\n filter,\n column,\n actions,\n locale = 'en',\n}: FilterValueDisplayProps) {\n if (!filter) return null\n if (\n filter.values.length === 0 ||\n (filter.values[0] && filter.values[0].trim() === '')\n )\n return \n\n const value = filter.values[0]\n\n return {value}\n}\n\nexport function FilterValueNumberDisplay({\n filter,\n column,\n actions,\n locale = 'en',\n}: FilterValueDisplayProps) {\n if (!filter || !filter.values || filter.values.length === 0) return null\n\n if (\n filter.operator === 'is between' ||\n filter.operator === 'is not between'\n ) {\n const minValue = filter.values[0]\n const maxValue = filter.values[1]\n\n return (\n \n {minValue} {t('and', locale)} {maxValue}\n \n )\n }\n\n const value = filter.values[0]\n return {value}\n}\n\nexport function FilterValueBooleanDisplay({\n filter,\n column,\n}: FilterValueDisplayProps) {\n if (!filter || filter.values.length === 0) return null\n return {column.toggledStateName}\n}\n\n/****** Property Filter Value Controller ******/\n\ninterface FilterValueControllerProps {\n filter: FilterModel\n column: Column\n actions: DataTableFilterActions\n strategy: FilterStrategy\n locale?: Locale\n}\n\nexport const FilterValueController = memo(\n __FilterValueController,\n) as typeof __FilterValueController\n\nfunction __FilterValueController({\n filter,\n column,\n actions,\n strategy,\n locale = 'en',\n}: FilterValueControllerProps) {\n switch (column.type) {\n case 'option':\n return (\n }\n column={column as Column}\n actions={actions}\n strategy={strategy}\n locale={locale}\n />\n )\n case 'multiOption':\n return (\n }\n column={column as Column}\n actions={actions}\n strategy={strategy}\n locale={locale}\n />\n )\n case 'date':\n return (\n }\n column={column as Column}\n actions={actions}\n strategy={strategy}\n locale={locale}\n />\n )\n case 'text':\n return (\n }\n column={column as Column}\n actions={actions}\n strategy={strategy}\n locale={locale}\n />\n )\n case 'number':\n return (\n }\n column={column as Column}\n actions={actions}\n strategy={strategy}\n locale={locale}\n />\n )\n default:\n return null\n }\n}\n\ninterface OptionItemProps {\n option: ColumnOptionExtended\n onToggle: (value: string, checked: boolean) => void\n}\n\n// Memoized option item to prevent re-renders unless its own props change\nconst OptionItem = memo(function OptionItem({\n option,\n onToggle,\n}: OptionItemProps) {\n const { value, label, icon: Icon, selected, count } = option\n const handleSelect = useCallback(() => {\n onToggle(value, !selected)\n }, [onToggle, value, selected])\n\n return (\n \n
\n \n
\n {Icon &&\n (isValidElement(Icon) ? (\n Icon\n ) : (\n \n ))}\n
\n \n {label}\n \n
\n {count && (\n \n {new Intl.NumberFormat().format(count)}\n \n )}\n \n )\n})\n\nexport function FilterValueOptionController({\n filter,\n column,\n actions,\n locale = 'en',\n}: FilterValueControllerProps) {\n // Derive the initial selected values on mount\n const initialSelectedValues = useMemo(() => new Set(filter?.values || []), [])\n\n // Separate the selected and unselected options\n const { selectedOptions, unselectedOptions } = useMemo(() => {\n const counts = column.getFacetedUniqueValues()\n const allOptions = column.getOptions().map((o) => {\n const currentlySelected = filter?.values.includes(o.value) ?? false\n return {\n ...o,\n selected: currentlySelected,\n count: counts?.get(o.value) ?? 0,\n }\n })\n\n const selected = allOptions.filter((o) =>\n initialSelectedValues.has(o.value),\n )\n const unselected = allOptions.filter(\n (o) => !initialSelectedValues.has(o.value),\n )\n return { selectedOptions: selected, unselectedOptions: unselected }\n }, [column, filter?.values, initialSelectedValues])\n\n const handleToggle = useCallback(\n (value: string, checked: boolean) => {\n if (checked) actions.addFilterValue(column, [value])\n else actions.removeFilterValue(column, [value])\n },\n [actions, column],\n )\n\n return (\n \n \n {t('noresults', locale)}\n \n \n {selectedOptions.map((option) => (\n \n ))}\n \n {/* Only show separator if there are both selected AND unselected options */}\n \n \n {unselectedOptions.map((option) => (\n \n ))}\n \n \n \n )\n}\n\nexport function FilterValueMultiOptionController({\n filter,\n column,\n actions,\n locale = 'en',\n}: FilterValueControllerProps) {\n // Derive the initial selected values on mount\n const initialSelectedValues = useMemo(() => new Set(filter?.values || []), [])\n\n // Separate the selected and unselected options\n const { selectedOptions, unselectedOptions } = useMemo(() => {\n const counts = column.getFacetedUniqueValues()\n const allOptions = column.getOptions().map((o) => {\n const currentlySelected = filter?.values.includes(o.value) ?? false\n return {\n ...o,\n selected: currentlySelected,\n count: counts?.get(o.value) ?? 0,\n }\n })\n\n const selected = allOptions.filter((o) =>\n initialSelectedValues.has(o.value),\n )\n const unselected = allOptions.filter(\n (o) => !initialSelectedValues.has(o.value),\n )\n return { selectedOptions: selected, unselectedOptions: unselected }\n }, [column, filter?.values, initialSelectedValues])\n\n const handleToggle = useCallback(\n (value: string, checked: boolean) => {\n if (checked) actions.addFilterValue(column, [value])\n else actions.removeFilterValue(column, [value])\n },\n [actions, column],\n )\n\n return (\n \n \n {t('noresults', locale)}\n \n \n {selectedOptions.map((option) => (\n \n ))}\n \n {/* Only show separator if there are both selected AND unselected options */}\n \n \n {unselectedOptions.map((option) => (\n \n ))}\n \n \n \n )\n}\n\nexport function FilterValueDateController({\n filter,\n column,\n actions,\n}: FilterValueControllerProps) {\n const [date, setDate] = useState({\n from: filter?.values[0] ?? new Date(),\n to: filter?.values[1] ?? undefined,\n })\n\n function changeDateRange(value: DateRange | undefined) {\n const start = value?.from\n const end =\n start && value && value.to && !isEqual(start, value.to)\n ? value.to\n : undefined\n\n setDate({ from: start, to: end })\n\n const isRange = start && end\n const newValues = isRange ? [start, end] : start ? [start] : []\n\n actions.setFilterValue(column, newValues)\n }\n\n return (\n \n \n \n
\n \n
\n
\n
\n
\n )\n}\n\nexport function FilterValueTextController({\n filter,\n column,\n actions,\n locale = 'en',\n}: FilterValueControllerProps) {\n const changeText = (value: string | number) => {\n actions.setFilterValue(column, [String(value)])\n }\n\n return (\n \n \n \n \n \n \n \n \n \n )\n}\n\nexport function FilterValueNumberController({\n filter,\n column,\n actions,\n locale = 'en',\n}: FilterValueControllerProps) {\n const minMax = useMemo(\n () => column.getFacetedMinMaxValues() as MinMaxReturn<'number'>,\n [column],\n )\n const [sliderMin, sliderMax] = [\n minMax ? minMax[0] : 0,\n minMax ? minMax[1] : 0,\n ]\n\n // Local state for values\n const [values, setValues] = useState(filter?.values ?? [0, 0])\n\n // Sync with parent filter changes\n useEffect(() => {\n if (\n filter?.values &&\n filter.values.length === values.length &&\n filter.values.every((v, i) => v === values[i])\n ) {\n setValues(filter.values)\n }\n }, [filter?.values, values])\n\n const isNumberRange =\n // filter && values.length === 2\n filter && numberFilterOperators[filter.operator].target === 'multiple'\n\n const setFilterOperatorDebounced = useDebounceCallback(\n actions.setFilterOperator,\n 500,\n )\n const setFilterValueDebounced = useDebounceCallback(\n actions.setFilterValue,\n 500,\n )\n\n const changeNumber = (value: number[]) => {\n setValues(value)\n setFilterValueDebounced(column as any, value)\n }\n\n const changeMinNumber = (value: number) => {\n const newValues = createNumberRange([value, values[1]!])\n setValues(newValues)\n setFilterValueDebounced(column as any, newValues)\n }\n\n const changeMaxNumber = (value: number) => {\n const newValues = createNumberRange([values[0]!, value])\n setValues(newValues)\n setFilterValueDebounced(column as any, newValues)\n }\n\n const changeType = useCallback(\n (type: 'single' | 'range') => {\n let newValues: number[] = []\n if (type === 'single')\n newValues = [values[0]!] // Keep the first value for single mode\n else if (!minMax)\n newValues = createNumberRange([values[0]!, values[1] ?? 0])\n else {\n const value = values[0]!\n newValues =\n value - minMax[0] < minMax[1] - value\n ? createNumberRange([value, minMax[1]])\n : createNumberRange([minMax[0], value])\n }\n\n const newOperator = type === 'single' ? 'is' : 'is between'\n\n // Update local state\n setValues(newValues)\n\n // Cancel in-flight debounced calls to prevent flicker/race conditions\n setFilterOperatorDebounced.cancel()\n setFilterValueDebounced.cancel()\n\n // Update global filter state atomically\n actions.setFilterOperator(column.id, newOperator)\n actions.setFilterValue(column, newValues)\n },\n [values, column, actions, minMax],\n )\n\n return (\n \n \n \n
\n changeType(v as 'single' | 'range')}\n >\n \n {t('single', locale)}\n {t('range', locale)}\n \n \n {minMax && (\n changeNumber(value)}\n min={sliderMin}\n max={sliderMax}\n step={1}\n aria-orientation=\"horizontal\"\n />\n )}\n
\n \n {t('value', locale)}\n \n changeNumber([Number(v)])}\n />\n
\n
\n \n {minMax && (\n \n )}\n
\n
\n \n {t('min', locale)}\n \n changeMinNumber(Number(v))}\n />\n
\n
\n \n {t('max', locale)}\n \n changeMaxNumber(Number(v))}\n />\n
\n
\n
\n \n
\n
\n
\n
\n )\n}\n\nexport function FilterValueBooleanController({\n filter,\n column,\n actions,\n}: FilterValueControllerProps) {\n const handleChange = (value: boolean) => {\n actions.setFilterValue(column, [value])\n }\n\n return (\n \n \n \n \n \n \n \n \n \n )\n}\n\n// TODO: Add support for `bigint` filtering\n", + "content": "/** biome-ignore-all lint/correctness/useUniqueElementIds: */\nimport {\n type Column,\n type ColumnDataType,\n type ColumnOptionExtended,\n createNumberRange,\n type DataTableFilterActions,\n type FilterModel,\n type FilterOperators,\n type FilterStrategy,\n filterTypeOperatorDetails,\n type Locale,\n type MinMaxReturn,\n numberFilterOperators,\n t,\n take,\n} from '@bazza-ui/filters'\nimport { format, isEqual } from 'date-fns'\nimport { Ellipsis } from 'lucide-react'\nimport {\n cloneElement,\n isValidElement,\n memo,\n useCallback,\n useEffect,\n useMemo,\n useState,\n} from 'react'\nimport type { DateRange } from 'react-day-picker'\nimport { Button } from '@/components/ui/button'\nimport { Calendar } from '@/components/ui/calendar'\nimport { Checkbox } from '@/components/ui/checkbox'\nimport {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n CommandSeparator,\n} from '@/components/ui/command'\nimport {\n Popover,\n PopoverAnchor,\n PopoverContent,\n PopoverTrigger,\n} from '@/components/ui/popover'\nimport { Slider } from '@/components/ui/slider'\nimport { Switch } from '@/components/ui/switch'\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'\nimport { cn } from '@/lib/utils'\nimport { useDebounceCallback } from '../hooks/use-debounce-callback'\nimport { DebouncedInput } from '../ui/debounced-input'\n\ninterface FilterValueProps {\n filter: FilterModel\n column: Column\n actions: DataTableFilterActions\n strategy: FilterStrategy\n locale?: Locale\n entityName?: string\n}\n\nexport const FilterValue = memo(__FilterValue) as typeof __FilterValue\n\nfunction __FilterValue({\n filter,\n column,\n actions,\n strategy,\n locale,\n entityName,\n}: FilterValueProps) {\n // Don't open the value controller for boolean columns\n // We can toggle the filter operator instead\n function handleClick(e: React.MouseEvent) {\n if (column.type === 'boolean') e.preventDefault()\n }\n\n return (\n \n \n \n \n \n \n \n \n \n \n \n )\n}\n\ninterface FilterValueDisplayProps {\n filter: FilterModel\n column: Column\n actions: DataTableFilterActions\n locale?: Locale\n entityName?: string\n}\n\nexport function FilterValueDisplay({\n filter,\n column,\n actions,\n locale = 'en',\n entityName,\n}: FilterValueDisplayProps) {\n switch (column.type) {\n case 'option':\n return (\n }\n column={column as Column}\n actions={actions}\n locale={locale}\n />\n )\n case 'multiOption':\n return (\n }\n column={column as Column}\n actions={actions}\n locale={locale}\n />\n )\n case 'date':\n return (\n }\n column={column as Column}\n actions={actions}\n locale={locale}\n />\n )\n case 'text':\n return (\n }\n column={column as Column}\n actions={actions}\n locale={locale}\n />\n )\n case 'number':\n return (\n }\n column={column as Column}\n actions={actions}\n locale={locale}\n />\n )\n case 'boolean':\n return (\n }\n column={column as Column}\n actions={actions}\n locale={locale}\n entityName={entityName}\n />\n )\n default:\n return null\n }\n}\n\nexport function FilterValueOptionDisplay({\n filter,\n column,\n actions,\n locale = 'en',\n}: FilterValueDisplayProps) {\n const options = useMemo(() => column.getOptions(), [column])\n const selected = options.filter((o) => filter?.values.includes(o.value))\n\n // We display the selected options based on how many are selected\n //\n // If there is only one option selected, we display its icon and label\n //\n // If there are multiple options selected, we display:\n // 1) up to 3 icons of the selected options\n // 2) the number of selected options\n if (selected.length === 1 && selected[0]) {\n const { label, icon: Icon } = selected[0]\n const hasIcon = !!Icon\n return (\n \n {hasIcon &&\n (isValidElement(Icon) ? (\n Icon\n ) : (\n \n ))}\n {label}\n \n )\n }\n const name = column.displayName.toLowerCase()\n // TODO: Better pluralization for different languages\n const pluralName = name.endsWith('s') ? `${name}es` : `${name}s`\n\n const hasOptionIcons = !options?.some((o) => !o.icon)\n\n return (\n
\n {hasOptionIcons &&\n take(selected, 3).map(({ value, icon }) => {\n const Icon = icon!\n return isValidElement(Icon) ? (\n Icon\n ) : (\n \n )\n })}\n \n {selected.length} {pluralName}\n \n
\n )\n}\n\nexport function FilterValueMultiOptionDisplay({\n filter,\n column,\n actions,\n locale = 'en',\n}: FilterValueDisplayProps) {\n const options = useMemo(() => column.getOptions(), [column])\n const selected = options.filter((o) => filter.values.includes(o.value))\n\n if (selected.length === 1 && selected[0]) {\n const { label, icon: Icon } = selected[0]\n const hasIcon = !!Icon\n return (\n \n {hasIcon &&\n (isValidElement(Icon) ? (\n Icon\n ) : (\n \n ))}\n\n {label}\n \n )\n }\n\n const name = column.displayName.toLowerCase()\n\n const hasOptionIcons = !options?.some((o) => !o.icon)\n\n return (\n
\n {hasOptionIcons && (\n
\n {take(selected, 3).map(({ value, icon }) => {\n const Icon = icon!\n return isValidElement(Icon) ? (\n cloneElement(Icon, { key: value })\n ) : (\n \n )\n })}\n
\n )}\n \n {selected.length} {name}\n \n
\n )\n}\n\nfunction formatDateRange(start: Date, end: Date) {\n const sameMonth = start.getMonth() === end.getMonth()\n const sameYear = start.getFullYear() === end.getFullYear()\n\n if (sameMonth && sameYear) {\n return `${format(start, 'MMM d')} - ${format(end, 'd, yyyy')}`\n }\n\n if (sameYear) {\n return `${format(start, 'MMM d')} - ${format(end, 'MMM d, yyyy')}`\n }\n\n return `${format(start, 'MMM d, yyyy')} - ${format(end, 'MMM d, yyyy')}`\n}\n\nexport function FilterValueDateDisplay({\n filter,\n column,\n actions,\n locale = 'en',\n}: FilterValueDisplayProps) {\n if (!filter) return null\n if (filter.values.length === 0) return \n if (filter.values.length === 1 && filter.values[0]) {\n const value = filter.values[0]\n\n const formattedDateStr = format(value, 'MMM d, yyyy')\n\n return {formattedDateStr}\n }\n if (filter.values.length === 2 && filter.values[0] && filter.values[1]) {\n const formattedRangeStr = formatDateRange(\n filter.values[0],\n filter.values[1],\n )\n\n return {formattedRangeStr}\n }\n\n return null\n}\n\nexport function FilterValueTextDisplay({\n filter,\n column,\n actions,\n locale = 'en',\n}: FilterValueDisplayProps) {\n if (!filter) return null\n if (\n filter.values.length === 0 ||\n (filter.values[0] && filter.values[0].trim() === '')\n )\n return \n\n const value = filter.values[0]\n\n return {value}\n}\n\nexport function FilterValueNumberDisplay({\n filter,\n column,\n actions,\n locale = 'en',\n}: FilterValueDisplayProps) {\n if (!filter || !filter.values || filter.values.length === 0) return null\n\n if (\n filter.operator === 'is between' ||\n filter.operator === 'is not between'\n ) {\n const minValue = filter.values[0]\n const maxValue = filter.values[1]\n\n return (\n \n {minValue} {t('and', locale)} {maxValue}\n \n )\n }\n\n const value = filter.values[0]\n return {value}\n}\n\nexport function FilterValueBooleanDisplay({\n filter,\n column,\n}: FilterValueDisplayProps) {\n if (!filter || filter.values.length === 0) return null\n return {column.toggledStateName}\n}\n\n/****** Property Filter Value Controller ******/\n\ninterface FilterValueControllerProps {\n filter: FilterModel\n column: Column\n actions: DataTableFilterActions\n strategy: FilterStrategy\n locale?: Locale\n}\n\nexport const FilterValueController = memo(\n __FilterValueController,\n) as typeof __FilterValueController\n\nfunction __FilterValueController({\n filter,\n column,\n actions,\n strategy,\n locale = 'en',\n}: FilterValueControllerProps) {\n switch (column.type) {\n case 'option':\n return (\n }\n column={column as Column}\n actions={actions}\n strategy={strategy}\n locale={locale}\n />\n )\n case 'multiOption':\n return (\n }\n column={column as Column}\n actions={actions}\n strategy={strategy}\n locale={locale}\n />\n )\n case 'date':\n return (\n }\n column={column as Column}\n actions={actions}\n strategy={strategy}\n locale={locale}\n />\n )\n case 'text':\n return (\n }\n column={column as Column}\n actions={actions}\n strategy={strategy}\n locale={locale}\n />\n )\n case 'number':\n return (\n }\n column={column as Column}\n actions={actions}\n strategy={strategy}\n locale={locale}\n />\n )\n default:\n return null\n }\n}\n\ninterface OptionItemProps {\n option: ColumnOptionExtended\n onToggle: (value: string, checked: boolean) => void\n}\n\n// Memoized option item to prevent re-renders unless its own props change\nconst OptionItem = memo(function OptionItem({\n option,\n onToggle,\n}: OptionItemProps) {\n const { value, label, icon: Icon, selected, count } = option\n const handleSelect = useCallback(() => {\n onToggle(value, !selected)\n }, [onToggle, value, selected])\n\n return (\n \n
\n \n
\n {Icon &&\n (isValidElement(Icon) ? (\n Icon\n ) : (\n \n ))}\n
\n \n {label}\n \n
\n {count && (\n \n {new Intl.NumberFormat().format(count)}\n \n )}\n \n )\n})\n\nexport function FilterValueOptionController({\n filter,\n column,\n actions,\n locale = 'en',\n}: FilterValueControllerProps) {\n // Derive the initial selected values on mount\n const initialSelectedValues = useMemo(() => new Set(filter?.values || []), [])\n\n // Separate the selected and unselected options\n const { selectedOptions, unselectedOptions } = useMemo(() => {\n const counts = column.getFacetedUniqueValues()\n const allOptions = column.getOptions().map((o) => {\n const currentlySelected = filter?.values.includes(o.value) ?? false\n return {\n ...o,\n selected: currentlySelected,\n count: counts?.get(o.value) ?? 0,\n }\n })\n\n const selected = allOptions.filter((o) =>\n initialSelectedValues.has(o.value),\n )\n const unselected = allOptions.filter(\n (o) => !initialSelectedValues.has(o.value),\n )\n return { selectedOptions: selected, unselectedOptions: unselected }\n }, [column, filter?.values, initialSelectedValues])\n\n const handleToggle = useCallback(\n (value: string, checked: boolean) => {\n if (checked) actions.addFilterValue(column, [value])\n else actions.removeFilterValue(column, [value])\n },\n [actions, column],\n )\n\n return (\n \n \n {t('noresults', locale)}\n \n \n {selectedOptions.map((option) => (\n \n ))}\n \n {/* Only show separator if there are both selected AND unselected options */}\n \n \n {unselectedOptions.map((option) => (\n \n ))}\n \n \n \n )\n}\n\nexport function FilterValueMultiOptionController({\n filter,\n column,\n actions,\n locale = 'en',\n}: FilterValueControllerProps) {\n // Derive the initial selected values on mount\n const initialSelectedValues = useMemo(() => new Set(filter?.values || []), [])\n\n // Separate the selected and unselected options\n const { selectedOptions, unselectedOptions } = useMemo(() => {\n const counts = column.getFacetedUniqueValues()\n const allOptions = column.getOptions().map((o) => {\n const currentlySelected = filter?.values.includes(o.value) ?? false\n return {\n ...o,\n selected: currentlySelected,\n count: counts?.get(o.value) ?? 0,\n }\n })\n\n const selected = allOptions.filter((o) =>\n initialSelectedValues.has(o.value),\n )\n const unselected = allOptions.filter(\n (o) => !initialSelectedValues.has(o.value),\n )\n return { selectedOptions: selected, unselectedOptions: unselected }\n }, [column, filter?.values, initialSelectedValues])\n\n const handleToggle = useCallback(\n (value: string, checked: boolean) => {\n if (checked) actions.addFilterValue(column, [value])\n else actions.removeFilterValue(column, [value])\n },\n [actions, column],\n )\n\n return (\n \n \n {t('noresults', locale)}\n \n \n {selectedOptions.map((option) => (\n \n ))}\n \n {/* Only show separator if there are both selected AND unselected options */}\n \n \n {unselectedOptions.map((option) => (\n \n ))}\n \n \n \n )\n}\n\nexport function FilterValueDateController({\n filter,\n column,\n actions,\n}: FilterValueControllerProps) {\n const [date, setDate] = useState({\n from: filter?.values[0] ?? new Date(),\n to: filter?.values[1] ?? undefined,\n })\n\n function changeDateRange(value: DateRange | undefined) {\n const start = value?.from\n const end =\n start && value && value.to && !isEqual(start, value.to)\n ? value.to\n : undefined\n\n setDate({ from: start, to: end })\n\n const isRange = start && end\n const newValues = isRange ? [start, end] : start ? [start] : []\n\n actions.setFilterValue(column, newValues)\n }\n\n return (\n \n \n \n
\n \n
\n
\n
\n
\n )\n}\n\nexport function FilterValueTextController({\n filter,\n column,\n actions,\n locale = 'en',\n}: FilterValueControllerProps) {\n const changeText = (value: string | number) => {\n actions.setFilterValue(column, [String(value)])\n }\n\n return (\n \n \n \n \n \n \n \n \n \n )\n}\n\nexport function FilterValueNumberController({\n filter,\n column,\n actions,\n locale = 'en',\n}: FilterValueControllerProps) {\n const minMax = useMemo(\n () => column.getFacetedMinMaxValues() as MinMaxReturn<'number'>,\n [column],\n )\n const [sliderMin, sliderMax] = [\n minMax ? minMax[0] : 0,\n minMax ? minMax[1] : 0,\n ]\n\n // Local state for values\n const [values, setValues] = useState(filter?.values ?? [0, 0])\n\n // Sync with parent filter changes\n useEffect(() => {\n if (\n filter?.values &&\n filter.values.length === values.length &&\n filter.values.every((v, i) => v === values[i])\n ) {\n setValues(filter.values)\n }\n }, [filter?.values, values])\n\n const isNumberRange =\n // filter && values.length === 2\n filter && numberFilterOperators[filter.operator].target === 'multiple'\n\n const setFilterOperatorDebounced = useDebounceCallback(\n actions.setFilterOperator,\n 500,\n )\n const setFilterValueDebounced = useDebounceCallback(\n actions.setFilterValue,\n 500,\n )\n\n const changeNumber = (value: number[]) => {\n setValues(value)\n setFilterValueDebounced(column as any, value)\n }\n\n const changeMinNumber = (value: number) => {\n const newValues = createNumberRange([value, values[1]!])\n setValues(newValues)\n setFilterValueDebounced(column as any, newValues)\n }\n\n const changeMaxNumber = (value: number) => {\n const newValues = createNumberRange([values[0]!, value])\n setValues(newValues)\n setFilterValueDebounced(column as any, newValues)\n }\n\n const changeType = useCallback(\n (type: 'single' | 'range') => {\n let newValues: number[] = []\n if (type === 'single')\n newValues = [values[0]!] // Keep the first value for single mode\n else if (!minMax)\n newValues = createNumberRange([values[0]!, values[1] ?? 0])\n else {\n const value = values[0]!\n newValues =\n value - minMax[0] < minMax[1] - value\n ? createNumberRange([value, minMax[1]])\n : createNumberRange([minMax[0], value])\n }\n\n const newOperator = type === 'single' ? 'is' : 'is between'\n\n // Update local state\n setValues(newValues)\n\n // Cancel in-flight debounced calls to prevent flicker/race conditions\n setFilterOperatorDebounced.cancel()\n setFilterValueDebounced.cancel()\n\n // Update global filter state atomically\n actions.setFilterOperator(column.id, newOperator)\n actions.setFilterValue(column, newValues)\n },\n [values, column, actions, minMax],\n )\n\n return (\n \n \n \n
\n changeType(v as 'single' | 'range')}\n >\n \n {t('single', locale)}\n {t('range', locale)}\n \n \n {minMax && (\n changeNumber(value)}\n min={sliderMin}\n max={sliderMax}\n step={1}\n aria-orientation=\"horizontal\"\n />\n )}\n
\n \n {t('value', locale)}\n \n changeNumber([Number(v)])}\n />\n
\n
\n \n {minMax && (\n \n )}\n
\n
\n \n {t('min', locale)}\n \n changeMinNumber(Number(v))}\n />\n
\n
\n \n {t('max', locale)}\n \n changeMaxNumber(Number(v))}\n />\n
\n
\n
\n \n
\n
\n
\n
\n )\n}\n\nexport function FilterValueBooleanController({\n filter,\n column,\n actions,\n}: FilterValueControllerProps) {\n const handleChange = (value: boolean) => {\n actions.setFilterValue(column, [value])\n }\n\n return (\n \n \n \n \n \n \n \n \n \n )\n}\n\n// TODO: Add support for `bigint` filtering\n", "type": "registry:file", "target": "components/data-table-filter/components/filter-value.tsx" }, diff --git a/apps/web/registry.json b/apps/web/registry.json index 76bc64d3..60bc5f18 100644 --- a/apps/web/registry.json +++ b/apps/web/registry.json @@ -3,6 +3,19 @@ "name": "bazza/ui", "homepage": "https://ui.bazza.dev", "items": [ + { + "name": "action-menu", + "title": "Action menu", + "type": "registry:component", + "dependencies": ["@bazza-ui/action-menu@canary", "lucide-react"], + "files": [ + { + "path": "registry/action-menu/index.tsx", + "type": "registry:file", + "target": "components/ui/action-menu.tsx" + } + ] + }, { "name": "data-table-filter", "title": "Data table filter", diff --git a/apps/web/registry/action-menu/index.tsx b/apps/web/registry/action-menu/index.tsx new file mode 100644 index 00000000..1f501bd4 --- /dev/null +++ b/apps/web/registry/action-menu/index.tsx @@ -0,0 +1,151 @@ +import { createActionMenu, renderIcon } from '@bazza-ui/action-menu' +import { ChevronRightIcon } from 'lucide-react' +import { Fragment } from 'react' +import { cn } from '@/lib/utils' + +const TriangleRightIcon = ({ + ...props +}: React.HTMLAttributes) => { + return ( + + + + ) +} + +export const LabelWithBreadcrumbs = ({ + label, + breadcrumbs, +}: { + label: string + breadcrumbs?: string[] +}) => ( +
+ {breadcrumbs?.map((crumb, idx) => ( + + {crumb} + + + ))} + {label} +
+) + +export const ActionMenu = createActionMenu({ + defaults: { + content: { + onCloseAutoClear: 300, + }, + }, + shell: { + classNames: { + overlay: cn( + 'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/40 h-full w-full', + ), + drawerContent: cn( + 'group/drawer-content border rounded-lg bg-popover fixed z-50 flex h-auto flex-col min-h-0 overflow-hidden shadow-lg', + 'data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80svh] data-[vaul-drawer-direction=top]:rounded-lg', + 'data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-4 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80svh] data-[vaul-drawer-direction=bottom]:rounded-lg data-[vaul-drawer-direction=bottom]:mx-4', + ), + drawerHandle: cn( + 'bg-muted mx-auto mt-4 mb-2 hidden h-2 w-[100px] shrink-0 rounded-full group-data-[vaul-drawer-direction=bottom]/drawer-content:block', + ), + }, + slotProps: { + drawerRoot: { + shouldScaleBackground: true, + }, + }, + }, + surface: { + slots: { + Item: ({ node, bind, search }) => { + const props = bind.getRowProps({ + className: 'group/row', + }) + + return ( +
+ {node.icon && ( +
+ {renderIcon( + node.icon, + 'size-4 shrink-0 text-muted-foreground group-data-[focused=true]/row:text-primary', + )} +
+ )} + +
+ ) + }, + SubmenuTrigger: ({ node, bind, search }) => { + const props = bind.getRowProps({}) + + return ( +
+
+ {node.icon && ( +
+ {renderIcon( + node.icon, + 'size-4 shrink-0 text-muted-foreground group-data-[focused=true]:text-primary', + )} +
+ )} + +
+ +
+ ) + }, + Empty: () => ( +
+ No matching options. +
+ ), + }, + classNames: { + content: cn( + 'data-[mode=dropdown]:border bg-popover rounded-lg z-50 text-sm shadow-md origin-(--radix-popper-transform-origin) flex flex-col h-full w-full min-h-0 overflow-hidden', + 'data-[root-menu]:data-[state=open]:animate-in data-[root-menu]:data-[state=closed]:animate-out data-[root-menu]:data-[state=closed]:fade-out-0 data-[root-menu]:data-[state=open]:fade-in-0 data-[root-menu]:data-[state=closed]:zoom-out-95 data-[root-menu]:data-[state=open]:zoom-in-95 data-[root-menu]:data-[side=bottom]:slide-in-from-top-2 data-[root-menu]:data-[side=left]:slide-in-from-right-2 data-[root-menu]:data-[side=right]:slide-in-from-left-2 data-[root-menu]:data-[side=top]:slide-in-from-bottom-2', + ), + + list: cn( + 'p-1 flex flex-col w-full scroll-py-1 overflow-x-hidden overflow-y-auto outline-none', + 'data-[mode=dropdown]:min-w-[200px] data-[mode=dropdown]:max-w-[500px] data-[mode=dropdown]:max-h-[300px]', + 'data-[mode=drawer]:flex-1 data-[mode=drawer]:max-w-full', + 'data-[mode=drawer]:[&_[data-action-menu-group-heading]]:px-5', + ), + input: cn( + 'outline-hidden disabled:cursor-not-allowed disabled:opacity-50 min-h-9 max-h-9 px-4 placeholder-muted-foreground/70 focus-visible:placeholder-muted-foreground placeholder:transition-[color] placeholder:duration-50 placeholder:ease-in-out border-b caret-blue-500', + 'data-[mode=drawer]:px-6', + ), + group: cn('not-last:mb-2'), + groupHeading: cn('text-xs font-medium text-muted-foreground px-3 my-1'), + item: cn( + 'group flex items-center gap-2 rounded-md px-3 py-1.5 text-sm select-none', + 'data-[focused=true]:bg-accent data-[focused=true]:text-accent-foreground', + 'data-[mode=drawer]:px-5', + ), + subtrigger: cn( + "group w-full flex items-center justify-between data-[focused=true]:bg-accent data-[focused=true]:text-accent-foreground relative cursor-default gap-4 rounded-md py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground", + 'data-[mode=dropdown]:px-3', + 'data-[mode=drawer]:px-5', + 'relative', + ), + }, + }, +}) diff --git a/apps/web/registry/data-table-filter/components/filter-value.tsx b/apps/web/registry/data-table-filter/components/filter-value.tsx index 076de745..5229ec1c 100644 --- a/apps/web/registry/data-table-filter/components/filter-value.tsx +++ b/apps/web/registry/data-table-filter/components/filter-value.tsx @@ -1,3 +1,4 @@ +/** biome-ignore-all lint/correctness/useUniqueElementIds: */ import { type Column, type ColumnDataType, diff --git a/apps/web/scripts/build-types-meta.ts b/apps/web/scripts/build-types-meta.ts new file mode 100644 index 00000000..d5a6e22b --- /dev/null +++ b/apps/web/scripts/build-types-meta.ts @@ -0,0 +1,347 @@ +import fs from 'node:fs' +import path from 'node:path' +import ts from 'typescript' + +/** ---------- CLI parsing (typed) ---------- */ + +type PkgArg = { name: string; entry: string } + +interface Args { + out: string + tsconfig?: string + packages: PkgArg[] +} + +function parseArgs(argv: string[]): Args { + const getOnce = (k: string): string | undefined => { + const i = argv.indexOf(`--${k}`) + return i >= 0 ? argv[i + 1] : undefined + } + const getMany = (k: string): string[] => { + const out: string[] = [] + for (let i = 0; i < argv.length; i++) { + if (argv[i] === `--${k}` && argv[i + 1]) out.push(argv[i + 1]!) + } + return out + } + + const out = getOnce('out') ?? '.types/types-meta.json' + const tsconfig = getOnce('tsconfig') + const pkgSpecs = getMany('pkg') + + if (pkgSpecs.length === 0) { + throw new Error('Pass at least one --pkg "name=path/to/entry.ts"') + } + + const packages: PkgArg[] = pkgSpecs.map((spec) => { + const eq = spec.indexOf('=') + if (eq === -1) + throw new Error(`Invalid --pkg "${spec}" (expected "name=path")`) + const name = spec.slice(0, eq).trim() + const entry = path.resolve(process.cwd(), spec.slice(eq + 1).trim()) + return { name, entry } + }) + + return { out, tsconfig, packages } +} + +/** ---------- Output shapes (typed) ---------- */ + +export type PropMeta = { + name: string + type: string + required: boolean + description?: string +} + +export type TypeMeta = { + name: string + kind: 'interface' | 'typealias' | 'enum' + typeParams?: Array<{ name: string; constraint?: string; default?: string }> + doc?: string + props?: PropMeta[] // not present for enums + definition?: string // not present for enums +} + +export type PackageMeta = { + entrypoint: string + types: Record +} + +export type MetaOutput = Record + +/** ---------- TS helpers (typed) ---------- */ + +function isUnionType(t: ts.Type): t is ts.UnionType { + return (t.flags & ts.TypeFlags.Union) !== 0 +} + +function isIntersectionType(t: ts.Type): t is ts.IntersectionType { + return (t.flags & ts.TypeFlags.Intersection) !== 0 +} + +function isObjectLikeType(t: ts.Type): boolean { + return (t.flags & ts.TypeFlags.Object) !== 0 +} + +/** Collect properties only from object(-like) types. Flattens intersections. */ +function collectObjectProps(t: ts.Type, checker: ts.TypeChecker): PropMeta[] { + const seen = new Map() + + const addProps = (tt: ts.Type) => { + if (!isObjectLikeType(tt)) return + for (const s of checker.getPropertiesOfType(tt)) { + seen.set(s.getName(), s) + } + } + + if (isIntersectionType(t)) { + for (const part of t.types) addProps(part) + } else if (!isUnionType(t)) { + // unions are skipped (e.g., 'a' | 'b'); object unions aren’t summarized here + addProps(t) + } + + return [...seen.values()].map((sym) => propMeta(sym, checker)) +} + +function loadCompilerOptions(tsconfigPath?: string): ts.CompilerOptions { + if (!tsconfigPath) return { skipLibCheck: true, strict: false } + const cfg = ts.readConfigFile(tsconfigPath, ts.sys.readFile) + if (cfg.error) + throw new Error( + ts.formatDiagnosticsWithColorAndContext([cfg.error], formatHost), + ) + const parsed = ts.parseJsonConfigFileContent( + cfg.config, + ts.sys, + path.dirname(tsconfigPath), + ) + return parsed.options +} + +function sanitizeForAnalysis(options: ts.CompilerOptions): ts.CompilerOptions { + // We don't emit; ensure JSX + DOM + React types are available and resolution works with .js -> .tsx re-exports. + const merged: ts.CompilerOptions = { + ...options, + noEmit: true, + skipLibCheck: true, + jsx: options.jsx ?? ts.JsxEmit.ReactJSX, + jsxImportSource: options.jsxImportSource ?? 'react', + lib: options.lib ?? ['ES2021', 'DOM'], + types: Array.from( + new Set([...(options.types ?? []), 'node', 'react', 'react-dom']), + ), + moduleResolution: + options.moduleResolution ?? ts.ModuleResolutionKind.Bundler, + } + // Remove build-only flags that cause diagnostics in analysis mode + delete (merged as any).incremental + delete (merged as any).tsBuildInfoFile + delete (merged as any).composite + return merged +} + +const formatHost: ts.FormatDiagnosticsHost = { + getCanonicalFileName: (f) => f, + getCurrentDirectory: () => process.cwd(), + getNewLine: () => '\n', +} + +const isInterfaceDecl = (n: ts.Node): n is ts.InterfaceDeclaration => + n.kind === ts.SyntaxKind.InterfaceDeclaration +const isTypeAliasDecl = (n: ts.Node): n is ts.TypeAliasDeclaration => + n.kind === ts.SyntaxKind.TypeAliasDeclaration +const isEnumDecl = (n: ts.Node): n is ts.EnumDeclaration => + n.kind === ts.SyntaxKind.EnumDeclaration + +function kindOfDecl( + d: ts.InterfaceDeclaration | ts.TypeAliasDeclaration | ts.EnumDeclaration, +): TypeMeta['kind'] { + if (isInterfaceDecl(d)) return 'interface' + if (isTypeAliasDecl(d)) return 'typealias' + return 'enum' +} + +const printer = ts.createPrinter({ removeComments: false }) +function nodeText(node: ts.Node): string { + return printer.printNode(ts.EmitHint.Unspecified, node, node.getSourceFile()) +} + +function getSymbolDoc( + sym: ts.Symbol, + checker: ts.TypeChecker, +): string | undefined { + const txt = ts + .displayPartsToString(sym.getDocumentationComment(checker)) + .trim() + return txt || undefined +} + +function typeParamsMeta( + node: ts.InterfaceDeclaration | ts.TypeAliasDeclaration, +): Array<{ name: string; constraint?: string; default?: string }> | undefined { + const tps = + node.typeParameters?.map((tp) => ({ + name: tp.name.getText(), + constraint: tp.constraint ? nodeText(tp.constraint) : undefined, + default: tp.default ? nodeText(tp.default) : undefined, + })) ?? [] + return tps.length ? tps : undefined +} + +function propMeta(propSym: ts.Symbol, checker: ts.TypeChecker): PropMeta { + const decl = (propSym.valueDeclaration ?? propSym.declarations?.[0]) as + | ts.Declaration + | undefined + const type = checker.getTypeOfSymbolAtLocation( + propSym, + decl ?? propSym.declarations?.[0] ?? (propSym as unknown as ts.Node), + ) + const required = + decl && ts.isPropertySignature(decl) + ? !decl.questionToken + : decl && ts.isPropertyDeclaration(decl) + ? !decl.questionToken + : true + + return { + name: propSym.getName(), + type: checker.typeToString(type), + required, + description: getSymbolDoc(propSym, checker), + } +} + +/** Resolve a SourceFile robustly (path normalization). */ +function findSourceFile( + program: ts.Program, + absPath: string, +): ts.SourceFile | undefined { + const want = path.normalize(absPath) + return program + .getSourceFiles() + .find((sf) => path.normalize(sf.fileName) === want) +} + +/** Resolve re-exported symbols to their real declarations. */ +function resolveExport(sym: ts.Symbol, checker: ts.TypeChecker): ts.Symbol { + return (sym.getFlags() & ts.SymbolFlags.Alias) !== 0 + ? checker.getAliasedSymbol(sym) + : sym +} + +/** ---------- Collector ---------- */ + +function collectPackageTypes( + prog: ts.Program, + checker: ts.TypeChecker, + pkg: PkgArg, +): PackageMeta { + const sf = findSourceFile(prog, pkg.entry) + if (!sf) throw new Error(`Entry not in program: ${pkg.entry}`) + const moduleSym = checker.getSymbolAtLocation(sf) + if (!moduleSym) throw new Error(`No module symbol for: ${pkg.entry}`) + + const exportsArr = checker.getExportsOfModule(moduleSym) + const types: Record = {} + + if (process.env.DEBUG_TYPES) { + console.log(`\n[${pkg.name}] entry: ${pkg.entry}`) + } + + for (const exp of exportsArr) { + const target = resolveExport(exp, checker) + + const decls = target.getDeclarations() ?? [] + const decl = decls.find( + (d) => isInterfaceDecl(d) || isTypeAliasDecl(d) || isEnumDecl(d), + ) as + | ts.InterfaceDeclaration + | ts.TypeAliasDeclaration + | ts.EnumDeclaration + | undefined + + if (process.env.DEBUG_TYPES) { + const kinds = decls.map((d) => ts.SyntaxKind[d.kind]).join(', ') + console.log( + ' export', + exp.getName(), + exp.getFlags() & ts.SymbolFlags.Alias ? '(alias)' : '', + '-> decl kinds:', + kinds || '(none)', + ) + } + + if (!decl) continue + + const kind = kindOfDecl(decl) + const typeParams = isEnumDecl(decl) ? undefined : typeParamsMeta(decl) + const doc = getSymbolDoc(target, checker) || getSymbolDoc(exp, checker) + + const meta: TypeMeta = { name: exp.getName(), kind, typeParams, doc } + + if (isTypeAliasDecl(decl)) { + meta.definition = nodeText(decl.type) // e.g. "'item' | 'group' | 'submenu'" + } + + if (kind !== 'enum') { + const declaredType = checker.getDeclaredTypeOfSymbol( + target /* not exp; see alias fix */, + ) + const props = collectObjectProps(declaredType, checker) + if (props.length) meta.props = props + } + + types[meta.name] = meta + } + + return { entrypoint: path.relative(process.cwd(), pkg.entry), types } +} + +/** ---------- Main ---------- */ + +async function main(): Promise { + const args = parseArgs(process.argv.slice(2)) + const rawOptions = loadCompilerOptions(args.tsconfig) + const options = sanitizeForAnalysis(rawOptions) + const rootNames = args.packages.map((p) => p.entry) + const program = ts.createProgram({ rootNames, options }) + const checker = program.getTypeChecker() + + if (process.env.DEBUG_TYPES) { + console.log( + 'Program files:\n' + + program + .getSourceFiles() + .map((sf) => ' - ' + sf.fileName) + .join('\n'), + ) + } + + // Trigger type checking so diagnostics surface early + const diagnostics = ts.getPreEmitDiagnostics(program) + if (diagnostics.length) { + console.warn( + ts.formatDiagnosticsWithColorAndContext(diagnostics, formatHost), + ) + } + + const output: MetaOutput = {} + for (const pkg of args.packages) { + const meta = collectPackageTypes(program, checker, pkg) + if (process.env.DEBUG_TYPES && Object.keys(meta.types).length === 0) { + console.warn(`[warn] No exported types found for ${pkg.name}`) + } + output[pkg.name] = meta + } + + fs.mkdirSync(path.dirname(args.out), { recursive: true }) + fs.writeFileSync(args.out, JSON.stringify(output, null, 2)) + console.log(`[types:meta] wrote ${args.out}`) +} + +main().catch((err) => { + console.error(err) + process.exit(1) +}) diff --git a/apps/web/tsconfig.docs.json b/apps/web/tsconfig.docs.json new file mode 100644 index 00000000..a5d1e367 --- /dev/null +++ b/apps/web/tsconfig.docs.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "incremental": false, + "composite": false, + "noEmit": true + } +} diff --git a/bun.lock b/bun.lock index c2a3d248..e8af6532 100644 --- a/bun.lock +++ b/bun.lock @@ -4,7 +4,7 @@ "": { "name": "ui", "devDependencies": { - "@biomejs/biome": "2.0.6", + "@biomejs/biome": "2.2.2", "@types/node": "^22", "husky": "^9.1.7", "lint-staged": "^15.5.0", @@ -17,20 +17,24 @@ "name": "web", "version": "0.0.0", "dependencies": { + "@bazza-ui/action-menu": "workspace:*", "@bazza-ui/filters": "workspace:*", "@ndaidong/txtgen": "^4.0.1", "@radix-ui/react-avatar": "^1.1.3", "@radix-ui/react-checkbox": "^1.3.1", + "@radix-ui/react-collapsible": "^1.1.12", "@radix-ui/react-dialog": "^1.1.13", "@radix-ui/react-dropdown-menu": "^2.1.14", + "@radix-ui/react-hover-card": "^1.1.15", "@radix-ui/react-label": "^2.1.6", "@radix-ui/react-popover": "^1.1.13", + "@radix-ui/react-scroll-area": "^1.2.10", "@radix-ui/react-select": "^2.1.6", "@radix-ui/react-separator": "^1.1.6", "@radix-ui/react-slider": "^1.3.4", "@radix-ui/react-slot": "^1.2.2", "@radix-ui/react-switch": "^1.1.3", - "@radix-ui/react-tabs": "^1.1.11", + "@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-tooltip": "^1.1.8", "@t3-oss/env-nextjs": "^0.12.0", "@tanstack/react-query": "^5.72.2", @@ -42,15 +46,16 @@ "jotai": "^2.12.2", "lucide-react": "^0.482.0", "nanoid": "^5.1.5", - "next": "^15.4.5", + "next": "^15.5.2", "next-mdx-remote": "^5.0.0", - "next-themes": "^0.4.4", + "next-themes": "^0.4.6", "nuqs": "^2.4.1", "react": "^19.1.0", "react-day-picker": "8.10.1", "react-dom": "^19.1.0", "remeda": "^2.26.1", "shadcn": "2.4.0-canary.13", + "sonner": "^2.0.7", "tailwind-merge": "^3.0.2", "tailwindcss-animate": "^1.0.7", "vaul": "^1.1.2", @@ -66,6 +71,7 @@ "@types/react": "^19.1.1", "@types/react-dom": "^19.1.2", "@types/unist": "^3.0.3", + "chokidar": "^4.0.3", "husky": "^9.1.7", "jsdom": "^26.0.0", "mdast-util-toc": "^7.1.0", @@ -80,14 +86,50 @@ "remark-gfm": "^4.0.1", "shiki": "^3.2.1", "tailwindcss": "^4.1.0", + "ts-morph": "^27.0.0", + "tsx": "^4.20.5", "typescript": "^5", "unist-builder": "^3.0.0", "unist-util-visit": "^5.0.0", }, }, + "packages/action-menu": { + "name": "@bazza-ui/action-menu", + "version": "0.0.2025082700", + "dependencies": { + "@radix-ui/primitive": "^1.1.2", + "@radix-ui/react-compose-refs": "^1.1.2", + "@radix-ui/react-popper": "^1.2.7", + "@radix-ui/react-portal": "^1.1.9", + "@radix-ui/react-presence": "^1.1.4", + "@radix-ui/react-primitive": "^2.1.3", + "@radix-ui/react-use-controllable-state": "^1.2.2", + "clsx": "^2.1.1", + "remeda": "^2.30.0", + "tailwind-merge": "^3.3.1", + "vaul": "^1.1.2", + }, + "devDependencies": { + "@bazza-ui/typescript-config": "*", + "@testing-library/dom": "^10.4.0", + "@testing-library/react": "^16.3.0", + "@types/react": "^19.1.1", + "@types/react-dom": "^19.1.2", + "@vitejs/plugin-react": "^4.3.4", + "tinybench": "^4.0.1", + "tsup": "^8.5.0", + "typescript": "^5", + "vite-tsconfig-paths": "^5.1.4", + "vitest": "^3.2.4", + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0", + }, + }, "packages/filters": { "name": "@bazza-ui/filters", - "version": "0.3.2025082201", + "version": "0.3.2025091001", "devDependencies": { "@bazza-ui/typescript-config": "*", "@ndaidong/txtgen": "^4.0.1", @@ -183,27 +225,29 @@ "@babel/types": ["@babel/types@7.27.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9" } }, "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg=="], + "@bazza-ui/action-menu": ["@bazza-ui/action-menu@workspace:packages/action-menu"], + "@bazza-ui/filters": ["@bazza-ui/filters@workspace:packages/filters"], "@bazza-ui/typescript-config": ["@bazza-ui/typescript-config@workspace:packages/typescript-config"], - "@biomejs/biome": ["@biomejs/biome@2.0.6", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.0.6", "@biomejs/cli-darwin-x64": "2.0.6", "@biomejs/cli-linux-arm64": "2.0.6", "@biomejs/cli-linux-arm64-musl": "2.0.6", "@biomejs/cli-linux-x64": "2.0.6", "@biomejs/cli-linux-x64-musl": "2.0.6", "@biomejs/cli-win32-arm64": "2.0.6", "@biomejs/cli-win32-x64": "2.0.6" }, "bin": { "biome": "bin/biome" } }, "sha512-RRP+9cdh5qwe2t0gORwXaa27oTOiQRQvrFf49x2PA1tnpsyU7FIHX4ZOFMtBC4QNtyWsN7Dqkf5EDbg4X+9iqA=="], + "@biomejs/biome": ["@biomejs/biome@2.2.2", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.2.2", "@biomejs/cli-darwin-x64": "2.2.2", "@biomejs/cli-linux-arm64": "2.2.2", "@biomejs/cli-linux-arm64-musl": "2.2.2", "@biomejs/cli-linux-x64": "2.2.2", "@biomejs/cli-linux-x64-musl": "2.2.2", "@biomejs/cli-win32-arm64": "2.2.2", "@biomejs/cli-win32-x64": "2.2.2" }, "bin": { "biome": "bin/biome" } }, "sha512-j1omAiQWCkhuLgwpMKisNKnsM6W8Xtt1l0WZmqY/dFj8QPNkIoTvk4tSsi40FaAAkBE1PU0AFG2RWFBWenAn+w=="], - "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.0.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-AzdiNNjNzsE6LfqWyBvcL29uWoIuZUkndu+wwlXW13EKcBHbbKjNQEZIJKYDc6IL+p7bmWGx3v9ZtcRyIoIz5A=="], + "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.2.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-6ePfbCeCPryWu0CXlzsWNZgVz/kBEvHiPyNpmViSt6A2eoDf4kXs3YnwQPzGjy8oBgQulrHcLnJL0nkCh80mlQ=="], - "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.0.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-wJjjP4E7bO4WJmiQaLnsdXMa516dbtC6542qeRkyJg0MqMXP0fvs4gdsHhZ7p9XWTAmGIjZHFKXdsjBvKGIJJQ=="], + "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.2.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-Tn4JmVO+rXsbRslml7FvKaNrlgUeJot++FkvYIhl1OkslVCofAtS35MPlBMhXgKWF9RNr9cwHanrPTUUXcYGag=="], - "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.0.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-ZSVf6TYo5rNMUHIW1tww+rs/krol7U5A1Is/yzWyHVZguuB0lBnIodqyFuwCNqG9aJGyk7xIMS8HG0qGUPz0SA=="], + "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-JfrK3gdmWWTh2J5tq/rcWCOsImVyzUnOS2fkjhiYKCQ+v8PqM+du5cfB7G1kXas+7KQeKSWALv18iQqdtIMvzw=="], - "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.0.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-CVPEMlin3bW49sBqLBg2x016Pws7eUXA27XYDFlEtponD0luYjg2zQaMJ2nOqlkKG9fqzzkamdYxHdMDc2gZFw=="], + "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-/MhYg+Bd6renn6i1ylGFL5snYUn/Ct7zoGVKhxnro3bwekiZYE8Kl39BSb0MeuqM+72sThkQv4TnNubU9njQRw=="], - "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.0.6", "", { "os": "linux", "cpu": "x64" }, "sha512-geM1MkHTV1Kh2Cs/Xzot9BOF3WBacihw6bkEmxkz4nSga8B9/hWy5BDiOG3gHDGIBa8WxT0nzsJs2f/hPqQIQw=="], + "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-Ogb+77edO5LEP/xbNicACOWVLt8mgC+E1wmpUakr+O4nKwLt9vXe74YNuT3T1dUBxC/SnrVmlzZFC7kQJEfquQ=="], - "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.0.6", "", { "os": "linux", "cpu": "x64" }, "sha512-mKHE/e954hR/hSnAcJSjkf4xGqZc/53Kh39HVW1EgO5iFi0JutTN07TSjEMg616julRtfSNJi0KNyxvc30Y4rQ=="], + "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-ZCLXcZvjZKSiRY/cFANKg+z6Fhsf9MHOzj+NrDQcM+LbqYRT97LyCLWy2AS+W2vP+i89RyRM+kbGpUzbRTYWig=="], - "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.0.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-290V4oSFoKaprKE1zkYVsDfAdn0An5DowZ+GIABgjoq1ndhvNxkJcpxPsiYtT7slbVe3xmlT0ncdfOsN7KruzA=="], + "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.2.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-wBe2wItayw1zvtXysmHJQoQqXlTzHSpQRyPpJKiNIR21HzH/CrZRDFic1C1jDdp+zAPtqhNExa0owKMbNwW9cQ=="], - "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.0.6", "", { "os": "win32", "cpu": "x64" }, "sha512-bfM1Bce0d69Ao7pjTjUS+AWSZ02+5UHdiAP85Th8e9yV5xzw6JrHXbL5YWlcEKQ84FIZMdDc7ncuti1wd2sdbw=="], + "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.2.2", "", { "os": "win32", "cpu": "x64" }, "sha512-DAuHhHekGfiGb6lCcsT4UyxQmVwQiBCBUMwVra/dcOSs9q8OhfaZgey51MlekT3p8UwRqtXQfFuEJBhJNdLZwg=="], "@bundled-es-modules/cookie": ["@bundled-es-modules/cookie@2.0.1", "", { "dependencies": { "cookie": "^0.7.2" } }, "sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw=="], @@ -363,23 +407,23 @@ "@ndaidong/txtgen": ["@ndaidong/txtgen@4.0.1", "", {}, "sha512-z0I53ozZXEhjDKYl/1CEdrunOw/Ck15dJH+ciKgIKxb5atf/bGilxws8G9nF1iKMfb8rieCE03cHud7odMGEKg=="], - "@next/env": ["@next/env@15.4.5", "", {}, "sha512-ruM+q2SCOVCepUiERoxOmZY9ZVoecR3gcXNwCYZRvQQWRjhOiPJGmQ2fAiLR6YKWXcSAh7G79KEFxN3rwhs4LQ=="], + "@next/env": ["@next/env@15.5.2", "", {}, "sha512-Qe06ew4zt12LeO6N7j8/nULSOe3fMXE4dM6xgpBQNvdzyK1sv5y4oAP3bq4LamrvGCZtmRYnW8URFCeX5nFgGg=="], - "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.4.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-84dAN4fkfdC7nX6udDLz9GzQlMUwEMKD7zsseXrl7FTeIItF8vpk1lhLEnsotiiDt+QFu3O1FVWnqwcRD2U3KA=="], + "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.5.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-8bGt577BXGSd4iqFygmzIfTYizHb0LGWqH+qgIF/2EDxS5JsSdERJKA8WgwDyNBZgTIIA4D8qUtoQHmxIIquoQ=="], - "@next/swc-darwin-x64": ["@next/swc-darwin-x64@15.4.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-CL6mfGsKuFSyQjx36p2ftwMNSb8PQog8y0HO/ONLdQqDql7x3aJb/wB+LA651r4we2pp/Ck+qoRVUeZZEvSurA=="], + "@next/swc-darwin-x64": ["@next/swc-darwin-x64@15.5.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-2DjnmR6JHK4X+dgTXt5/sOCu/7yPtqpYt8s8hLkHFK3MGkka2snTv3yRMdHvuRtJVkPwCGsvBSwmoQCHatauFQ=="], - "@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@15.4.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-1hTVd9n6jpM/thnDc5kYHD1OjjWYpUJrJxY4DlEacT7L5SEOXIifIdTye6SQNNn8JDZrcN+n8AWOmeJ8u3KlvQ=="], + "@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@15.5.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-3j7SWDBS2Wov/L9q0mFJtEvQ5miIqfO4l7d2m9Mo06ddsgUK8gWfHGgbjdFlCp2Ek7MmMQZSxpGFqcC8zGh2AA=="], - "@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@15.4.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-4W+D/nw3RpIwGrqpFi7greZ0hjrCaioGErI7XHgkcTeWdZd146NNu1s4HnaHonLeNTguKnL2Urqvj28UJj6Gqw=="], + "@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@15.5.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-s6N8k8dF9YGc5T01UPQ08yxsK6fUow5gG1/axWc1HVVBYQBgOjca4oUZF7s4p+kwhkB1bDSGR8QznWrFZ/Rt5g=="], - "@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@15.4.5", "", { "os": "linux", "cpu": "x64" }, "sha512-N6Mgdxe/Cn2K1yMHge6pclffkxzbSGOydXVKYOjYqQXZYjLCfN/CuFkaYDeDHY2VBwSHyM2fUjYBiQCIlxIKDA=="], + "@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@15.5.2", "", { "os": "linux", "cpu": "x64" }, "sha512-o1RV/KOODQh6dM6ZRJGZbc+MOAHww33Vbs5JC9Mp1gDk8cpEO+cYC/l7rweiEalkSm5/1WGa4zY7xrNwObN4+Q=="], - "@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@15.4.5", "", { "os": "linux", "cpu": "x64" }, "sha512-YZ3bNDrS8v5KiqgWE0xZQgtXgCTUacgFtnEgI4ccotAASwSvcMPDLua7BWLuTfucoRv6mPidXkITJLd8IdJplQ=="], + "@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@15.5.2", "", { "os": "linux", "cpu": "x64" }, "sha512-/VUnh7w8RElYZ0IV83nUcP/J4KJ6LLYliiBIri3p3aW2giF+PAVgZb6mk8jbQSB3WlTai8gEmCAr7kptFa1H6g=="], - "@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.4.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-9Wr4t9GkZmMNcTVvSloFtjzbH4vtT4a8+UHqDoVnxA5QyfWe6c5flTH1BIWPGNWSUlofc8dVJAE7j84FQgskvQ=="], + "@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.5.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-sMPyTvRcNKXseNQ/7qRfVRLa0VhR0esmQ29DD6pqvG71+JdVnESJaHPA8t7bc67KD5spP3+DOCNLhqlEI2ZgQg=="], - "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.4.5", "", { "os": "win32", "cpu": "x64" }, "sha512-voWk7XtGvlsP+w8VBz7lqp8Y+dYw/MTI4KeS0gTVtfdhdJ5QwhXLmNrndFOin/MDoCvUaLWMkYKATaCoUkt2/A=="], + "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.5.2", "", { "os": "win32", "cpu": "x64" }, "sha512-W5VvyZHnxG/2ukhZF/9Ikdra5fdNftxI6ybeVKYvBPDtyx7x4jPPSNduUkfH5fo3zG0JQ0bPxgy41af2JX5D4Q=="], "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], @@ -397,14 +441,16 @@ "@radix-ui/number": ["@radix-ui/number@1.1.1", "", {}, "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g=="], - "@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="], + "@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="], - "@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.6", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-2JMfHJf/eVnwq+2dewT3C0acmCWD3XiVA1Da+jTDqo342UlU13WvXtqHhG+yJw5JeQmu4ue2eMy6gcEArLBlcw=="], + "@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w=="], "@radix-ui/react-avatar": ["@radix-ui/react-avatar@1.1.4", "", { "dependencies": { "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.0.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-+kBesLBzwqyDiYCtYFK+6Ktf+N7+Y6QOTUueLGLIbLZ/YeyFW6bsBGDsN+5HxHpM55C90u5fxsg0ErxzXTcwKA=="], "@radix-ui/react-checkbox": ["@radix-ui/react-checkbox@1.3.1", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.2", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-xTaLKAO+XXMPK/BpVTSaAAhlefmvMSACjIhK9mGsImvX2ljcTDm8VGR1CuS1uYcNdR5J+oiOhoJZc5un6bh3VQ=="], + "@radix-ui/react-collapsible": ["@radix-ui/react-collapsible@1.1.12", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA=="], + "@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.0.3", "@radix-ui/react-slot": "1.2.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-mM2pxoQw5HJ49rkzwOs7Y6J4oYH22wS8BfK2/bBxROlI4xuR0c4jEenQP63LlTlDkO6Buj2Vt+QYAYcOgqtrXA=="], "@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="], @@ -423,6 +469,8 @@ "@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.6", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.2", "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-r9zpYNUQY+2jWHWZGyddQLL9YHkM/XvSFHVcWs7bdVuxMAnCwTAuy6Pf47Z4nw7dYcUou1vg/VgjjrrH03VeBw=="], + "@radix-ui/react-hover-card": ["@radix-ui/react-hover-card@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg=="], + "@radix-ui/react-id": ["@radix-ui/react-id@1.1.1", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg=="], "@radix-ui/react-label": ["@radix-ui/react-label@2.1.6", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-S/hv1mTlgcPX2gCTJrWuTjSXf7ER3Zf7zWGtOprxhIIY93Qin3n5VgNA0Ez9AgrK/lEtlYgzLd4f5x6AVar4Yw=="], @@ -431,15 +479,17 @@ "@radix-ui/react-popover": ["@radix-ui/react-popover@1.1.13", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.9", "@radix-ui/react-focus-guards": "1.1.2", "@radix-ui/react-focus-scope": "1.1.6", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.6", "@radix-ui/react-portal": "1.1.8", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.2", "@radix-ui/react-slot": "1.2.2", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-84uqQV3omKDR076izYgcha6gdpN8m3z6w/AeJ83MSBJYVG/AbOHdLjAgsPZkeC/kt+k64moXFCnio8BbqXszlw=="], - "@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.6", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.6", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.2", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-rect": "1.1.1", "@radix-ui/react-use-size": "1.1.1", "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7iqXaOWIjDBfIG7aq8CUEeCSsQMLFdn7VEE8TaFz704DtEzpPHR7w/uuzRflvKgltqSAImgcmxQ7fFX3X7wasg=="], + "@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.7", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-rect": "1.1.1", "@radix-ui/react-use-size": "1.1.1", "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-IUFAccz1JyKcf/RjB552PlWwxjeCJB8/4KxT7EhBHOJM+mN7LdW+B3kacJXILm32xawcMMjb2i0cIZpo+f9kiQ=="], - "@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.8", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-hQsTUIn7p7fxCPvao/q6wpbxmCwgLrlz+nOrJgC+RwfZqWY/WN+UMqkXzrtKbPrF82P43eCTl3ekeKuyAQbFeg=="], + "@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.9", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ=="], - "@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA=="], + "@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.5", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ=="], - "@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Pf/t/GkndH7CQ8wE2hbkXA+WyZ83fhQQn5DDmwDiDo6AwN/fhaH8oqZ0jRjMrO2iaMhDi6P1HRx6AZwyMinY1g=="], + "@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - "@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.9", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-collection": "1.1.6", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.2", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-ZzrIFnMYHHCNqSNCsuN6l7wlewBEq0O0BCSBkabJMFXVO51LRUTq71gLP1UxFvmrXElqmPjA5VX7IqC9VpazAQ=="], + "@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA=="], + + "@radix-ui/react-scroll-area": ["@radix-ui/react-scroll-area@1.2.10", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A=="], "@radix-ui/react-select": ["@radix-ui/react-select@2.1.7", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.2", "@radix-ui/react-collection": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.6", "@radix-ui/react-focus-guards": "1.1.2", "@radix-ui/react-focus-scope": "1.1.3", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.3", "@radix-ui/react-portal": "1.1.5", "@radix-ui/react-primitive": "2.0.3", "@radix-ui/react-slot": "1.2.0", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-visually-hidden": "1.1.3", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-exzGIRtc7S8EIM2KjFg+7lJZsH7O7tpaBaJbBNVDnOZNhtoQ2iV+iSNfi2Wth0m6h3trJkMVvzAehB3c6xj/3Q=="], @@ -447,11 +497,11 @@ "@radix-ui/react-slider": ["@radix-ui/react-slider@1.3.4", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.2", "@radix-ui/react-collection": "1.1.6", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-primitive": "2.1.2", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Cp6hEmQtRJFci285vkdIJ+HCDLTRDk+25VhFwa1fcubywjMUE3PynBgtN5RLudOgSCYMlT4jizCXdmV+8J7Y2w=="], - "@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.2", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-y7TBO4xN4Y94FvcWIOIh18fM4R1A8S4q1jhoz4PNzOoHsFcN8pogcFmZrTYAm4F9VRUrWP/Mw7xSKybIeRI+CQ=="], + "@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], "@radix-ui/react-switch": ["@radix-ui/react-switch@1.1.4", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.0.3", "@radix-ui/react-use-controllable-state": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-zGP6W8plLeogoeGMiTHJ/uvf+TE1C2chVsEwfP8YlvpQKJHktG+iCkUtCLGPAuDV8/qDSmIRPm4NggaTxFMVBQ=="], - "@radix-ui/react-tabs": ["@radix-ui/react-tabs@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.2", "@radix-ui/react-roving-focus": "1.1.9", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-4FiKSVoXqPP/KfzlB7lwwqoFV6EPwkrrqGp9cUYXjwDYHhvpnqq79P+EPHKcdoTE7Rl8w/+6s9rTlsfXHES9GA=="], + "@radix-ui/react-tabs": ["@radix-ui/react-tabs@1.1.13", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A=="], "@radix-ui/react-tooltip": ["@radix-ui/react-tooltip@1.2.0", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.6", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.3", "@radix-ui/react-portal": "1.1.5", "@radix-ui/react-presence": "1.1.3", "@radix-ui/react-primitive": "2.0.3", "@radix-ui/react-slot": "1.2.0", "@radix-ui/react-use-controllable-state": "1.1.1", "@radix-ui/react-visually-hidden": "1.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-b1Sdc75s7zN9B8ONQTGBSHL3XS8+IcjcOIY51fhM4R1Hx8s0YbgqgyNZiri4qcYMVZK8hfCZVBiyCm7N9rs0rw=="], @@ -587,7 +637,7 @@ "@testing-library/react": ["@testing-library/react@16.3.0", "", { "dependencies": { "@babel/runtime": "^7.12.5" }, "peerDependencies": { "@testing-library/dom": "^10.0.0", "@types/react": "^18.0.0 || ^19.0.0", "@types/react-dom": "^18.0.0 || ^19.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw=="], - "@ts-morph/common": ["@ts-morph/common@0.19.0", "", { "dependencies": { "fast-glob": "^3.2.12", "minimatch": "^7.4.3", "mkdirp": "^2.1.6", "path-browserify": "^1.0.1" } }, "sha512-Unz/WHmd4pGax91rdIKWi51wnVUW11QttMEPpBiBgIewnc9UQIX7UDLxr5vRlqeByXCwhkF6VabSsI0raWcyAQ=="], + "@ts-morph/common": ["@ts-morph/common@0.28.0", "", { "dependencies": { "minimatch": "^10.0.1", "path-browserify": "^1.0.1", "tinyglobby": "^0.2.14" } }, "sha512-4w6X/oFmvXcwux6y6ExfM/xSqMHw20cYwFJH+BlYrtGa6nwY9qGq8GXnUs1sVYeF2o/KT3S8hAH6sKBI3VOkBg=="], "@types/aria-query": ["@types/aria-query@5.0.4", "", {}, "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw=="], @@ -761,7 +811,7 @@ "cmdk": ["cmdk@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "^1.1.1", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-id": "^1.1.0", "@radix-ui/react-primitive": "^2.0.2" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "react-dom": "^18 || ^19 || ^19.0.0-rc" } }, "sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg=="], - "code-block-writer": ["code-block-writer@12.0.0", "", {}, "sha512-q4dMFMlXtKR3XNBHyMHt/3pwYNA69EDk00lloMOaaUMKPUXBw6lpXtbu3MMVG6/uOihGnRDOlkyqsONEUj60+w=="], + "code-block-writer": ["code-block-writer@13.0.3", "", {}, "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg=="], "collapse-white-space": ["collapse-white-space@2.1.0", "", {}, "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw=="], @@ -975,6 +1025,8 @@ "get-stream": ["get-stream@8.0.1", "", {}, "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA=="], + "get-tsconfig": ["get-tsconfig@4.10.1", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ=="], + "git-up": ["git-up@8.1.1", "", { "dependencies": { "is-ssh": "^1.4.0", "parse-url": "^9.2.0" } }, "sha512-FDenSF3fVqBYSaJoYy1KSc2wosx0gCvKP+c+PRBht7cAaiCeQlBtfBDX9vgnNOHmdePlSFITVcn4pFfcgNvx3g=="], "git-url-parse": ["git-url-parse@16.1.0", "", { "dependencies": { "git-up": "^8.1.0" } }, "sha512-cPLz4HuK86wClEW7iDdeAKcCVlWXmrLpb2L+G9goW0Z1dtpNS6BXXSOckUTlJT/LDQViE1QZKstNORzHsLnobw=="], @@ -1349,7 +1401,7 @@ "neo-async": ["neo-async@2.6.2", "", {}, "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="], - "next": ["next@15.4.5", "", { "dependencies": { "@next/env": "15.4.5", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.4.5", "@next/swc-darwin-x64": "15.4.5", "@next/swc-linux-arm64-gnu": "15.4.5", "@next/swc-linux-arm64-musl": "15.4.5", "@next/swc-linux-x64-gnu": "15.4.5", "@next/swc-linux-x64-musl": "15.4.5", "@next/swc-win32-arm64-msvc": "15.4.5", "@next/swc-win32-x64-msvc": "15.4.5", "sharp": "^0.34.3" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-nJ4v+IO9CPmbmcvsPebIoX3Q+S7f6Fu08/dEWu0Ttfa+wVwQRh9epcmsyCPjmL2b8MxC+CkBR97jgDhUUztI3g=="], + "next": ["next@15.5.2", "", { "dependencies": { "@next/env": "15.5.2", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.5.2", "@next/swc-darwin-x64": "15.5.2", "@next/swc-linux-arm64-gnu": "15.5.2", "@next/swc-linux-arm64-musl": "15.5.2", "@next/swc-linux-x64-gnu": "15.5.2", "@next/swc-linux-x64-musl": "15.5.2", "@next/swc-win32-arm64-msvc": "15.5.2", "@next/swc-win32-x64-msvc": "15.5.2", "sharp": "^0.34.3" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-H8Otr7abj1glFhbGnvUt3gz++0AF1+QoCXEBmd/6aKbfdFwrn0LpA836Ed5+00va/7HQSDD+mOoVhn3tNy3e/Q=="], "next-mdx-remote": ["next-mdx-remote@5.0.0", "", { "dependencies": { "@babel/code-frame": "^7.23.5", "@mdx-js/mdx": "^3.0.1", "@mdx-js/react": "^3.0.1", "unist-util-remove": "^3.1.0", "vfile": "^6.0.1", "vfile-matter": "^5.0.0" }, "peerDependencies": { "react": ">=16" } }, "sha512-RNNbqRpK9/dcIFZs/esQhuLA8jANqlH694yqoDBK8hkVdJUndzzGmnPHa2nyi90N4Z9VmzuSWNRpr5ItT3M7xQ=="], @@ -1535,6 +1587,8 @@ "resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], + "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], + "restore-cursor": ["restore-cursor@5.1.0", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="], "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], @@ -1595,6 +1649,8 @@ "slice-ansi": ["slice-ansi@7.1.0", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg=="], + "sonner": ["sonner@2.0.7", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w=="], + "source-map": ["source-map@0.8.0-beta.0", "", { "dependencies": { "whatwg-url": "^7.0.0" } }, "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA=="], "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], @@ -1653,7 +1709,7 @@ "system-architecture": ["system-architecture@0.1.0", "", {}, "sha512-ulAk51I9UVUyJgxlv9M6lFot2WP3e7t8Kz9+IS6D4rVba1tR9kON+Ey69f+1R4Q8cd45Lod6a4IcJIxnzGc/zA=="], - "tailwind-merge": ["tailwind-merge@3.2.0", "", {}, "sha512-FQT/OVqCD+7edmmJpsgCsY820RTD5AkBryuG5IUqR5YQZSdj5xlH5nLgH7YPths7WsLPSpSBNneJdM8aS8aeFA=="], + "tailwind-merge": ["tailwind-merge@3.3.1", "", {}, "sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g=="], "tailwindcss": ["tailwindcss@4.1.3", "", {}, "sha512-2Q+rw9vy1WFXu5cIxlvsabCwhU2qUwodGq03ODhLJ0jW4ek5BUtoCsnLB0qG+m8AHgEsSJcJGDSDe06FXlP74g=="], @@ -1705,7 +1761,7 @@ "ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="], - "ts-morph": ["ts-morph@18.0.0", "", { "dependencies": { "@ts-morph/common": "~0.19.0", "code-block-writer": "^12.0.0" } }, "sha512-Kg5u0mk19PIIe4islUI/HWRvm9bC1lHejK4S0oh1zaZ77TMZAEmQC0sHQYiu2RgCQFZKXz1fMVi/7nOOeirznA=="], + "ts-morph": ["ts-morph@27.0.0", "", { "dependencies": { "@ts-morph/common": "~0.28.0", "code-block-writer": "^13.0.3" } }, "sha512-xcqelpTR5PCuZMs54qp9DE3t7tPgA2v/P1/qdW4ke5b3Y5liTGTYj6a/twT35EQW/H5okRqp1UOqwNlgg0K0eQ=="], "tsconfck": ["tsconfck@3.1.5", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "tsconfck": "bin/tsconfck.js" } }, "sha512-CLDfGgUp7XPswWnezWwsCRxNmgQjhYq3VXHM0/XIRxhVrKw0M1if9agzryh1QS3nxjCROvV+xWxoJO1YctzzWg=="], @@ -1715,6 +1771,8 @@ "tsup": ["tsup@8.5.0", "", { "dependencies": { "bundle-require": "^5.1.0", "cac": "^6.7.14", "chokidar": "^4.0.3", "consola": "^3.4.0", "debug": "^4.4.0", "esbuild": "^0.25.0", "fix-dts-default-cjs-exports": "^1.0.0", "joycon": "^3.1.1", "picocolors": "^1.1.1", "postcss-load-config": "^6.0.1", "resolve-from": "^5.0.0", "rollup": "^4.34.8", "source-map": "0.8.0-beta.0", "sucrase": "^3.35.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.11", "tree-kill": "^1.2.2" }, "peerDependencies": { "@microsoft/api-extractor": "^7.36.0", "@swc/core": "^1", "postcss": "^8.4.12", "typescript": ">=4.5.0" }, "optionalPeers": ["@microsoft/api-extractor", "@swc/core", "postcss", "typescript"], "bin": { "tsup": "dist/cli-default.js", "tsup-node": "dist/cli-node.js" } }, "sha512-VmBp77lWNQq6PfuMqCHD3xWl22vEoWsKajkF8t+yMBawlUS8JzEI+vOVMeuNZIuMML8qXRizFKi9oD5glKQVcQ=="], + "tsx": ["tsx@4.20.5", "", { "dependencies": { "esbuild": "~0.25.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-+wKjMNU9w/EaQayHXb7WA7ZaHY6hN8WgfvHNQ3t1PnU91/7O8TcTnIhCDYTZwnt8JsO9IBqZ30Ln1r7pPF52Aw=="], + "turbo": ["turbo@2.5.0", "", { "optionalDependencies": { "turbo-darwin-64": "2.5.0", "turbo-darwin-arm64": "2.5.0", "turbo-linux-64": "2.5.0", "turbo-linux-arm64": "2.5.0", "turbo-windows-64": "2.5.0", "turbo-windows-arm64": "2.5.0" }, "bin": { "turbo": "bin/turbo" } }, "sha512-PvSRruOsitjy6qdqwIIyolv99+fEn57gP6gn4zhsHTEcCYgXPhv6BAxzAjleS8XKpo+Y582vTTA9nuqYDmbRuA=="], "turbo-darwin-64": ["turbo-darwin-64@2.5.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-fP1hhI9zY8hv0idym3hAaXdPi80TLovmGmgZFocVAykFtOxF+GlfIgM/l4iLAV9ObIO4SUXPVWHeBZQQ+Hpjag=="], @@ -1871,37 +1929,75 @@ "@mdx-js/mdx/source-map": ["source-map@0.7.4", "", {}, "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA=="], - "@radix-ui/react-arrow/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.2", "", { "dependencies": { "@radix-ui/react-slot": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-uHa+l/lKfxuDD2zjN/0peM/RhhSmRjr5YWdk/37EnSv1nJ88uvG85DPexSm8HdFQROd2VdERJ6ynXbkCFi+APw=="], + "@radix-ui/react-avatar/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Pf/t/GkndH7CQ8wE2hbkXA+WyZ83fhQQn5DDmwDiDo6AwN/fhaH8oqZ0jRjMrO2iaMhDi6P1HRx6AZwyMinY1g=="], + + "@radix-ui/react-checkbox/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="], + + "@radix-ui/react-checkbox/@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA=="], "@radix-ui/react-checkbox/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.2", "", { "dependencies": { "@radix-ui/react-slot": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-uHa+l/lKfxuDD2zjN/0peM/RhhSmRjr5YWdk/37EnSv1nJ88uvG85DPexSm8HdFQROd2VdERJ6ynXbkCFi+APw=="], + "@radix-ui/react-collection/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Pf/t/GkndH7CQ8wE2hbkXA+WyZ83fhQQn5DDmwDiDo6AwN/fhaH8oqZ0jRjMrO2iaMhDi6P1HRx6AZwyMinY1g=="], + "@radix-ui/react-collection/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.0", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w=="], + "@radix-ui/react-dialog/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="], + + "@radix-ui/react-dialog/@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.8", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-hQsTUIn7p7fxCPvao/q6wpbxmCwgLrlz+nOrJgC+RwfZqWY/WN+UMqkXzrtKbPrF82P43eCTl3ekeKuyAQbFeg=="], + + "@radix-ui/react-dialog/@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA=="], + "@radix-ui/react-dialog/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.2", "", { "dependencies": { "@radix-ui/react-slot": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-uHa+l/lKfxuDD2zjN/0peM/RhhSmRjr5YWdk/37EnSv1nJ88uvG85DPexSm8HdFQROd2VdERJ6ynXbkCFi+APw=="], + "@radix-ui/react-dialog/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.2", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-y7TBO4xN4Y94FvcWIOIh18fM4R1A8S4q1jhoz4PNzOoHsFcN8pogcFmZrTYAm4F9VRUrWP/Mw7xSKybIeRI+CQ=="], + + "@radix-ui/react-dismissable-layer/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="], + "@radix-ui/react-dismissable-layer/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.2", "", { "dependencies": { "@radix-ui/react-slot": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-uHa+l/lKfxuDD2zjN/0peM/RhhSmRjr5YWdk/37EnSv1nJ88uvG85DPexSm8HdFQROd2VdERJ6ynXbkCFi+APw=="], + "@radix-ui/react-dropdown-menu/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="], + "@radix-ui/react-dropdown-menu/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.2", "", { "dependencies": { "@radix-ui/react-slot": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-uHa+l/lKfxuDD2zjN/0peM/RhhSmRjr5YWdk/37EnSv1nJ88uvG85DPexSm8HdFQROd2VdERJ6ynXbkCFi+APw=="], "@radix-ui/react-focus-scope/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.2", "", { "dependencies": { "@radix-ui/react-slot": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-uHa+l/lKfxuDD2zjN/0peM/RhhSmRjr5YWdk/37EnSv1nJ88uvG85DPexSm8HdFQROd2VdERJ6ynXbkCFi+APw=="], + "@radix-ui/react-hover-card/@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-escape-keydown": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg=="], + + "@radix-ui/react-hover-card/@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.8", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-rect": "1.1.1", "@radix-ui/react-use-size": "1.1.1", "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw=="], + "@radix-ui/react-label/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.2", "", { "dependencies": { "@radix-ui/react-slot": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-uHa+l/lKfxuDD2zjN/0peM/RhhSmRjr5YWdk/37EnSv1nJ88uvG85DPexSm8HdFQROd2VdERJ6ynXbkCFi+APw=="], + "@radix-ui/react-menu/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="], + "@radix-ui/react-menu/@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.6", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.2", "@radix-ui/react-slot": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-PbhRFK4lIEw9ADonj48tiYWzkllz81TM7KVYyyMMw2cwHO7D5h4XKEblL8NlaRisTK3QTe6tBEhDccFUryxHBQ=="], + "@radix-ui/react-menu/@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.6", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.6", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.2", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-rect": "1.1.1", "@radix-ui/react-use-size": "1.1.1", "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7iqXaOWIjDBfIG7aq8CUEeCSsQMLFdn7VEE8TaFz704DtEzpPHR7w/uuzRflvKgltqSAImgcmxQ7fFX3X7wasg=="], + + "@radix-ui/react-menu/@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.8", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-hQsTUIn7p7fxCPvao/q6wpbxmCwgLrlz+nOrJgC+RwfZqWY/WN+UMqkXzrtKbPrF82P43eCTl3ekeKuyAQbFeg=="], + + "@radix-ui/react-menu/@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA=="], + "@radix-ui/react-menu/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.2", "", { "dependencies": { "@radix-ui/react-slot": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-uHa+l/lKfxuDD2zjN/0peM/RhhSmRjr5YWdk/37EnSv1nJ88uvG85DPexSm8HdFQROd2VdERJ6ynXbkCFi+APw=="], - "@radix-ui/react-popover/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.2", "", { "dependencies": { "@radix-ui/react-slot": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-uHa+l/lKfxuDD2zjN/0peM/RhhSmRjr5YWdk/37EnSv1nJ88uvG85DPexSm8HdFQROd2VdERJ6ynXbkCFi+APw=="], + "@radix-ui/react-menu/@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.9", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-collection": "1.1.6", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.2", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-ZzrIFnMYHHCNqSNCsuN6l7wlewBEq0O0BCSBkabJMFXVO51LRUTq71gLP1UxFvmrXElqmPjA5VX7IqC9VpazAQ=="], + + "@radix-ui/react-menu/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.2", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-y7TBO4xN4Y94FvcWIOIh18fM4R1A8S4q1jhoz4PNzOoHsFcN8pogcFmZrTYAm4F9VRUrWP/Mw7xSKybIeRI+CQ=="], + + "@radix-ui/react-popover/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="], + + "@radix-ui/react-popover/@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.6", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.6", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.2", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-rect": "1.1.1", "@radix-ui/react-use-size": "1.1.1", "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7iqXaOWIjDBfIG7aq8CUEeCSsQMLFdn7VEE8TaFz704DtEzpPHR7w/uuzRflvKgltqSAImgcmxQ7fFX3X7wasg=="], + + "@radix-ui/react-popover/@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.8", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-hQsTUIn7p7fxCPvao/q6wpbxmCwgLrlz+nOrJgC+RwfZqWY/WN+UMqkXzrtKbPrF82P43eCTl3ekeKuyAQbFeg=="], - "@radix-ui/react-popper/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.2", "", { "dependencies": { "@radix-ui/react-slot": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-uHa+l/lKfxuDD2zjN/0peM/RhhSmRjr5YWdk/37EnSv1nJ88uvG85DPexSm8HdFQROd2VdERJ6ynXbkCFi+APw=="], + "@radix-ui/react-popover/@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA=="], - "@radix-ui/react-portal/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.2", "", { "dependencies": { "@radix-ui/react-slot": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-uHa+l/lKfxuDD2zjN/0peM/RhhSmRjr5YWdk/37EnSv1nJ88uvG85DPexSm8HdFQROd2VdERJ6ynXbkCFi+APw=="], + "@radix-ui/react-popover/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.2", "", { "dependencies": { "@radix-ui/react-slot": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-uHa+l/lKfxuDD2zjN/0peM/RhhSmRjr5YWdk/37EnSv1nJ88uvG85DPexSm8HdFQROd2VdERJ6ynXbkCFi+APw=="], - "@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.0", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w=="], + "@radix-ui/react-popover/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.2", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-y7TBO4xN4Y94FvcWIOIh18fM4R1A8S4q1jhoz4PNzOoHsFcN8pogcFmZrTYAm4F9VRUrWP/Mw7xSKybIeRI+CQ=="], - "@radix-ui/react-roving-focus/@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.6", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.2", "@radix-ui/react-slot": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-PbhRFK4lIEw9ADonj48tiYWzkllz81TM7KVYyyMMw2cwHO7D5h4XKEblL8NlaRisTK3QTe6tBEhDccFUryxHBQ=="], + "@radix-ui/react-roving-focus/@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.7", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw=="], - "@radix-ui/react-roving-focus/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.2", "", { "dependencies": { "@radix-ui/react-slot": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-uHa+l/lKfxuDD2zjN/0peM/RhhSmRjr5YWdk/37EnSv1nJ88uvG85DPexSm8HdFQROd2VdERJ6ynXbkCFi+APw=="], + "@radix-ui/react-select/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="], "@radix-ui/react-select/@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.6", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.0.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-escape-keydown": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7gpgMT2gyKym9Jz2ZhlRXSg2y6cNQIK8d/cqBZ0RBCaps8pFryCWXiUKI+uHGFrhMrbGUP7U6PWgiXzIxoyF3Q=="], @@ -1911,19 +2007,27 @@ "@radix-ui/react-select/@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.5", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.3", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-ps/67ZqsFm+Mb6lSPJpfhRLrVL2i2fntgCmGMqqth4eaGUf+knAuuRtWVJrNjUhExgmdRqftSgzpf0DF0n6yXA=="], + "@radix-ui/react-select/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Pf/t/GkndH7CQ8wE2hbkXA+WyZ83fhQQn5DDmwDiDo6AwN/fhaH8oqZ0jRjMrO2iaMhDi6P1HRx6AZwyMinY1g=="], + "@radix-ui/react-select/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.0", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w=="], "@radix-ui/react-select/@radix-ui/react-use-controllable-state": ["@radix-ui/react-use-controllable-state@1.1.1", "", { "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-YnEXIy8/ga01Y1PN0VfaNH//MhA91JlEGVBDxDzROqwrAtG5Yr2QGEPz8A/rJA3C7ZAHryOYGaUv8fLSW2H/mg=="], "@radix-ui/react-separator/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.2", "", { "dependencies": { "@radix-ui/react-slot": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-uHa+l/lKfxuDD2zjN/0peM/RhhSmRjr5YWdk/37EnSv1nJ88uvG85DPexSm8HdFQROd2VdERJ6ynXbkCFi+APw=="], + "@radix-ui/react-slider/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="], + "@radix-ui/react-slider/@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.6", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.2", "@radix-ui/react-slot": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-PbhRFK4lIEw9ADonj48tiYWzkllz81TM7KVYyyMMw2cwHO7D5h4XKEblL8NlaRisTK3QTe6tBEhDccFUryxHBQ=="], "@radix-ui/react-slider/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.2", "", { "dependencies": { "@radix-ui/react-slot": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-uHa+l/lKfxuDD2zjN/0peM/RhhSmRjr5YWdk/37EnSv1nJ88uvG85DPexSm8HdFQROd2VdERJ6ynXbkCFi+APw=="], + "@radix-ui/react-switch/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="], + + "@radix-ui/react-switch/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Pf/t/GkndH7CQ8wE2hbkXA+WyZ83fhQQn5DDmwDiDo6AwN/fhaH8oqZ0jRjMrO2iaMhDi6P1HRx6AZwyMinY1g=="], + "@radix-ui/react-switch/@radix-ui/react-use-controllable-state": ["@radix-ui/react-use-controllable-state@1.1.1", "", { "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-YnEXIy8/ga01Y1PN0VfaNH//MhA91JlEGVBDxDzROqwrAtG5Yr2QGEPz8A/rJA3C7ZAHryOYGaUv8fLSW2H/mg=="], - "@radix-ui/react-tabs/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.2", "", { "dependencies": { "@radix-ui/react-slot": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-uHa+l/lKfxuDD2zjN/0peM/RhhSmRjr5YWdk/37EnSv1nJ88uvG85DPexSm8HdFQROd2VdERJ6ynXbkCFi+APw=="], + "@radix-ui/react-tooltip/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="], "@radix-ui/react-tooltip/@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.6", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.0.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-escape-keydown": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7gpgMT2gyKym9Jz2ZhlRXSg2y6cNQIK8d/cqBZ0RBCaps8pFryCWXiUKI+uHGFrhMrbGUP7U6PWgiXzIxoyF3Q=="], @@ -1933,16 +2037,18 @@ "@radix-ui/react-tooltip/@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-IrVLIhskYhH3nLvtcBLQFZr61tBG7wx7O3kEmdzcYwRGAEBmBicGGL7ATzNgruYJ3xBTbuzEEq9OXJM3PAX3tA=="], + "@radix-ui/react-tooltip/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Pf/t/GkndH7CQ8wE2hbkXA+WyZ83fhQQn5DDmwDiDo6AwN/fhaH8oqZ0jRjMrO2iaMhDi6P1HRx6AZwyMinY1g=="], + "@radix-ui/react-tooltip/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.0", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w=="], "@radix-ui/react-tooltip/@radix-ui/react-use-controllable-state": ["@radix-ui/react-use-controllable-state@1.1.1", "", { "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-YnEXIy8/ga01Y1PN0VfaNH//MhA91JlEGVBDxDzROqwrAtG5Yr2QGEPz8A/rJA3C7ZAHryOYGaUv8fLSW2H/mg=="], + "@radix-ui/react-visually-hidden/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Pf/t/GkndH7CQ8wE2hbkXA+WyZ83fhQQn5DDmwDiDo6AwN/fhaH8oqZ0jRjMrO2iaMhDi6P1HRx6AZwyMinY1g=="], + "@secretlint/core/debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="], "@testing-library/dom/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], - "@ts-morph/common/minimatch": ["minimatch@7.4.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw=="], - "accepts/mime-types": ["mime-types@3.0.1", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA=="], "body-parser/debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="], @@ -1959,6 +2065,8 @@ "cmdk/@radix-ui/react-dialog": ["@radix-ui/react-dialog@1.1.7", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.6", "@radix-ui/react-focus-guards": "1.1.2", "@radix-ui/react-focus-scope": "1.1.3", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-portal": "1.1.5", "@radix-ui/react-presence": "1.1.3", "@radix-ui/react-primitive": "2.0.3", "@radix-ui/react-slot": "1.2.0", "@radix-ui/react-use-controllable-state": "1.1.1", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-EIdma8C0C/I6kL6sO02avaCRqi3fmWJpxH6mqbVScorW6nNktzKJT/le7VPho3o/7wCsyRg3z0+Q+Obr0Gy/VQ=="], + "cmdk/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Pf/t/GkndH7CQ8wE2hbkXA+WyZ83fhQQn5DDmwDiDo6AwN/fhaH8oqZ0jRjMrO2iaMhDi6P1HRx6AZwyMinY1g=="], + "cosmiconfig/path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="], "estree-util-to-js/source-map": ["source-map@0.7.4", "", {}, "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA=="], @@ -2019,6 +2127,8 @@ "shadcn/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="], + "shadcn/ts-morph": ["ts-morph@18.0.0", "", { "dependencies": { "@ts-morph/common": "~0.19.0", "code-block-writer": "^12.0.0" } }, "sha512-Kg5u0mk19PIIe4islUI/HWRvm9bC1lHejK4S0oh1zaZ77TMZAEmQC0sHQYiu2RgCQFZKXz1fMVi/7nOOeirznA=="], + "shadcn/zod": ["zod@3.24.2", "", {}, "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ=="], "sharp/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], @@ -2083,10 +2193,36 @@ "@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="], + "@radix-ui/react-avatar/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.0", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w=="], + + "@radix-ui/react-checkbox/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.2", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-y7TBO4xN4Y94FvcWIOIh18fM4R1A8S4q1jhoz4PNzOoHsFcN8pogcFmZrTYAm4F9VRUrWP/Mw7xSKybIeRI+CQ=="], + + "@radix-ui/react-dismissable-layer/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.2", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-y7TBO4xN4Y94FvcWIOIh18fM4R1A8S4q1jhoz4PNzOoHsFcN8pogcFmZrTYAm4F9VRUrWP/Mw7xSKybIeRI+CQ=="], + + "@radix-ui/react-dropdown-menu/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.2", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-y7TBO4xN4Y94FvcWIOIh18fM4R1A8S4q1jhoz4PNzOoHsFcN8pogcFmZrTYAm4F9VRUrWP/Mw7xSKybIeRI+CQ=="], + + "@radix-ui/react-focus-scope/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.2", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-y7TBO4xN4Y94FvcWIOIh18fM4R1A8S4q1jhoz4PNzOoHsFcN8pogcFmZrTYAm4F9VRUrWP/Mw7xSKybIeRI+CQ=="], + + "@radix-ui/react-label/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.2", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-y7TBO4xN4Y94FvcWIOIh18fM4R1A8S4q1jhoz4PNzOoHsFcN8pogcFmZrTYAm4F9VRUrWP/Mw7xSKybIeRI+CQ=="], + + "@radix-ui/react-menu/@radix-ui/react-popper/@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.6", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-2JMfHJf/eVnwq+2dewT3C0acmCWD3XiVA1Da+jTDqo342UlU13WvXtqHhG+yJw5JeQmu4ue2eMy6gcEArLBlcw=="], + + "@radix-ui/react-popover/@radix-ui/react-popper/@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.6", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-2JMfHJf/eVnwq+2dewT3C0acmCWD3XiVA1Da+jTDqo342UlU13WvXtqHhG+yJw5JeQmu4ue2eMy6gcEArLBlcw=="], + "@radix-ui/react-select/@radix-ui/react-popper/@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.3", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-2dvVU4jva0qkNZH6HHWuSz5FN5GeU5tymvCgutF8WaXz9WnD1NgUhy73cqzkjkN4Zkn8lfTPv5JIfrC221W+Nw=="], + "@radix-ui/react-separator/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.2", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-y7TBO4xN4Y94FvcWIOIh18fM4R1A8S4q1jhoz4PNzOoHsFcN8pogcFmZrTYAm4F9VRUrWP/Mw7xSKybIeRI+CQ=="], + + "@radix-ui/react-slider/@radix-ui/react-collection/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.2", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-y7TBO4xN4Y94FvcWIOIh18fM4R1A8S4q1jhoz4PNzOoHsFcN8pogcFmZrTYAm4F9VRUrWP/Mw7xSKybIeRI+CQ=="], + + "@radix-ui/react-slider/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.2", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-y7TBO4xN4Y94FvcWIOIh18fM4R1A8S4q1jhoz4PNzOoHsFcN8pogcFmZrTYAm4F9VRUrWP/Mw7xSKybIeRI+CQ=="], + + "@radix-ui/react-switch/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.0", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w=="], + "@radix-ui/react-tooltip/@radix-ui/react-popper/@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.3", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-2dvVU4jva0qkNZH6HHWuSz5FN5GeU5tymvCgutF8WaXz9WnD1NgUhy73cqzkjkN4Zkn8lfTPv5JIfrC221W+Nw=="], + "@radix-ui/react-visually-hidden/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.0", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w=="], + "accepts/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], "cli-truncate/slice-ansi/ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="], @@ -2097,6 +2233,8 @@ "cliui/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + "cmdk/@radix-ui/react-dialog/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="], + "cmdk/@radix-ui/react-dialog/@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.6", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.0.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-escape-keydown": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7gpgMT2gyKym9Jz2ZhlRXSg2y6cNQIK8d/cqBZ0RBCaps8pFryCWXiUKI+uHGFrhMrbGUP7U6PWgiXzIxoyF3Q=="], "cmdk/@radix-ui/react-dialog/@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.0.3", "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-4XaDlq0bPt7oJwR+0k0clCiCO/7lO7NKZTAaJBYxDNQT/vj4ig0/UvctrRscZaFREpRvUTkpKR96ov1e6jptQg=="], @@ -2109,6 +2247,8 @@ "cmdk/@radix-ui/react-dialog/@radix-ui/react-use-controllable-state": ["@radix-ui/react-use-controllable-state@1.1.1", "", { "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-YnEXIy8/ga01Y1PN0VfaNH//MhA91JlEGVBDxDzROqwrAtG5Yr2QGEPz8A/rJA3C7ZAHryOYGaUv8fLSW2H/mg=="], + "cmdk/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.0", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w=="], + "express/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], "next/postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], @@ -2125,18 +2265,26 @@ "shadcn/postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + "shadcn/ts-morph/@ts-morph/common": ["@ts-morph/common@0.19.0", "", { "dependencies": { "fast-glob": "^3.2.12", "minimatch": "^7.4.3", "mkdirp": "^2.1.6", "path-browserify": "^1.0.1" } }, "sha512-Unz/WHmd4pGax91rdIKWi51wnVUW11QttMEPpBiBgIewnc9UQIX7UDLxr5vRlqeByXCwhkF6VabSsI0raWcyAQ=="], + + "shadcn/ts-morph/code-block-writer": ["code-block-writer@12.0.0", "", {}, "sha512-q4dMFMlXtKR3XNBHyMHt/3pwYNA69EDk00lloMOaaUMKPUXBw6lpXtbu3MMVG6/uOihGnRDOlkyqsONEUj60+w=="], + "source-map/whatwg-url/tr46": ["tr46@1.0.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA=="], "source-map/whatwg-url/webidl-conversions": ["webidl-conversions@4.0.2", "", {}, "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg=="], "type-is/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], + "vaul/@radix-ui/react-dialog/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="], + "vaul/@radix-ui/react-dialog/@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.8", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.1", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-escape-keydown": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-md5dYvyWDY6884yKjXZA8c+iezVMW5rkdxGwwZJ/TieN5al6UBI5YQGZzkuHbA45S3WqrfG6YwDBMxk4BqmbuA=="], "vaul/@radix-ui/react-dialog/@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.5", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.1", "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-+LNWvYmkkwwAB6NZ3HJOfjwYs8GFz5kR8rUZzf5QQGp1ckjVkn5dPcHv5negHL8GyN0qOsCzY66W7NkasPY0PA=="], "vaul/@radix-ui/react-dialog/@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.1", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-r2Cpk0yzycgplKYq+X9mo+2o3g12RpTeTZslQocXrAsSY9cz8HJ/QAVb6kxFOhd9U6eq9zTACw1X3JZeLQHj2A=="], + "vaul/@radix-ui/react-dialog/@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA=="], + "vaul/@radix-ui/react-dialog/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.1", "", { "dependencies": { "@radix-ui/react-slot": "1.2.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-+YilAeO2cNOSdE7hh0lp5AJCRI8bwl2sNVWRdl9aQN3JUXZ1/TNZomOAHFvXamLXj1/KPSyjA2p+/Evl6rRrqA=="], "vaul/@radix-ui/react-dialog/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RJMUK12Fr2fUEK18xtbY9akYytRqhPzbeTTlWZoCNfXjxCGDnprfBzpobZBS2oWHNtVZnrfTQ2iC8sdUFG3SvQ=="], @@ -2161,6 +2309,8 @@ "ora/cli-cursor/restore-cursor/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + "shadcn/ts-morph/@ts-morph/common/minimatch": ["minimatch@7.4.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw=="], + "ora/cli-cursor/restore-cursor/onetime/mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="], } } diff --git a/package.json b/package.json index 16552582..d783228d 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,9 @@ "private": true, "scripts": { "dev": "turbo run dev", - "dev:up": "turbo run dev test:watch", + "dev:up": "turbo run dev test:watch types:meta:watch", + "types:meta": "turbo run types:meta", + "types:meta:watch": "turbo run types:meta:watch", "test": "turbo run test", "test:watch": "turbo run test:watch", "bench": "turbo run bench", @@ -18,7 +20,7 @@ "filters:llm": "repomix --include \"packages/filters/**/*\" --ignore \"**/locales/*.json\" --copy --stdout" }, "devDependencies": { - "@biomejs/biome": "2.0.6", + "@biomejs/biome": "2.2.2", "@types/node": "^22", "husky": "^9.1.7", "lint-staged": "^15.5.0", @@ -29,7 +31,7 @@ "engines": { "node": ">=22" }, - "packageManager": "bun@1.2.4", + "packageManager": "bun@1.2.21", "workspaces": [ "apps/*", "packages/*" diff --git a/packages/action-menu/package.json b/packages/action-menu/package.json new file mode 100644 index 00000000..a7c80c9f --- /dev/null +++ b/packages/action-menu/package.json @@ -0,0 +1,76 @@ +{ + "name": "@bazza-ui/action-menu", + "version": "0.0.2025082700", + "private": false, + "publishConfig": { + "access": "public" + }, + "type": "module", + "main": "./dist/index.js", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.cjs" + } + }, + "files": [ + "dist" + ], + "scripts": { + "dev": "tsc --watch", + "build": "tsup", + "test": "vitest run --run --mode test", + "test:watch": "vitest --mode test --watch --disable-console-intercept", + "bench": "vitest bench --run", + "bench:watch": "vitest bench --watch", + "/**** Version Bumping (Recommended)": "", + "release:patch": "bun scripts/release.ts --bump patch --path .", + "release:minor": "bun scripts/release.ts --bump minor --path .", + "release:major": "bun scripts/release.ts --bump major --path .", + "/**** Dry runs": "", + "release:patch:dry": "bun scripts/release.ts --bump patch --path . --dry-run", + "release:minor:dry": "bun scripts/release.ts --bump minor --path . --dry-run", + "release:major:dry": "bun scripts/release.ts --bump major --path . --dry-run", + "/**** Canary releases": "", + "release:patch:canary": "bun scripts/release.ts --bump patch --path . --tag canary", + "release:minor:canary": "bun scripts/release.ts --bump minor --path . --tag canary", + "release:major:canary": "bun scripts/release.ts --bump major --path . --tag canary", + "/**** Utilities": "", + "version": "bun scripts/generate-version.ts 1 0 .", + "publish-package": "bun scripts/publish.ts .", + "publish-package:dry": "bun scripts/publish.ts . --dry-run" + }, + "devDependencies": { + "@bazza-ui/typescript-config": "*", + "@testing-library/dom": "^10.4.0", + "@testing-library/react": "^16.3.0", + "@types/react": "^19.1.1", + "@types/react-dom": "^19.1.2", + "@vitejs/plugin-react": "^4.3.4", + "tinybench": "^4.0.1", + "tsup": "^8.5.0", + "typescript": "^5", + "vite-tsconfig-paths": "^5.1.4", + "vitest": "^3.2.4" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + }, + "dependencies": { + "@radix-ui/primitive": "^1.1.2", + "@radix-ui/react-compose-refs": "^1.1.2", + "@radix-ui/react-popper": "^1.2.7", + "@radix-ui/react-portal": "^1.1.9", + "@radix-ui/react-presence": "^1.1.4", + "@radix-ui/react-primitive": "^2.1.3", + "@radix-ui/react-use-controllable-state": "^1.2.2", + "clsx": "^2.1.1", + "remeda": "^2.30.0", + "tailwind-merge": "^3.3.1", + "vaul": "^1.1.2" + } +} diff --git a/packages/action-menu/scripts/generate-version.ts b/packages/action-menu/scripts/generate-version.ts new file mode 100644 index 00000000..49900566 --- /dev/null +++ b/packages/action-menu/scripts/generate-version.ts @@ -0,0 +1,139 @@ +#!/usr/bin/env bun + +import { execSync } from 'node:child_process' +import { readFileSync, writeFileSync } from 'node:fs' +import { join } from 'node:path' + +interface VersionConfig { + major: number + minor: number + packagePath: string +} + +/** + * Generates a custom semver version: major.minor. + * + * Why this format? + * - Automated builds need unique versions + * - Build tags aren't comparable, so don't work for npm + * - Previous format (with hour/minute) exceeded 32-bit limit in Bun + * - Current format allows 100 releases per day and works until ~year 4050 + */ +async function generateVersion({ + major, + minor, + packagePath, +}: VersionConfig): Promise { + const now = new Date() + const year = now.getFullYear() + const month = String(now.getMonth() + 1).padStart(2, '0') + const day = String(now.getDate()).padStart(2, '0') + + // Get today's date prefix + const datePrefix = `${year}${month}${day}` + + // Find existing versions for today + const counter = await getTodaysVersionCounter(datePrefix, packagePath) + const paddedCounter = String(counter).padStart(2, '0') + + const version = `${major}.${minor}.${datePrefix}${paddedCounter}` + + console.log(`Generated version: ${version}`) + console.log(` Date: ${year}-${month}-${day}`) + console.log(` Counter: ${counter}`) + + return version +} + +async function getTodaysVersionCounter( + datePrefix: string, + packagePath: string, +): Promise { + try { + // Get the package name from package.json + const packageJson = JSON.parse( + readFileSync(join(packagePath, 'package.json'), 'utf-8'), + ) + const packageName = packageJson.name + + // Query npm for existing versions + const npmViewCmd = `npm view ${packageName} versions --json` + const result = execSync(npmViewCmd, { encoding: 'utf-8', stdio: 'pipe' }) + const versions: string[] = JSON.parse(result) + + // Find versions from today + const todaysVersions = versions + .filter((v) => { + const parts = v.split('.') + return parts[2]?.startsWith(datePrefix) + }) + .map((v) => { + const parts = v.split('.') + const patch = parts[2] || '0' + const counterStr = patch.slice(datePrefix.length) + return Number.parseInt(counterStr) || 0 + }) + .sort((a, b) => b - a) // Descending order + + // Return next counter (highest + 1, or 0 if none exist) + return todaysVersions.length > 0 ? todaysVersions[0] + 1 : 0 + } catch (error) { + console.log( + 'No existing versions found or package not published yet, starting with counter 0', + ) + return 0 + } +} + +async function updatePackageJson( + packagePath: string, + version: string, +): Promise { + const packageJsonPath = join(packagePath, 'package.json') + const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8')) + + packageJson.version = version + packageJson.private = false // Ensure it's publishable + + writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n') + console.log(`Updated ${packageJsonPath} with version ${version}`) +} + +async function main() { + const args = process.argv.slice(2) + const [majorStr, minorStr, packagePath = '.'] = args + + if (!majorStr || !minorStr) { + console.error( + 'Usage: bun scripts/generate-version.ts [package-path]', + ) + console.error( + 'Example: bun scripts/generate-version.ts 1 0 packages/filters', + ) + process.exit(1) + } + + const major = Number.parseInt(majorStr) + const minor = Number.parseInt(minorStr) + + if (isNaN(major) || isNaN(minor)) { + console.error('Major and minor must be valid numbers') + process.exit(1) + } + + try { + const version = await generateVersion({ major, minor, packagePath }) + await updatePackageJson(packagePath, version) + + // Output the version for use in CI/CD + console.log(`::set-output name=version::${version}`) + } catch (error) { + console.error('Error generating version:', error) + process.exit(1) + } +} + +// @ts-expect-error +if (import.meta.main) { + main() +} diff --git a/packages/action-menu/scripts/publish.ts b/packages/action-menu/scripts/publish.ts new file mode 100644 index 00000000..11e7fff8 --- /dev/null +++ b/packages/action-menu/scripts/publish.ts @@ -0,0 +1,160 @@ +#!/usr/bin/env bun + +import { execSync } from 'node:child_process' +import { readFileSync } from 'node:fs' +import { join } from 'node:path' + +interface PublishOptions { + packagePath: string + dryRun?: boolean + noGitTag?: boolean + tag?: string + registry?: string +} + +async function publishPackage({ + packagePath, + dryRun = false, + noGitTag = false, + tag = 'latest', + registry = 'https://registry.npmjs.org/', +}: PublishOptions) { + console.log(`🚀 Publishing package from ${packagePath}`) + + // Verify package.json exists and is valid + const packageJsonPath = join(packagePath, 'package.json') + let packageJson: any + try { + packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8')) + } catch (error) { + throw new Error( + `Failed to read package.json from ${packageJsonPath}: ${error}`, + ) + } + + if (packageJson.private) { + throw new Error( + 'Cannot publish private package. Set "private": false in package.json', + ) + } + + console.log(`📦 Package: ${packageJson.name}@${packageJson.version}`) + + // Build the package first + console.log('🔨 Building package...') + try { + execSync('bun run build', { + cwd: packagePath, + stdio: 'inherit', + }) + } catch (error) { + throw new Error(`Build failed: ${error}`) + } + + // Run tests + console.log('🧪 Running tests...') + try { + execSync('bun run test', { + cwd: packagePath, + stdio: 'inherit', + }) + } catch (error) { + console.warn('! Tests failed, but continuing with publish...') + } + + // Prepare npm publish command + const publishCmd = [ + 'npm publish', + `--tag ${tag}`, + `--registry ${registry}`, + dryRun ? '--dry-run' : '', + ] + .filter(Boolean) + .join(' ') + + console.log(`📤 Publishing with: ${publishCmd}`) + + if (dryRun) { + console.log('🔍 DRY RUN - Not actually publishing') + } + + try { + execSync(publishCmd, { + cwd: packagePath, + stdio: 'inherit', + env: { + ...process.env, + NODE_AUTH_TOKEN: process.env.NPM_TOKEN || process.env.NODE_AUTH_TOKEN, + }, + }) + + if (!dryRun) { + console.log( + `✅ Successfully published ${packageJson.name}@${packageJson.version}`, + ) + + // Tag git commit + if (!noGitTag) { + try { + execSync(`git tag ${packageJson.name}@${packageJson.version}`, { + stdio: 'pipe', + }) + execSync( + `git push origin ${packageJson.name}@${packageJson.version}`, + { + stdio: 'pipe', + }, + ) + console.log(`🏷 Created git tag v${packageJson.version}`) + } catch (error) { + console.warn('! Failed to create git tag:', error) + } + } + } else { + console.log('✅ Dry run completed successfully') + } + } catch (error) { + throw new Error(`Publish failed: ${error}`) + } +} + +async function main() { + const args = process.argv.slice(2) + + let packagePath = '.' + let dryRun = false + let noGitTag = false + let tag = 'latest' + let registry = 'https://registry.npmjs.org/' + + // Parse arguments + for (let i = 0; i < args.length; i++) { + const arg = args[i] + + if (arg === '--dry-run') { + dryRun = true + } else if (arg === '--no-git-tag') { + noGitTag = true + } else if (arg === '--tag') { + tag = args[++i] || 'latest' + } else if (arg === '--registry') { + registry = args[++i] || registry + } else if (arg === '--path') { + packagePath = args[++i] || '.' + } else if (!arg.startsWith('--')) { + packagePath = arg + } + } + + try { + await publishPackage({ packagePath, dryRun, noGitTag, tag, registry }) + } catch (error) { + console.error('❌ Publish failed:', error) + process.exit(1) + } +} + +// @ts-expect-error +if (import.meta.main) { + main() +} diff --git a/packages/action-menu/scripts/release.ts b/packages/action-menu/scripts/release.ts new file mode 100644 index 00000000..00910060 --- /dev/null +++ b/packages/action-menu/scripts/release.ts @@ -0,0 +1,281 @@ +#!/usr/bin/env bun + +import { execSync } from 'node:child_process' +import { readFileSync } from 'node:fs' +import { join } from 'node:path' + +type BumpType = 'major' | 'minor' | 'patch' + +interface ReleaseOptions { + major?: number + minor?: number + packagePath: string + dryRun: boolean + noGitTag: boolean + tag: string + bump?: BumpType +} + +interface CurrentVersion { + major: number + minor: number + patch: string + full: string +} + +function parseCurrentVersion(packagePath: string): CurrentVersion | null { + try { + const packageJsonPath = join(packagePath, 'package.json') + const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8')) + + if (!packageJson.version || packageJson.version === '0.0.0') { + return null + } + + const [major, minor, patch] = packageJson.version.split('.') + + return { + major: Number.parseInt(major), + minor: Number.parseInt(minor), + patch: patch, + full: packageJson.version, + } + } catch (error) { + console.warn(`! Could not parse current version: ${error}`) + return null + } +} + +function calculateBumpedVersion( + current: CurrentVersion, + bumpType: BumpType, +): { major: number; minor: number } { + switch (bumpType) { + case 'major': + return { + major: current.major + 1, + minor: 0, + } + + case 'minor': + return { + major: current.major, + minor: current.minor + 1, + } + + case 'patch': + // For patch, we keep same major.minor but generate new patch (handled by generate-version.ts) + return { + major: current.major, + minor: current.minor, + } + + default: + throw new Error(`Unknown bump type: ${bumpType}`) + } +} + +async function release({ + major, + minor, + packagePath, + dryRun, + noGitTag, + tag, + bump, +}: ReleaseOptions) { + console.log(`🚀 Starting release process for ${packagePath}`) + + let finalMajor: number + let finalMinor: number + + if (bump) { + console.log(` Bump type: ${bump}`) + + const current = parseCurrentVersion(packagePath) + if (!current) { + throw new Error( + 'Cannot bump version - no current version found. Use --major and --minor to set initial version.', + ) + } + + console.log(` Current version: ${current.full}`) + + const bumped = calculateBumpedVersion(current, bump) + finalMajor = bumped.major + finalMinor = bumped.minor + + console.log( + ` Bumping ${bump}: ${current.major}.${current.minor}.* → ${finalMajor}.${finalMinor}.*`, + ) + } else if (major !== undefined && minor !== undefined) { + console.log(` Explicit version: ${major}.${minor}.YYYYMMDDNN`) + finalMajor = major + finalMinor = minor + } else { + throw new Error( + 'Must specify either --bump or both --major and --minor', + ) + } + + console.log(` Final version: ${finalMajor}.${finalMinor}.YYYYMMDDNN`) + console.log(` Dry run: ${dryRun}`) + console.log(` Tag: ${tag}`) + + try { + // Step 1: Generate version + console.log('\n📝 Step 1: Generating version...') + execSync( + `bun scripts/generate-version.ts ${finalMajor} ${finalMinor} ${packagePath}`, + { + stdio: 'inherit', + }, + ) + + // Step 2: Publish + console.log('\n📦 Step 2: Publishing...') + const publishArgs = [ + packagePath, + dryRun ? '--dry-run' : '', + noGitTag ? '--no-git-tag' : '', + `--tag ${tag}`, + ] + .filter(Boolean) + .join(' ') + + execSync(`bun scripts/publish.ts ${publishArgs}`, { + stdio: 'inherit', + }) + + console.log('\n✅ Release completed successfully!') + } catch (error) { + console.error('\n❌ Release failed:', error) + process.exit(1) + } +} + +async function main() { + const args = process.argv.slice(2) + + let major: number | undefined + let minor: number | undefined + let packagePath = '.' + let dryRun = false + let noGitTag = false + let tag = 'latest' + let bump: BumpType | undefined + + // Parse arguments + for (let i = 0; i < args.length; i++) { + const arg = args[i] + + if (arg === '--major') { + major = Number.parseInt(args[++i]) + } else if (arg === '--minor') { + minor = Number.parseInt(args[++i]) + } else if (arg === '--bump') { + const bumpValue = args[++i] + if (!bumpValue || !['major', 'minor', 'patch'].includes(bumpValue)) { + console.error('❌ Error: --bump must be one of: major, minor, patch') + process.exit(1) + } + bump = bumpValue as BumpType + } else if (arg === '--path') { + packagePath = args[++i] || '.' + } else if (arg === '--dry-run') { + dryRun = true + } else if (arg === '--no-git-tag') { + noGitTag = true + } else if (arg === '--tag') { + tag = args[++i] || 'latest' + } else if (arg === '--help' || arg === '-h') { + console.log(` +🚀 Release Script + +Usage: + bun scripts/release.ts --bump [options] + bun scripts/release.ts --major --minor [options] + +Version Bumping: + --bump major Increment major version (e.g., 1.0.* → 2.0.*) + --bump minor Increment minor version (e.g., 1.0.* → 1.1.*) + --bump patch Increment patch counter for today (e.g., 1.0.20240115000 → 1.0.20240115001) + +Explicit Version: + --major Major version number + --minor Minor version number + +Options: + --path Path to package directory (default: .) + --dry-run Run without actually publishing + --no-git-tag Do not create a git tag + --tag NPM dist tag (default: latest) + --help, -h Show this help + +Examples: + # Bump patch (increment daily counter) + bun scripts/release.ts --bump patch + + # Bump minor version (1.0.* → 1.1.YYYYMMDD00) + bun scripts/release.ts --bump minor + + # Bump major version (1.0.* → 2.0.YYYYMMDD00) + bun scripts/release.ts --bump major + + # Explicit version (legacy mode) + bun scripts/release.ts --major 1 --minor 0 + + # Dry run with bump + bun scripts/release.ts --bump patch --dry-run + + # Specific package path + bun scripts/release.ts --bump minor --path packages/filters + + # Beta release with bump + bun scripts/release.ts --bump patch --tag beta + +Version Format: + major.minor. + + Examples: + - 1.0.2024011500 (first release on Jan 15, 2024) + - 1.0.2024011501 (second release same day) + - 1.1.2024011600 (minor bump next day) +`) + process.exit(0) + } + } + + // Validation + if (bump && (major !== undefined || minor !== undefined)) { + console.error( + '❌ Error: Cannot use --bump with --major/--minor. Choose one approach.', + ) + process.exit(1) + } + + if (!bump && (major === undefined || minor === undefined)) { + console.error( + '❌ Error: Must specify either --bump or both --major and --minor', + ) + console.error('Use --help for usage information') + process.exit(1) + } + + if (major !== undefined && (Number.isNaN(major) || major < 0)) { + console.error('❌ Error: --major must be a valid non-negative number') + process.exit(1) + } + + if (minor !== undefined && (Number.isNaN(minor) || minor < 0)) { + console.error('❌ Error: --minor must be a valid non-negative number') + process.exit(1) + } + + await release({ major, minor, packagePath, dryRun, noGitTag, tag, bump }) +} + +// @ts-expect-error +if (import.meta.main) { + main() +} diff --git a/packages/action-menu/src/action-menu.tsx b/packages/action-menu/src/action-menu.tsx new file mode 100644 index 00000000..bd6b0202 --- /dev/null +++ b/packages/action-menu/src/action-menu.tsx @@ -0,0 +1,2931 @@ +/** biome-ignore-all lint/a11y/useSemanticElements: This library renders ARIA-only primitives intentionally. */ + +import { composeEventHandlers } from '@radix-ui/primitive' +import { composeRefs } from '@radix-ui/react-compose-refs' +import * as DismissableLayer from '@radix-ui/react-dismissable-layer' +import * as Popper from '@radix-ui/react-popper' +import { Portal } from '@radix-ui/react-portal' +import { Presence } from '@radix-ui/react-presence' +import { Primitive } from '@radix-ui/react-primitive' +import { useControllableState } from '@radix-ui/react-use-controllable-state' +import * as React from 'react' +import { flat, partition, pipe, prop, sortBy } from 'remeda' +import { Drawer } from 'vaul' +import { cn } from './cn.js' +import { commandScore } from './command-score.js' +import { useMediaQuery } from './use-media-query.js' + +/* ================================================================================================ + * Types — Menu model (generic) + * ============================================================================================== */ + +export type MenuNodeKind = 'item' | 'group' | 'submenu' + +export type BaseDef = { + /** The kind of node. */ + kind: K + /** Unique id for this node. */ + id: string + hidden?: boolean +} + +export type Searchable = { + /** A human-readable label for the searchable item. */ + label?: string + /** A list of aliases for the node, used when searching/filtering. */ + keywords?: string[] +} + +export type Iconish = + | React.ReactNode + | React.ReactElement + | React.ElementType + | React.ComponentType<{ className?: string }> + +export type MenuDef = { + id: string + title?: string + inputPlaceholder?: string + hideSearchUntilActive?: boolean + nodes?: NodeDef[] + defaults?: MenuNodeDefaults + ui?: { + slots?: Partial> + slotProps?: Partial + classNames?: Partial + } +} + +export type ItemDef = BaseDef<'item'> & + Searchable & { + icon?: Iconish + /** Arman is a bitch. */ + data?: T + onSelect?: (args: { + node: Omit, 'onSelect'> + search?: SearchContext + }) => void + closeOnSelect?: boolean + } + +export type GroupDef = BaseDef<'group'> & { + nodes: (ItemDef | SubmenuDef)[] + heading?: string +} + +export type SubmenuDef = BaseDef<'submenu'> & + Searchable & { + nodes: NodeDef[] + data?: T + icon?: Iconish + title?: string + inputPlaceholder?: string + hideSearchUntilActive?: boolean + defaults?: MenuNodeDefaults + ui?: { + slots?: Partial> + slotProps?: Partial + classNames?: Partial + } + } + +export type Menu = Omit, 'nodes'> & { + nodes: Node[] + surfaceId: string + depth: number +} + +/** Runtime node (instance) */ +export type BaseNode> = { + /** The kind of node. */ + kind: K + /** Unique id for this node. */ + id: string + hidden?: boolean + /** Owning menu surface at runtime. */ + parent: Menu + /** Original author definition for this node. */ + def: D +} + +export type ItemNode = BaseNode<'item', ItemDef> & + Omit, 'kind' | 'hidden'> + +export type GroupNode = BaseNode<'group', GroupDef> & { + heading?: string + nodes: (ItemNode | SubmenuNode)[] +} + +/** NOTE: Submenu node exposes its runtime child menu as `child` */ +export type SubmenuNode = BaseNode< + 'submenu', + SubmenuDef +> & + Omit, 'kind' | 'hidden' | 'nodes'> & { + child: Menu + nodes: Node[] + } + +export type Node = ItemNode | GroupNode | SubmenuNode + +export type NodeDef = ItemDef | GroupDef | SubmenuDef + +/** Additional context passed to item/submenu renderers during search. */ +export type SearchContext = { + query: string + isDeep: boolean + breadcrumbs: string[] + breadcrumbIds: string[] +} + +/** Defaulted parts of nodes for convenience. */ +export type MenuNodeDefaults = { + item?: Pick, 'onSelect' | 'closeOnSelect'> +} + +function instantiateMenuFromDef( + def: MenuDef, + surfaceId: string, + depth: number, +): Menu { + const parentless: Menu = { + id: def.id, + title: def.title, + inputPlaceholder: def.inputPlaceholder, + hideSearchUntilActive: def.hideSearchUntilActive, + defaults: def.defaults, + ui: def.ui, + nodes: [] as Node[], + surfaceId, + depth, + } + + function inst(d: NodeDef, parent: Menu): Node { + if (d.kind === 'item') { + const node: ItemNode = { + ...(d as ItemDef), + kind: 'item', + parent, + def: d, + } + return node + } + + if (d.kind === 'group') { + const children = (d.nodes ?? []).map((c) => + inst(c as NodeDef, parent), + ) + const node: GroupNode = { + id: d.id, + kind: 'group', + hidden: d.hidden, + parent, + def: d as GroupDef, + heading: (d as GroupDef).heading, + nodes: children as (ItemNode | SubmenuNode)[], + } + return node + } + + // submenu + const subDef = d as SubmenuDef + const childSurfaceId = `${parent.surfaceId}::${subDef.id}` + + // ! In TSX, don't write instantiateMenuFromDef(...) + // Use casts instead of a generic call to avoid `` being parsed as JSX: + const child = instantiateMenuFromDef( + { + id: subDef.id, + title: subDef.title, + inputPlaceholder: subDef.inputPlaceholder, + hideSearchUntilActive: subDef.hideSearchUntilActive, + nodes: subDef.nodes as NodeDef[], + defaults: subDef.defaults as MenuNodeDefaults | undefined, + ui: subDef.ui as MenuDef['ui'], + } as MenuDef, + childSurfaceId, + parent.depth + 1, + ) as Menu + + const node: SubmenuNode = { + ...(subDef as SubmenuDef), + kind: 'submenu', + parent, + def: d, + child, + nodes: child.nodes, + } + + return node as Node + } + + parentless.nodes = (def.nodes ?? []).map((n) => + inst(n as any, parentless), + ) as any + return parentless +} + +/* ================================================================================================ + * Types — Renderer & bind APIs + * ============================================================================================== */ + +type DivProps = React.ComponentPropsWithoutRef +type ButtonProps = React.ComponentPropsWithoutRef +type Children = Pick + +/** Row interaction & wiring helpers provided to slot renderers. */ +export type RowBindAPI = { + focused: boolean + disabled: boolean + getRowProps: >( + overrides?: T, + ) => T & { + ref: React.Ref + id: string + role: 'option' + tabIndex: -1 + 'data-action-menu-item-id': string + 'data-focused'?: 'true' + 'aria-selected'?: boolean + 'aria-disabled'?: boolean + } +} + +/** Content/surface wiring helpers provided to slot renderers. */ +export type ContentBindAPI = { + getContentProps: >( + overrides?: T, + ) => T & { + ref: React.Ref + role: 'menu' + tabIndex: -1 + 'data-slot': 'action-menu-content' + 'data-state': 'open' | 'closed' + 'data-action-menu-surface': true + 'data-surface-id': string + } +} + +/** Search input wiring helpers provided to slot renderers. */ +export type InputBindAPI = { + getInputProps: >( + overrides?: T, + ) => T & { + ref: React.Ref + role: 'combobox' + 'data-slot': 'action-menu-input' + 'data-action-menu-input': true + 'aria-autocomplete': 'list' + 'aria-expanded': true + 'aria-controls'?: string + 'aria-activedescendant'?: string + } +} + +/** List wiring helpers provided to slot renderers. */ +export type ListBindAPI = { + getListProps: >( + overrides?: T, + ) => T & { + ref: React.Ref + role: 'listbox' + id: string + tabIndex: number + 'data-slot': 'action-menu-list' + 'data-action-menu-list': true + 'aria-activedescendant'?: string + } + getItemOrder: () => string[] + getActiveId: () => string | null +} + +/* ================================================================================================ + * Types — ClassNames & SlotProps (SPLIT into Shell vs Surface) + * ============================================================================================== */ + +/** ClassNames that style the *shell* (overlay / drawer container / trigger). */ +export type ShellClassNames = { + root?: string + overlay?: string + drawerContent?: string + drawerHandle?: string + trigger?: string +} + +/** ClassNames that style the *surface* (content/list/items/etc.). */ +export type SurfaceClassNames = { + root?: string // (reserved) if you wrap the entire surface + content?: string + input?: string + list?: string + item?: string + subtrigger?: string + group?: string + groupHeading?: string +} + +/** Slot props forwarded to the *shell* (Vaul). */ +export type ShellSlotProps = { + drawerRoot?: Partial> + drawerOverlay?: React.ComponentPropsWithoutRef + drawerContent?: React.ComponentPropsWithoutRef +} + +/** Slot props forwarded to *surface* slots (Input/List/Content/Header/Footer). */ +export type SurfaceSlotProps = { + content?: React.HTMLAttributes + header?: React.HTMLAttributes + input?: React.InputHTMLAttributes + list?: React.HTMLAttributes + footer?: React.HTMLAttributes +} + +/** Slot renderers to customize visuals. */ +export type MenuSlots = { + Item: (args: { + node: ItemNode + search?: SearchContext + mode: Omit + bind: RowBindAPI + }) => React.ReactNode + Content: (args: { + menu: Menu + children: React.ReactNode + bind: ContentBindAPI + }) => React.ReactNode + Header?: (args: { menu: Menu }) => React.ReactNode + Input: (args: { + value: string + onChange: (v: string) => void + bind: InputBindAPI + }) => React.ReactNode + Empty?: (args: { query: string }) => React.ReactNode + List: (args: { + children: React.ReactNode + bind: ListBindAPI + }) => React.ReactNode + Footer?: (args: { menu: Menu }) => React.ReactNode + SubmenuTrigger: (args: { + node: SubmenuNode + search?: SearchContext + bind: RowBindAPI + }) => React.ReactNode +} + +/* Default minimal renderers (safe fallbacks) */ +function defaultSlots(): Required> { + return { + Content: ({ children, bind }) => ( +
{children}
+ ), + Header: () => null, + Input: ({ value, onChange, bind }) => ( + onChange(e.target.value), + })} + /> + ), + List: ({ children, bind }) => ( +
{children}
+ ), + Empty: ({ query }) => ( +
+ No results{query ? ` for “${query}”` : ''}. +
+ ), + Item: ({ node, bind }) => ( +
+ {node.icon ? {renderIcon(node.icon)} : null} + {node.label ?? String(node.id)} +
+ ), + SubmenuTrigger: ({ node, bind }) => ( +
+ {node.icon ? {renderIcon(node.icon)} : null} + {node.label ?? node.title ?? String(node.id)} +
+ ), + Footer: () => null, + } +} + +/* ================================================================================================ + * Utils + * ============================================================================================== */ + +const HANDLER_KEYS = [ + 'onClick', + 'onKeyDown', + 'onKeyUp', + 'onMouseDown', + 'onMouseUp', + 'onMouseEnter', + 'onMouseLeave', + 'onPointerDown', + 'onPointerUp', + 'onFocus', + 'onBlur', +] as const + +/** Merge two sets of React props (className, handlers, refs are composed). */ +function mergeProps< + A extends Record, + B extends Record, +>(base: A | undefined, overrides?: B): A & B { + const a: any = base ?? {} + const b: any = overrides ?? {} + const merged: any = { ...a, ...b } + if (a.className || b.className) + merged.className = cn(a.className, b.className) + for (const key of HANDLER_KEYS) { + const aH = a[key] + const bH = b[key] + if (aH || bH) merged[key] = composeEventHandlers(aH, bH) + } + if (a.ref || b.ref) merged.ref = composeRefs(a.ref, b.ref) + return merged +} + +/** True when the ReactNode is an element that carries `propName`. */ +function isElementWithProp(node: React.ReactNode, propName: string) { + return React.isValidElement(node) && propName in (node.props as any) +} + +/** True when any descendant element carries `propName`. */ +function hasDescendantWithProp( + node: React.ReactNode, + propName: string, +): boolean { + if (!node) return false + if (Array.isArray(node)) + return node.some((n) => hasDescendantWithProp(n, propName)) + if (React.isValidElement(node)) { + if (propName in (node.props as any)) return true + const ch = (node.props as any)?.children + return hasDescendantWithProp(ch, propName) + } + return false +} + +/** Render an icon from heterogeneous inputs (node, element, component). */ +export function renderIcon(icon?: Iconish, className?: string) { + if (!icon) return null + if (typeof icon === 'string') return icon + if (React.isValidElement(icon)) { + const prev = (icon.props as any)?.className + return React.cloneElement(icon as any, { className: cn(prev, className) }) + } + const Comp = icon as React.ElementType + return +} + +/** Hit-test a point (x,y) against a DOMRect. */ +function isInBounds(x: number, y: number, rect: DOMRect) { + return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom +} + +/** Find the input & list elements within a surface container. */ +function findWidgetsWithinSurface(surface: HTMLElement | null) { + const input = + surface?.querySelector('[data-action-menu-input]') ?? null + const list = + surface?.querySelector('[data-action-menu-list]') ?? null + return { input, list } +} + +/* ================================================================================================ + * Keyboard helpers & options + * ============================================================================================== */ + +export type Direction = 'ltr' | 'rtl' + +export const SELECTION_KEYS = ['Enter'] as const +export const FIRST_KEYS = ['ArrowDown', 'PageUp', 'Home'] as const +export const LAST_KEYS = ['ArrowUp', 'PageDown', 'End'] as const + +export const SUB_OPEN_KEYS: Record = { + ltr: [...SELECTION_KEYS, 'ArrowRight'], + rtl: [...SELECTION_KEYS, 'ArrowLeft'], +} +export const SUB_CLOSE_KEYS: Record = { + ltr: ['ArrowLeft'], + rtl: ['ArrowRight'], +} + +export const isSelectionKey = (k: string) => + (SELECTION_KEYS as readonly string[]).includes(k) +export const isFirstKey = (k: string) => + (FIRST_KEYS as readonly string[]).includes(k) +export const isLastKey = (k: string) => + (LAST_KEYS as readonly string[]).includes(k) +export const isOpenKey = (dir: Direction, k: string) => + SUB_OPEN_KEYS[dir].includes(k) +export const isCloseKey = (dir: Direction, k: string) => + SUB_CLOSE_KEYS[dir].includes(k) +export const isVimNext = (e: React.KeyboardEvent) => + e.ctrlKey && (e.key === 'n' || e.key === 'j') +export const isVimPrev = (e: React.KeyboardEvent) => + e.ctrlKey && (e.key === 'p' || e.key === 'k') +export const isVimOpen = (e: React.KeyboardEvent) => e.ctrlKey && e.key === 'l' +export const isVimClose = (e: React.KeyboardEvent) => e.ctrlKey && e.key === 'h' + +export const getDir = (explicit?: Direction): Direction => { + if (explicit) return explicit + if (typeof document !== 'undefined') { + const d = document?.dir?.toLowerCase() + if (d === 'rtl' || d === 'ltr') return d as Direction + } + return 'ltr' +} + +/** Keyboard options context */ +type KeyboardOptions = { dir: Direction; vimBindings: boolean } +const KeyboardCtx = React.createContext({ + dir: 'ltr', + vimBindings: true, +}) +const useKeyboardOpts = () => React.useContext(KeyboardCtx) + +/* ================================================================================================ + * Custom events (open/select/internal notifications) + * ============================================================================================== */ + +const OPEN_SUB_EVENT = 'actionmenu-open-sub' as const +const SELECT_ITEM_EVENT = 'actionmenu-select-item' as const +const INPUT_VISIBILITY_CHANGE_EVENT = + 'actionmenu-input-visibility-change' as const + +function dispatch(node: HTMLElement | null | undefined, type: string) { + if (!node) return + node.dispatchEvent(new CustomEvent(type, { bubbles: true })) +} + +function openSubmenuForActive(activeId: string | null) { + const el = activeId ? document.getElementById(activeId) : null + if (el && (el as HTMLElement).dataset.subtrigger === 'true') { + dispatch(el, OPEN_SUB_EVENT) + } +} + +/* ================================================================================================ + * Root / Shell context & Focus context + * ============================================================================================== */ + +export type ResponsiveMode = 'auto' | 'drawer' | 'dropdown' +export type ResponsiveConfig = { mode: ResponsiveMode; query: string } + +type ActionMenuRootContextValue = { + open: boolean + onOpenChange: (open: boolean) => void + onOpenToggle: () => void + modal: boolean + anchorRef: React.RefObject + debug: boolean + responsive: ResponsiveConfig + shellClassNames?: Partial + shellSlotProps?: Partial +} +const RootCtx = React.createContext(null) +const useRootCtx = () => { + const ctx = React.useContext(RootCtx) + if (!ctx) throw new Error('useActionMenu must be used within an ActionMenu') + return ctx +} + +/** Provides a stable id string for the current surface. */ +const SurfaceIdCtx = React.createContext(null) +const useSurfaceId = () => React.useContext(SurfaceIdCtx) + +type MenuDisplayMode = Omit +const DisplayModeCtx = React.createContext('dropdown') +const useDisplayMode = () => React.useContext(DisplayModeCtx) + +/** Submenu context (open state/refs/ids). */ +type SubContextValue = { + open: boolean + onOpenChange: (open: boolean) => void + onOpenToggle: () => void + triggerRef: React.RefObject + contentRef: React.RefObject + parentSurfaceId: string + triggerItemId: string | null + setTriggerItemId: (id: string | null) => void + parentSetActiveId: (id: string | null, cause?: ActivationCause) => void + childSurfaceId: string + pendingOpenModalityRef: React.RefObject<'keyboard' | 'pointer' | null> + intentZoneActiveRef: React.RefObject +} +const SubCtx = React.createContext(null) +const useSubCtx = () => React.useContext(SubCtx) + +/** Focus owner context (which surface owns real DOM focus). */ +type FocusOwnerCtxValue = { + ownerId: string | null + setOwnerId: (id: string | null) => void +} +const FocusOwnerCtx = React.createContext(null) +const useFocusOwner = () => { + const ctx = React.useContext(FocusOwnerCtx) + if (!ctx) throw new Error('FocusOwnerCtx missing') + return ctx +} + +/* ================================================================================================ + * Surface store (per Content/Surface) — roving focus & registration + * ============================================================================================== */ + +type SurfaceState = { + activeId: string | null + hasInput: boolean + listId: string | null +} + +type RowRecord = { + ref: React.RefObject + disabled?: boolean + kind: 'item' | 'submenu' + openSub?: () => void + closeSub?: () => void +} + +type ActivationCause = 'keyboard' | 'pointer' | 'programmatic' + +type SurfaceStore = { + subscribe(cb: () => void): () => void + snapshot(): SurfaceState + set(k: K, v: SurfaceState[K]): void + + registerRow(id: string, rec: RowRecord): void + unregisterRow(id: string): void + getOrder(): string[] + resetOrder(ids: string[]): void + + setActiveId(id: string | null, cause?: ActivationCause): void + setActiveByIndex(idx: number, cause?: ActivationCause): void + first(cause?: ActivationCause): void + last(cause?: ActivationCause): void + next(cause?: ActivationCause): void + prev(cause?: ActivationCause): void + + readonly rows: Map + readonly inputRef: React.RefObject + readonly listRef: React.RefObject +} + +function createSurfaceStore(): SurfaceStore { + const state: SurfaceState = { activeId: null, hasInput: true, listId: null } + const listeners = new Set<() => void>() + const rows = new Map() + const order: string[] = [] + const listRef = React.createRef() + const inputRef = React.createRef() + + const emit = () => + listeners.forEach((l) => { + l() + }) + const snapshot = () => state + const set = (k: K, v: SurfaceState[K]) => { + if (Object.is((state as any)[k], v)) return + ;(state as any)[k] = v + emit() + } + + const getOrder = () => order.slice() + const resetOrder = (ids: string[]) => { + order.splice(0) + order.push(...ids) + emit() + } + + const ensureActiveExists = () => { + if (state.activeId && rows.has(state.activeId)) return + state.activeId = order[0] ?? null + } + + const setActiveId = ( + id: string | null, + cause: ActivationCause = 'keyboard', + ) => { + const prev = state.activeId + if (Object.is(prev, id)) return + state.activeId = id + // Close any open submenu that is not the active trigger + for (const [rid, rec] of rows) { + if (rec.kind === 'submenu' && rec.closeSub && rid !== id) { + try { + rec.closeSub() + } catch {} + } + } + emit() + // Scroll active row into view when keyboard navigating + if (cause !== 'keyboard') return + const el = id ? rows.get(id)?.ref.current : null + const listEl = listRef.current + if (el && listEl) { + try { + const inList = listEl.contains(el) + if (inList) el.scrollIntoView({ block: 'nearest' }) + } catch {} + } + } + + const setActiveByIndex = ( + idx: number, + cause: ActivationCause = 'keyboard', + ) => { + if (!order.length) return setActiveId(null, cause) + const clamped = idx < 0 ? 0 : idx >= order.length ? order.length - 1 : idx + setActiveId(order[clamped]!, cause) + } + const first = (cause: ActivationCause = 'keyboard') => + setActiveByIndex(0, cause) + const last = (cause: ActivationCause = 'keyboard') => + setActiveByIndex(order.length - 1, cause) + const next = (cause: ActivationCause = 'keyboard') => { + if (!order.length) return setActiveId(null, cause) + const i = state.activeId ? order.indexOf(state.activeId) : -1 + const n = i === -1 ? 0 : (i + 1) % order.length + setActiveId(order[n]!, cause) + } + const prev = (cause: ActivationCause = 'keyboard') => { + if (!order.length) return setActiveId(null, cause) + const i = state.activeId ? order.indexOf(state.activeId) : 0 + const p = i <= 0 ? order.length - 1 : i - 1 + setActiveId(order[p]!, cause) + } + + return { + subscribe(cb) { + listeners.add(cb) + return () => listeners.delete(cb) + }, + snapshot, + set, + registerRow(id, rec) { + if (!rows.has(id)) order.push(id) + rows.set(id, rec) + ensureActiveExists() + emit() + }, + unregisterRow(id) { + rows.delete(id) + const idx = order.indexOf(id) + if (idx >= 0) order.splice(idx, 1) + if (state.activeId === id) { + ensureActiveExists() + emit() + } else { + emit() + } + }, + getOrder, + resetOrder, + setActiveId, + setActiveByIndex, + first, + last, + next, + prev, + rows, + inputRef, + listRef, + } +} + +function useSurfaceSel(store: SurfaceStore, sel: (s: SurfaceState) => T): T { + const get = React.useCallback(() => sel(store.snapshot()), [store, sel]) + return React.useSyncExternalStore(store.subscribe, get, get) +} + +/* ================================================================================================ + * Hover policy / Aim guard (to reduce accidental submenu closures) + * ============================================================================================== */ + +type HoverPolicy = { + suppressHoverOpen: boolean + clearSuppression: () => void + aimGuardActive: boolean + guardedTriggerId: string | null + activateAimGuard: (triggerId: string, timeoutMs?: number) => void + clearAimGuard: () => void + aimGuardActiveRef: React.RefObject + guardedTriggerIdRef: React.RefObject + isGuardBlocking: (rowId: string) => boolean +} + +const HoverPolicyCtx = React.createContext({ + suppressHoverOpen: false, + clearSuppression: () => {}, + aimGuardActive: false, + guardedTriggerId: null, + activateAimGuard: () => {}, + clearAimGuard: () => {}, + aimGuardActiveRef: React.createRef(), + guardedTriggerIdRef: React.createRef(), + isGuardBlocking: () => false, +}) +const useHoverPolicy = () => React.useContext(HoverPolicyCtx) + +/** Keep the last N mouse positions without causing re-renders. */ +function useMouseTrail(n = 2) { + const trailRef = React.useRef<[number, number][]>([]) + React.useEffect(() => { + const onMove = (e: PointerEvent) => { + const a = trailRef.current + a.push([e.clientX, e.clientY]) + if (a.length > n) a.shift() + } + window.addEventListener('pointermove', onMove, { passive: true }) + return () => window.removeEventListener('pointermove', onMove) + }, [n]) + return trailRef +} + +type AnchorSide = 'left' | 'right' +function resolveAnchorSide( + rect: DOMRect, + tRect: DOMRect | null, + mx: number, +): AnchorSide { + if (tRect) { + const tx = (tRect.left + tRect.right) / 2 + const dL = Math.abs(tx - rect.left) + const dR = Math.abs(tx - rect.right) + return dL <= dR ? 'left' : 'right' + } + return mx < rect.left ? 'left' : 'right' +} + +function getSmoothedHeading( + trail: [number, number][], + exitX: number, + exitY: number, + anchor: AnchorSide, + tRect: DOMRect | null, + rect: DOMRect, +): { dx: number; dy: number } { + let dx = 0 + let dy = 0 + const n = Math.min(Math.max(trail.length - 1, 0), 4) + for (let i = trail.length - n - 1; i < trail.length - 1; i++) { + if (i < 0) continue + const [x1, y1] = trail[i]! + const [x2, y2] = trail[i + 1]! + dx += x2 - x1 + dy += y2 - y1 + } + const mag = Math.hypot(dx, dy) + if (mag < 0.5) { + const tx = tRect ? (tRect.left + tRect.right) / 2 : exitX + const ty = tRect ? (tRect.top + tRect.bottom) / 2 : exitY + const edgeX = anchor === 'right' ? rect.left : rect.right + const edgeCy = (rect.top + rect.bottom) / 2 + dx = edgeX - tx + dy = edgeCy - ty + } + return { dx, dy } +} + +function willHitSubmenu( + exitX: number, + exitY: number, + heading: { dx: number; dy: number }, + rect: DOMRect, + anchor: AnchorSide, + triggerRect: DOMRect | null, +): boolean { + const { dx, dy } = heading + if (Math.abs(dx) < 0.01) return false + if (anchor === 'left' && dx <= 0) return false + if (anchor === 'right' && dx >= 0) return false + const edgeX = anchor === 'left' ? rect.left : rect.right + const t = (edgeX - exitX) / dx + if (t <= 0) return false + const yAtEdge = exitY + t * dy + const baseBand = triggerRect ? triggerRect.height * 0.75 : 28 + const extra = Math.max(12, Math.min(36, baseBand)) + const top = rect.top - extra * 0.25 + const bottom = rect.bottom + extra * 0.25 + return yAtEdge >= top && yAtEdge <= bottom +} + +/** Visual-only debug polygon showing the aim-guard band. */ +function IntentZone({ + parentRef, + triggerRef, + visible = false, +}: { + parentRef: React.RefObject + triggerRef: React.RefObject + visible?: boolean +}) { + const [mx, my] = useMousePosition() + const isCoarse = React.useMemo( + () => + typeof window !== 'undefined' + ? matchMedia('(pointer: coarse)').matches + : false, + [], + ) + const content = parentRef.current + const rect = content?.getBoundingClientRect() + if (!rect || isCoarse) return null + const tRect = triggerRef?.current?.getBoundingClientRect() ?? null + const x = rect.left + const y = rect.top + const w = rect.width + const h = rect.height + if (!w || !h) return null + const anchor = resolveAnchorSide(rect, tRect, mx) + if (anchor === 'left' && mx >= x) return null + if (anchor === 'right' && mx <= x + w) return null + const INSET = 2 + const pct = Math.max(0, Math.min(100, ((my - y) / h) * 100)) + const width = + anchor === 'left' + ? Math.max(x - mx, 10) + INSET + : Math.max(mx - (x + w), 10) + INSET + const left = anchor === 'left' ? x - width : x + w + const clip = + anchor === 'left' + ? `polygon(100% 0%, 0% ${pct}%, 100% 100%)` + : `polygon(0% 0%, 0% 100%, 100% ${pct}%)` + const Polygon = ( +
+ ) + return {Polygon} +} + +function useMousePosition(): [number, number] { + const [pos, setPos] = React.useState<[number, number]>([0, 0]) + React.useEffect(() => { + const onMove = (e: PointerEvent) => setPos([e.clientX, e.clientY]) + window.addEventListener('pointermove', onMove, { passive: true }) + return () => window.removeEventListener('pointermove', onMove) + }, []) + return pos +} + +/* ================================================================================================ + * Keyboard handling shared by Input/List + * ============================================================================================== */ + +function useNavKeydown(source: 'input' | 'list') { + const store = useSurface() + const root = useRootCtx() + const sub = useSubCtx() + const surfaceId = useSurfaceId() || 'root' + const { ownerId, setOwnerId } = useFocusOwner() + const { dir, vimBindings } = useKeyboardOpts() + + return React.useCallback( + (e: React.KeyboardEvent) => { + if (ownerId !== surfaceId) return + const k = e.key + const stop = () => { + e.preventDefault() + e.stopPropagation() + } + if (vimBindings) { + if (isVimNext(e)) { + stop() + store.next() + return + } + if (isVimPrev(e)) { + stop() + store.prev() + return + } + if (isVimOpen(e)) { + stop() + const activeId = store.snapshot().activeId + if (isSelectionKey(k)) { + const el = activeId ? document.getElementById(activeId) : null + if (el && el.dataset.subtrigger === 'true') { + openSubmenuForActive(activeId) + } else { + dispatch(el, SELECT_ITEM_EVENT) + } + } else { + openSubmenuForActive(activeId) + } + return + } + if (isVimClose(e)) { + if (sub) { + stop() + setOwnerId(sub.parentSurfaceId) + sub.onOpenChange(false) + sub.parentSetActiveId(sub.triggerItemId) + const parentEl = document.querySelector( + `[data-surface-id="${sub.parentSurfaceId}"]`, + ) + requestAnimationFrame(() => { + const { input, list } = findWidgetsWithinSurface(parentEl) + ;(input ?? list)?.focus() + }) + return + } + } + } + if (k === 'Tab') { + stop() + return + } + if (k === 'ArrowDown') { + stop() + store.next() + return + } + if (k === 'ArrowUp') { + stop() + store.prev() + return + } + if (k === 'Home' || k === 'PageUp') { + stop() + store.first() + return + } + if (k === 'End' || k === 'PageDown') { + stop() + store.last() + return + } + + if (isOpenKey(dir, k)) { + stop() + const activeId = store.snapshot().activeId + if (isSelectionKey(k)) { + const el = activeId ? document.getElementById(activeId) : null + if (el && el.dataset.subtrigger === 'true') { + openSubmenuForActive(activeId) + } else { + dispatch(el, SELECT_ITEM_EVENT) + } + } else { + openSubmenuForActive(activeId) + } + return + } + + if (isCloseKey(dir, k)) { + if (sub) { + stop() + setOwnerId(sub.parentSurfaceId) + sub.onOpenChange(false) + sub.parentSetActiveId(sub.triggerItemId) + const parentEl = document.querySelector( + `[data-surface-id="${sub.parentSurfaceId}"]`, + ) + requestAnimationFrame(() => { + const { input, list } = findWidgetsWithinSurface(parentEl) + ;(input ?? list)?.focus() + }) + return + } + } + + if (k === 'Enter') { + stop() + const activeId = store.snapshot().activeId + const el = activeId ? document.getElementById(activeId) : null + if (el && el.dataset.subtrigger === 'true') { + openSubmenuForActive(activeId) + } else { + dispatch(el, SELECT_ITEM_EVENT) + } + return + } + + if (k === 'Escape') { + stop() + if (sub) { + sub.onOpenChange(false) + return + } + root.onOpenChange(false) + return + } + }, + [store, root, sub, dir, vimBindings, ownerId, setOwnerId, surfaceId], + ) +} + +/* ================================================================================================ + * Positioner (Dropdown-only for root; always dropdown for submenus) + * ============================================================================================== */ + +export interface ActionMenuPositionerProps { + children: React.ReactElement + side?: 'top' | 'right' | 'bottom' | 'left' + align?: 'start' | 'center' | 'end' + sideOffset?: number + alignOffset?: number + avoidCollisions?: boolean + collisionPadding?: + | number + | Partial> + alignToFirstItem?: false | 'on-open' | 'always' +} + +export const Positioner: React.FC = ({ + children, + side, + align = 'start', + sideOffset = 8, + alignOffset = 0, + avoidCollisions = true, + collisionPadding = 8, + alignToFirstItem = false, +}) => { + const root = useRootCtx() + const sub = useSubCtx() + + const isSub = !!sub + const present = isSub ? sub!.open : root.open + const defaultSide = isSub ? 'right' : 'bottom' + const resolvedSide = side ?? defaultSide + const mode = useDisplayMode() + + const [firstRowAlignOffset, setFirstRowAlignOffset] = React.useState(0) + + const findContentEl = React.useCallback((): HTMLElement | null => { + if (!sub) return null + const byRef = sub.contentRef.current + if (byRef) return byRef + try { + return document.querySelector( + `[data-surface-id="${sub.childSurfaceId}"]`, + ) + } catch { + return null + } + }, [sub]) + + const measure = React.useCallback(() => { + if (!isSub || !present || !alignToFirstItem) { + setFirstRowAlignOffset(0) + return + } + if (!(resolvedSide === 'right' || resolvedSide === 'left')) { + setFirstRowAlignOffset(0) + return + } + const el = findContentEl() + if (!el) return + const inputEl = el.querySelector('[data-action-menu-input]') + const hasVisibleInput = !!inputEl && inputEl.offsetParent !== null + const firstRow = el.querySelector( + '[data-action-menu-list] [data-action-menu-item-id]', + ) + if (!hasVisibleInput || !firstRow) { + setFirstRowAlignOffset(0) + return + } + const cr = el.getBoundingClientRect() + const fr = firstRow.getBoundingClientRect() + setFirstRowAlignOffset(-Math.round(fr.top - cr.top)) + }, [isSub, present, alignToFirstItem, resolvedSide, sub]) + + React.useEffect(() => { + if (!isSub || !present || !alignToFirstItem) return + const handle = (e: Event) => { + const customEvent = e as CustomEvent<{ + surfaceId?: string + hideSearchUntilActive?: boolean + inputActive?: boolean + }> + const target = e.target as HTMLElement | null + const ok = + customEvent.detail?.surfaceId === sub!.childSurfaceId || + target?.closest?.(`[data-surface-id="${sub!.childSurfaceId}"]`) !== null + if (!ok) return + if ( + alignToFirstItem === 'on-open' && + customEvent.detail?.hideSearchUntilActive && + customEvent.detail?.inputActive + ) { + return + } + measure() + } + document.addEventListener(INPUT_VISIBILITY_CHANGE_EVENT, handle, true) + return () => + document.removeEventListener(INPUT_VISIBILITY_CHANGE_EVENT, handle, true) + }, [isSub, sub, present, alignToFirstItem, measure]) + + const effectiveAlignOffset = + isSub && alignToFirstItem ? firstRowAlignOffset : alignOffset + + // IMPORTANT: For the root surface in drawer mode, Positioner is a no-op pass-through. + // For submenus, we always position with Popper (even in drawer mode). + const shouldBypassForRoot = mode === 'drawer' && !isSub + + if (shouldBypassForRoot) { + return <>{children} + } + + const content = isSub ? ( + + + + {children} + + + + ) : ( + + + {children} + + + ) + + return ( + <> + {content} + {isSub && present ? ( + } + triggerRef={sub!.triggerRef as React.RefObject} + visible={root.debug} + /> + ) : null} + + ) +} + +/* ================================================================================================ + * Surface (formerly ContentBase) — generic, shell-agnostic + * ============================================================================================== */ + +export interface ActionMenuSurfaceProps + extends Omit { + menu: MenuDef | Menu + slots?: Partial> + surfaceSlotProps?: Partial + vimBindings?: boolean + dir?: Direction + defaults?: Partial> + surfaceClassNames?: Partial + value?: string + defaultValue?: string + onValueChange?: (value: string) => void + onOpenAutoFocus?: boolean + onCloseAutoClear?: boolean | number + /** @internal Forced surface id; used by submenus. */ + surfaceIdProp?: string + /** @internal Suppress hover-open until first pointer move; used by submenus opened via keyboard. */ + suppressHoverOpenOnMount?: boolean +} + +const SurfaceCtx = React.createContext(null) +const useSurface = () => { + const ctx = React.useContext(SurfaceCtx) + if (!ctx) throw new Error('SurfaceCtx missing') + return ctx +} + +const SurfaceBase = React.forwardRef(function SurfaceBaseInner( + { + menu: menuProp, + slots: slotOverrides, + surfaceSlotProps: slotPropsOverrides, + vimBindings = true, + dir: dirProp, + surfaceIdProp, + suppressHoverOpenOnMount, + defaults: defaultsOverrides, + surfaceClassNames, + value: valueProp, + defaultValue, + onValueChange, + onOpenAutoFocus = true, // reserved + onCloseAutoClear = true, + ...props + }: ActionMenuSurfaceProps, + ref: React.ForwardedRef, +) { + const root = useRootCtx() + const sub = useSubCtx() + const surfaceId = React.useMemo( + () => surfaceIdProp ?? sub?.childSurfaceId ?? 'root', + [surfaceIdProp, sub], + ) + const menu = React.useMemo>(() => { + if ((menuProp as any)?.surfaceId) return menuProp as Menu + // depth: root = 0, submenu = parent.depth + 1 (if you have access to parent via sub) + const depth = sub ? 1 : 0 + return instantiateMenuFromDef(menuProp as MenuDef, surfaceId, depth) + }, [menuProp, surfaceId, sub]) + const mode = useDisplayMode() + const { ownerId, setOwnerId } = useFocusOwner() + const isOwner = ownerId === surfaceId + const surfaceRef = React.useRef(null) + const composedRef = composeRefs( + ref, + surfaceRef, + sub ? (sub.contentRef as any) : undefined, + ) + const dir = getDir(dirProp) + + const [value, setValue] = useControllableState({ + prop: valueProp, + defaultProp: defaultValue ?? '', + onChange: onValueChange, + }) + + // Clear input on menu close + const clearTimeoutRef = React.useRef(null) + React.useEffect(() => { + if (clearTimeoutRef.current) { + window.clearTimeout(clearTimeoutRef.current) + clearTimeoutRef.current = null + } + if (!root.open && onCloseAutoClear) { + if (typeof onCloseAutoClear === 'number') { + clearTimeoutRef.current = window.setTimeout(() => { + setValue('') + clearTimeoutRef.current = null + }, onCloseAutoClear) + } else { + setValue('') + } + } + }, [root.open, onCloseAutoClear]) + + React.useEffect(() => { + return () => { + if (clearTimeoutRef.current) { + window.clearTimeout(clearTimeoutRef.current) + } + } + }, []) + + const slots = React.useMemo>>( + () => ({ + ...defaultSlots(), + ...(slotOverrides as any), + ...(menu.ui?.slots as any), + }), + [slotOverrides, menu.ui?.slots], + ) + + const slotProps = React.useMemo>( + () => ({ ...(slotPropsOverrides ?? {}), ...(menu.ui?.slotProps ?? {}) }), + [slotPropsOverrides, menu.ui?.slotProps], + ) + + const defaults = React.useMemo>>( + () => ({ ...defaultsOverrides, ...(menu.defaults ?? {}) }), + [defaultsOverrides, menu.defaults], + ) + + const mergedClassNames = React.useMemo( + () => ({ ...(surfaceClassNames ?? {}), ...(menu.ui?.classNames ?? {}) }), + [surfaceClassNames, menu.ui?.classNames], + ) + + const isSubmenu = !!sub + const [inputActive, setInputActive] = React.useState( + !menu.hideSearchUntilActive, + ) + + // Notify (e.g., Positioner) when input visibility changes + React.useLayoutEffect(() => { + const target: EventTarget = + surfaceRef.current ?? + (typeof document !== 'undefined' ? document : ({} as any)) + target.dispatchEvent( + new CustomEvent(INPUT_VISIBILITY_CHANGE_EVENT, { + bubbles: true, + composed: true, + detail: { + surfaceId, + hideSearchUntilActive: menu.hideSearchUntilActive, + inputActive, + }, + }), + ) + }, [inputActive, menu.hideSearchUntilActive]) + + // Create per-surface store once + const storeRef = React.useRef(null) + if (!storeRef.current) storeRef.current = createSurfaceStore() + const store = storeRef.current + + React.useEffect(() => { + store.set('hasInput', inputActive) + }, [inputActive, store]) + + // On open, claim focus ownership for the first surface and focus input/list. + React.useEffect(() => { + if (!root.open) { + setOwnerId(null) + return + } + if (root.open && ownerId === null) { + setOwnerId(surfaceId) + ;(store.inputRef.current ?? store.listRef.current)?.focus() + } + }, [root.open, ownerId, surfaceId, setOwnerId, store.inputRef, store.listRef]) + + // Keep focus on input/list after re-render when we own focus + React.useEffect(() => { + if (!root.open || !isOwner) return + const id = requestAnimationFrame(() => { + ;(store.inputRef.current ?? store.listRef.current)?.focus() + }) + return () => cancelAnimationFrame(id) + }, [root.open, isOwner, store.inputRef, store.listRef]) + + const [suppressHoverOpen, setSuppressHoverOpen] = React.useState( + !!suppressHoverOpenOnMount, + ) + const clearSuppression = React.useCallback(() => { + if (suppressHoverOpen) setSuppressHoverOpen(false) + }, [suppressHoverOpen]) + + const [aimGuardActive, setAimGuardActive] = React.useState(false) + const [guardedTriggerId, setGuardedTriggerId] = React.useState( + null, + ) + const aimGuardActiveRef = React.useRef(false) + const guardedTriggerIdRef = React.useRef(null) + React.useEffect(() => { + aimGuardActiveRef.current = aimGuardActive + }, [aimGuardActive]) + React.useEffect(() => { + guardedTriggerIdRef.current = guardedTriggerId + }, [guardedTriggerId]) + const guardTimerRef = React.useRef(null) + const clearAimGuard = React.useCallback(() => { + if (guardTimerRef.current) { + window.clearTimeout(guardTimerRef.current) + guardTimerRef.current = null + } + aimGuardActiveRef.current = false + guardedTriggerIdRef.current = null + setAimGuardActive(false) + setGuardedTriggerId(null) + }, []) + const activateAimGuard = React.useCallback( + (triggerId: string, timeoutMs = 450) => { + aimGuardActiveRef.current = true + guardedTriggerIdRef.current = triggerId + setGuardedTriggerId(triggerId) + setAimGuardActive(true) + if (guardTimerRef.current) window.clearTimeout(guardTimerRef.current) + guardTimerRef.current = window.setTimeout(() => { + aimGuardActiveRef.current = false + guardedTriggerIdRef.current = null + setAimGuardActive(false) + setGuardedTriggerId(null) + guardTimerRef.current = null + }, timeoutMs) as any + }, + [], + ) + const isGuardBlocking = React.useCallback( + (rowId: string) => + aimGuardActiveRef.current && guardedTriggerIdRef.current !== rowId, + [], + ) + + const baseContentProps = React.useMemo( + () => + ({ + ref: composedRef, + role: 'menu', + tabIndex: -1, + 'data-slot': 'action-menu-content', + 'data-root-menu': !isSubmenu ? 'true' : undefined, + 'data-state': root.open ? 'open' : 'closed', + 'data-action-menu-surface': true as const, + 'data-surface-id': surfaceId, + 'data-mode': mode, + className: mergedClassNames?.content, + onMouseMove: (e: React.MouseEvent) => { + clearSuppression() + const rect = ( + surfaceRef.current as HTMLElement | null + )?.getBoundingClientRect() + if (!rect || !isInBounds(e.clientX, e.clientY, rect)) return + setOwnerId(surfaceId) + }, + ...props, + }) as const, + [ + composedRef, + root.open, + clearSuppression, + surfaceId, + setOwnerId, + props, + mode, + mergedClassNames?.content, + ], + ) + + const contentBind: ContentBindAPI = { + getContentProps: (overrides) => + mergeProps( + baseContentProps as any, + mergeProps(slotProps?.content as any, overrides as any), + ), + } + + const headerEl = slots.Header ? ( +
+ {slots.Header({ menu })} +
+ ) : null + + const inputEl = inputActive ? ( + + store={store} + value={value} + onChange={setValue} + slot={slots.Input} + slotProps={slotProps} + classNames={mergedClassNames} + inputPlaceholder={menu.inputPlaceholder} + /> + ) : null + + const listEl = ( + + store={store} + menu={menu} + slots={slots} + slotProps={slotProps} + defaults={defaults} + query={value} + classNames={mergedClassNames} + inputActive={inputActive} + onTypeStart={(seed) => { + if (!inputActive && ownerId === surfaceId) { + setInputActive(true) + setValue(seed) + requestAnimationFrame(() => { + store.inputRef.current?.focus() + }) + } + }} + /> + ) + + const footerEl = slots.Footer ? ( +
+ {slots.Footer({ menu })} +
+ ) : null + + const childrenNoProvider = ( + <> + {headerEl} + {inputEl} + {listEl} + {footerEl} + + ) + + const body = slots.Content({ + menu, + children: childrenNoProvider, + bind: contentBind, + }) + + const wrapped = !isElementWithProp(body, 'data-action-menu-surface') ? ( + + {body} + + ) : ( + {body} + ) + + return ( + + + + {wrapped} + + + + ) +}) as ( + p: ActionMenuSurfaceProps & { ref?: React.Ref }, +) => ReturnType + +export const Surface = React.forwardRef< + HTMLDivElement, + ActionMenuSurfaceProps +>((p, ref) => ) +Surface.displayName = 'ActionMenu.Surface' + +/* ================================================================================================ + * Submenu plumbing (provider and rows) + * ============================================================================================== */ + +function Sub({ children }: { children: React.ReactNode }) { + const [open, setOpen] = React.useState(false) + const triggerRef = React.useRef( + null, + ) + const contentRef = React.useRef(null) + const parentStore = useSurface() + const parentSurfaceId = useSurfaceId() || 'root' + const [triggerItemId, setTriggerItemId] = React.useState(null) + const childSurfaceId = React.useId() + const pendingOpenModalityRef = React.useRef<'keyboard' | 'pointer' | null>( + null, + ) + const intentZoneActiveRef = React.useRef(false) + const { setOwnerId } = useFocusOwner() + const mode = useDisplayMode() + + const value: SubContextValue = React.useMemo( + () => ({ + open, + onOpenChange: setOpen, + onOpenToggle: () => setOpen((v) => !v), + triggerRef, + contentRef, + parentSurfaceId, + triggerItemId, + setTriggerItemId, + parentSetActiveId: parentStore.setActiveId, + childSurfaceId, + pendingOpenModalityRef, + intentZoneActiveRef, + }), + [ + open, + parentSurfaceId, + triggerItemId, + parentStore.setActiveId, + childSurfaceId, + ], + ) + + if (mode === 'drawer') { + // Use Vaul nested drawer for submenu layers + return ( + + { + setOpen(o) + if (o) { + // Claim focus for the child surface when the nested drawer opens + setOwnerId(childSurfaceId) + // focus input/list shortly after mount + requestAnimationFrame(() => { + const content = contentRef.current + const { input, list } = findWidgetsWithinSurface(content!) + ;(input ?? list)?.focus() + }) + } else { + // returning focus/selection to parent surface + setOwnerId(parentSurfaceId) + parentStore.setActiveId(triggerItemId) + requestAnimationFrame(() => { + const parentEl = document.querySelector( + `[data-surface-id="${parentSurfaceId}"]`, + ) + const { input, list } = findWidgetsWithinSurface(parentEl) + ;(input ?? list)?.focus() + }) + } + }} + > + {children} + + + ) + } + + return ( + + {children} + + ) +} + +function SubTriggerRow({ + node, + slot, + classNames, + search, +}: { + node: SubmenuNode + slot: NonNullable['SubmenuTrigger']> + classNames?: Partial + search?: SearchContext +}) { + const store = useSurface() + const sub = useSubCtx()! + const { setOwnerId } = useFocusOwner() + const { + guardedTriggerIdRef, + aimGuardActiveRef, + activateAimGuard, + clearAimGuard, + } = useHoverPolicy() + const mouseTrailRef = useMouseTrail(4) + const ref = React.useRef(null) + const surfaceId = useSurfaceId() + const { ownerId } = useFocusOwner() + const mode = useDisplayMode() + + const rowId = makeRowId(node.id, search, surfaceId) + + React.useEffect(() => { + store.registerRow(rowId, { + ref: ref as any, + disabled: false, + kind: 'submenu', + openSub: () => sub.onOpenChange(true), + closeSub: () => sub.onOpenChange(false), + }) + return () => store.unregisterRow(rowId) + }, [store, rowId]) + + React.useEffect(() => { + const nodeEl = ref.current + if (!nodeEl) return + const onOpen = () => { + sub.pendingOpenModalityRef.current = 'keyboard' + sub.onOpenChange(true) + setOwnerId(sub.childSurfaceId) + const tryFocus = (attempt = 0) => { + const content = sub.contentRef.current as HTMLElement | null + if (content) { + const { input, list } = findWidgetsWithinSurface(content) + ;(input ?? list)?.focus() + return + } + if (attempt < 5) requestAnimationFrame(() => tryFocus(attempt + 1)) + } + requestAnimationFrame(() => tryFocus()) + } + nodeEl.addEventListener(OPEN_SUB_EVENT, onOpen as EventListener) + return () => + nodeEl.removeEventListener(OPEN_SUB_EVENT, onOpen as EventListener) + }, [sub, setOwnerId]) + + React.useEffect(() => { + if (sub.triggerItemId !== rowId) sub.setTriggerItemId(rowId) + return () => { + if (sub.triggerItemId === rowId) sub.setTriggerItemId(null) + } + }, [rowId]) + + const activeId = useSurfaceSel(store, (s) => s.activeId) + const focused = activeId === rowId + const menuFocused = sub.childSurfaceId === ownerId + + const baseRowProps = React.useMemo(() => { + const common = { + id: rowId, + ref: composeRefs(ref as any, sub.triggerRef as any), + role: 'option' as const, + tabIndex: -1, + 'data-action-menu-item-id': rowId, + 'data-focused': focused, + 'data-menu-state': sub.open ? 'open' : 'closed', + 'data-menu-focused': menuFocused, + 'aria-selected': focused, + 'aria-disabled': false, + 'data-subtrigger': 'true', + 'data-mode': mode, + className: classNames?.subtrigger, + } as const + + if (mode === 'drawer') { + // Drawer mode: click (or Enter) opens nested drawer via Drawer.Trigger + return { + ...common, + onPointerDown: undefined, // let Drawer.Trigger handle it + onPointerEnter: undefined, + onPointerMove: undefined, + onPointerLeave: undefined, + onKeyDown: (e: React.KeyboardEvent) => { + // Keep keyboard navigation; Enter will bubble to Drawer.Trigger + if ( + e.key === 'ArrowUp' || + e.key === 'ArrowDown' || + e.key === 'Home' || + e.key === 'End' + ) { + // let list/input handlers deal with it via useNavKeydown + } + }, + } as const + } + + // Dropdown (Popper) mode: keep your original hover + aim-guard behavior + return { + ...common, + onPointerDown: (e: React.PointerEvent) => { + if (e.button === 0 && e.ctrlKey === false) { + e.preventDefault() + sub.pendingOpenModalityRef.current = 'pointer' + sub.onOpenToggle() + } + }, + onPointerEnter: () => { + if (aimGuardActiveRef.current && guardedTriggerIdRef.current !== rowId) + return + if (!focused) store.setActiveId(rowId, 'pointer') + clearAimGuard() + if (!sub.open) sub.onOpenChange(true) + }, + onPointerMove: () => { + if (aimGuardActiveRef.current && guardedTriggerIdRef.current !== rowId) + return + if (!focused) store.setActiveId(rowId, 'pointer') + if (!sub.open) sub.onOpenChange(true) + }, + onPointerLeave: (e: React.PointerEvent) => { + if (aimGuardActiveRef.current && guardedTriggerIdRef.current !== rowId) + return + const contentRect = sub.contentRef.current?.getBoundingClientRect() + if (!contentRect) { + clearAimGuard() + return + } + const tRect = + ( + sub.triggerRef.current as HTMLElement | null + )?.getBoundingClientRect() ?? null + const anchor = resolveAnchorSide(contentRect, tRect, e.clientX) + const heading = getSmoothedHeading( + mouseTrailRef.current, + e.clientX, + e.clientY, + anchor, + tRect, + contentRect, + ) + const hit = willHitSubmenu( + e.clientX, + e.clientY, + heading, + contentRect, + anchor, + tRect, + ) + if (hit) { + activateAimGuard(rowId, 600) + store.setActiveId(rowId, 'pointer') + sub.onOpenChange(true) + } else { + clearAimGuard() + } + }, + } as const + }, [ + mode, + rowId, + focused, + menuFocused, + classNames?.subtrigger, + store, + sub, + activateAimGuard, + clearAimGuard, + aimGuardActiveRef, + guardedTriggerIdRef, + ]) + + const bind: RowBindAPI = { + focused, + disabled: false, + getRowProps: (overrides) => + mergeProps(baseRowProps as any, overrides as any), + } + + const visual = slot({ node, bind, search }) + const content = hasDescendantWithProp(visual, 'data-action-menu-item-id') ? ( + visual + ) : ( +
{visual ?? node.label ?? node.title}
+ ) + + return mode === 'drawer' ? ( + {content as any} + ) : ( + {content as any} + ) +} + +function SubmenuContent({ + menu, + slots, + defaults, + classNames, +}: { + menu: SubmenuNode + slots: Required> + defaults?: Partial> + classNames?: Partial +}) { + const sub = useSubCtx()! + const mode = useDisplayMode() + const root = useRootCtx() + + const suppressHover = sub.pendingOpenModalityRef.current === 'keyboard' + React.useEffect(() => { + sub.pendingOpenModalityRef.current = null + }, [sub]) + + const inner = ( + + menu={menu.child as Menu} + slots={slots as any} + defaults={defaults} + surfaceClassNames={classNames} + surfaceIdProp={sub.childSurfaceId} + suppressHoverOpenOnMount={suppressHover} + /> + ) + + if (mode === 'drawer') { + return ( + + + { + event.preventDefault() + }} + onCloseAutoFocus={(event) => { + event.preventDefault() + }} + > + + {menu.title ?? 'Action Menu'} + +
+ {inner as any} + + + ) + } + + return {inner as any} +} + +/* ================================================================================================ + * Rendering helpers + * ============================================================================================== */ + +function makeRowId( + baseId: string, + search: SearchContext | undefined, + surfaceId: string | null, +) { + if (!search || !search.isDeep || !surfaceId) return baseId + return baseId // keep stable to avoid breaking references +} + +function renderMenu( + menu: Menu, + slots: Required>, + defaults: Partial> | undefined, + classNames: Partial | undefined, + store: SurfaceStore, + search?: SearchContext, +) { + return ( + + {(menu.nodes ?? []).map((node) => { + if (node.hidden) return null + if (node.kind === 'item') { + return ( + + ) + } + if (node.kind === 'group') { + return ( +
+ {node.heading ? ( +
+ {node.heading} +
+ ) : null} + {node.nodes.map((child) => { + if (child.hidden) return null + // Set the menu reference for the child node + if (child.kind === 'item') { + return ( + } + slot={slots.Item} + defaults={defaults} + classNames={classNames} + store={store} + search={search} + /> + ) + } + return ( + + } + slot={slots.SubmenuTrigger as any} + classNames={classNames} + search={search} + /> + + + ) + })} +
+ ) + } + if (node.kind === 'submenu') { + return ( + + + + + ) + } + return null + })} +
+ ) +} + +function ItemRow({ + node, + slot, + classNames, + defaults, + store, + search, +}: { + node: ItemNode + slot: NonNullable['Item']> + classNames?: Partial + defaults?: Partial> + store: SurfaceStore + search?: SearchContext +}) { + const ref = React.useRef(null) + const surfaceId = useSurfaceId() + const mode = useDisplayMode() + const rowId = makeRowId(node.id, search, surfaceId) + const root = useRootCtx() + const onSelect = node.onSelect ?? defaults?.item?.onSelect + const closeOnSelect = + node.closeOnSelect ?? defaults?.item?.closeOnSelect ?? false + + const handleSelect = React.useCallback(() => { + onSelect?.({ node, search }) + if (closeOnSelect) { + root.onOpenChange(false) + } + }, [onSelect, node, search, closeOnSelect, root]) + + React.useEffect(() => { + const el = ref.current + if (!el) return + const onSelectFromKey: EventListener = () => { + handleSelect() + } + el.addEventListener(SELECT_ITEM_EVENT, onSelectFromKey) + return () => el.removeEventListener(SELECT_ITEM_EVENT, onSelectFromKey) + }, [handleSelect]) + + React.useEffect(() => { + store.registerRow(rowId, { + ref: ref as any, + disabled: false, + kind: 'item', + }) + return () => store.unregisterRow(rowId) + }, [store, rowId]) + + const activeId = useSurfaceSel(store, (s) => s.activeId) + const focused = activeId === rowId + const { aimGuardActiveRef } = useHoverPolicy() + + const baseRowProps = React.useMemo( + () => + ({ + id: rowId, + ref: ref as any, + role: 'option' as const, + tabIndex: -1, + 'data-action-menu-item-id': rowId, + 'data-focused': focused, + 'aria-selected': focused, + 'aria-disabled': false, + 'data-mode': mode, + className: classNames?.item, + onPointerDown: (e: React.PointerEvent) => { + e.preventDefault() + }, + onMouseMove: () => { + if (aimGuardActiveRef.current) return + if (!focused) store.setActiveId(rowId, 'pointer') + }, + onClick: (e: React.MouseEvent) => { + e.preventDefault() + handleSelect() + }, + }) as const, + [rowId, handleSelect, focused, store, classNames?.item, aimGuardActiveRef], + ) + + const bind: RowBindAPI = { + focused, + disabled: false, + getRowProps: (overrides) => + mergeProps(baseRowProps as any, overrides as any), + } + + const visual = slot({ node, bind, search, mode }) + // If the slot placed `getRowProps` on any nested node, just return it as-is. + if (hasDescendantWithProp(visual, 'data-action-menu-item-id')) { + return visual as React.ReactElement + } + const fallbackVisual = visual ?? {node.label ?? String(node.id)} + return
{fallbackVisual}
+} + +/** Controlled/connected Input slot wrapper that wires ARIA and key handling. */ +function InputView({ + store, + value, + onChange, + slot, + slotProps, + inputPlaceholder, + classNames, +}: { + store: SurfaceStore + value: string + onChange: (v: string) => void + slot: NonNullable['Input']> + slotProps: Partial + inputPlaceholder?: string + classNames?: Partial +}) { + const activeId = useSurfaceSel(store, (s) => s.activeId ?? undefined) + const listId = useSurfaceSel(store, (s) => s.listId ?? undefined) + const mode = useDisplayMode() + const onKeyDown = useNavKeydown('input') + const baseInputProps = { + ref: store.inputRef as any, + role: 'combobox', + 'data-slot': 'action-menu-input', + 'data-action-menu-input': true, + 'aria-autocomplete': 'list', + 'aria-expanded': true, + 'aria-controls': listId, + 'aria-activedescendant': activeId, + 'data-mode': mode, + className: classNames?.input, + placeholder: inputPlaceholder ?? 'Filter...', + value, + onChange: (e: React.ChangeEvent) => + onChange(e.target.value), + onKeyDown, + } + const bind: InputBindAPI = { + getInputProps: (overrides) => + mergeProps( + baseInputProps as any, + mergeProps(slotProps?.input as any, overrides as any), + ), + } + const el = slot({ value, onChange, bind }) + if (!isElementWithProp(el, 'data-action-menu-input')) + return + return el as React.ReactElement +} + +/** List view that renders the unfiltered tree or flattened search results. */ +function ListView({ + store, + menu, + slots, + slotProps, + defaults, + classNames, + query, + inputActive, + onTypeStart, +}: { + store: SurfaceStore + menu: Menu + slots: Required> + slotProps?: Partial + defaults?: Partial> + classNames?: Partial + query?: string + inputActive: boolean + onTypeStart: (seed: string) => void +}) { + const localId = React.useId() + const listId = useSurfaceSel(store, (s) => s.listId) + const hasInput = useSurfaceSel(store, (s) => s.hasInput) + const activeId = useSurfaceSel(store, (s) => s.activeId ?? undefined) + const navKeydown = useNavKeydown('list') + const { ownerId } = useFocusOwner() + const surfaceId = useSurfaceId() ?? 'root' + const mode = useDisplayMode() + + const onKeyDown = React.useCallback( + (e: React.KeyboardEvent) => { + if (ownerId !== surfaceId) return + if (!inputActive && !e.altKey && !e.ctrlKey && !e.metaKey) { + if (e.key === 'Backspace') { + e.preventDefault() + onTypeStart('') + return + } + if (e.key.length === 1) { + e.preventDefault() + onTypeStart(e.key) + return + } + } + navKeydown(e) + }, + [surfaceId, ownerId, inputActive, onTypeStart, navKeydown], + ) + + React.useEffect(() => { + const id = listId ?? `action-menu-list-${localId}` + store.set('listId', id) + return () => store.set('listId', null) + }, [localId]) + + const effectiveListId = + store.snapshot().listId ?? `action-menu-list-${localId}` + + const baseListProps = { + ref: store.listRef as any, + role: 'listbox' as const, + id: effectiveListId, + tabIndex: hasInput ? -1 : 0, + 'data-slot': 'action-menu-list' as const, + 'data-action-menu-list': true as const, + 'aria-activedescendant': hasInput ? undefined : activeId, + 'data-mode': mode, + className: classNames?.list, + onKeyDown, + } + const bind: ListBindAPI = { + getListProps: (overrides) => + mergeProps( + baseListProps as any, + mergeProps(slotProps?.list as any, overrides as any), + ), + getItemOrder: () => store.getOrder(), + getActiveId: () => store.snapshot().activeId, + } + + const q = (query ?? '').trim() + + type SRItem = { + type: 'item' + node: ItemNode + breadcrumbs: string[] + breadcrumbIds: string[] + score: number + } + type SRSub = { + type: 'submenu' + node: SubmenuNode + breadcrumbs: string[] + breadcrumbIds: string[] + score: number + } + type SR = SRItem | SRSub + + const collect = React.useCallback( + ( + nodes: Node[] | undefined, + q: string, + bc: string[] = [], + bcIds: string[] = [], + currentMenu: Menu = menu, + ): SR[] => { + const out: SR[] = [] + for (const n of nodes ?? []) { + if ((n as any).hidden) continue + if (n.kind === 'item') { + const score = commandScore(n.id, q, n.keywords) + if (score > 0) + out.push({ + type: 'item', + node: { + ...n, + id: bcIds.at(-1) ? `${bcIds.at(-1)}-${n.id}` : n.id, + menu: currentMenu, + } as ItemNode, + breadcrumbs: bc, + breadcrumbIds: bcIds, + score, + }) + } else if (n.kind === 'group') { + out.push(...collect((n as GroupNode).nodes, q, bc, bcIds)) + } else if (n.kind === 'submenu') { + const sub = n as SubmenuNode + const score = commandScore(n.id, q, n.keywords) + if (score > 0) + out.push({ + type: 'submenu', + node: { ...sub, parent: currentMenu, def: sub.def }, + breadcrumbs: bc, + breadcrumbIds: bcIds, + score, + }) + const title = sub.title ?? sub.label ?? sub.id ?? '' + out.push( + ...collect( + sub.nodes as any, + q, + [...bc, title], + [...bcIds, sub.id], + sub.child as Menu, + ), + ) + } + } + return out + }, + [], + ) + + const results = React.useMemo( + () => + q + ? pipe( + collect(menu.nodes, q, [], [], menu), + sortBy([prop('score'), 'desc']), + partition((v) => v.type === 'submenu'), + flat(), + ) + : [], + [q, menu.nodes], + ) + const firstRowId = React.useMemo( + () => results[0]?.node.id ?? null, + [results[0]], + ) + + let children: React.ReactNode + + React.useLayoutEffect(() => { + if (!q) return + if (!firstRowId) return + const raf = requestAnimationFrame(() => + store.setActiveId(firstRowId, 'keyboard'), + ) + return () => cancelAnimationFrame(raf) + }, [q]) + + React.useLayoutEffect(() => { + const raf = requestAnimationFrame(() => { + const listEl = store.listRef.current + if (!listEl) return + const ids = Array.from( + listEl.querySelectorAll('[data-action-menu-item-id]'), + ).map((el) => el.id) + store.resetOrder(ids) + }) + return () => cancelAnimationFrame(raf) + }, [store, q]) + + if (q.length === 0) { + const hasAnyNodes = (menu.nodes ?? []).some((n) => !n.hidden) + children = hasAnyNodes + ? renderMenu(menu, slots, defaults, classNames, store, undefined) + : slots.Empty({ query: '' }) + } else { + children = + results.length === 0 + ? slots.Empty({ query: q }) + : results.map((res) => { + const searchCtx: SearchContext = { + query: q, + isDeep: res.breadcrumbs.length > 0, + breadcrumbs: res.breadcrumbs, + breadcrumbIds: res.breadcrumbIds, + } + if (res.type === 'item') { + return ( + + ) + } + const childMenu: SubmenuNode = { ...res.node } + return ( + + + + + ) + }) + } + + const el = slots.List({ children, bind }) + if (!isElementWithProp(el, 'data-action-menu-list')) { + return ( +
{}, + }), + ) as any)} + > + {children} +
+ ) + } + return el as React.ReactElement +} + +/* ================================================================================================ + * Shells & Entry (ActionMenu) + * ============================================================================================== */ + +export interface ActionMenuProps extends Children { + open?: boolean + defaultOpen?: boolean + onOpenChange?: (open: boolean) => void + modal?: boolean + responsive?: Partial + /** Drawer/overlay styles */ + shellClassNames?: Partial + /** Drawer-specific slot props */ + shellSlotProps?: Partial + debug?: boolean +} + +function DropdownShell({ + children, + disableOutsidePointerEvents, + onClose, +}: { + children: React.ReactNode + disableOutsidePointerEvents: boolean + onClose: () => void +}) { + return ( + onClose()} + onInteractOutside={(event) => { + const target = event.target as HTMLElement | null + if (target?.closest?.('[data-action-menu-surface]')) return + event.preventDefault() + onClose() + }} + > + {children} + + ) +} + +/** Drawer shell that mounts everything except the Trigger inside Vaul.Content. */ +function DrawerShell({ children }: { children: React.ReactNode }) { + const root = useRootCtx() + + // Split children: keep Triggers outside content, render everything else inside Drawer.Content + const elements = React.Children.toArray(children) as React.ReactElement[] + const triggerTypeName = 'ActionMenu.Trigger' + const triggers: React.ReactNode[] = [] + const body: React.ReactNode[] = [] + + elements.forEach((child) => { + const isTrigger = + React.isValidElement(child) && + (child.type as any)?.displayName === triggerTypeName + if (isTrigger) triggers.push(child) + else body.push(child) + }) + + return ( + // @ts-expect-error + + {triggers} + + + { + event.preventDefault() + }} + onCloseAutoFocus={(event) => { + event.preventDefault() + }} + > + {/* Optional accessible title; hidden visually */} + Action Menu +
+ {body} + + + + ) +} + +/** Entry component: chooses the shell and provides root/display/focus contexts. */ +export const ActionMenu = ({ + children, + open: openProp, + defaultOpen, + onOpenChange, + modal = true, + responsive: responsiveProp, + shellClassNames, + shellSlotProps, + debug = false, +}: ActionMenuProps) => { + const [open, setOpen] = useControllableState({ + prop: openProp, + defaultProp: defaultOpen ?? false, + onChange: onOpenChange, + }) + const anchorRef = React.useRef(null) + const [ownerId, setOwnerId] = React.useState(null) + + const responsive = React.useMemo( + () => ({ + mode: responsiveProp?.mode ?? 'auto', + query: responsiveProp?.query ?? '(max-width: 640px), (pointer: coarse)', + }), + [responsiveProp], + ) + const { mode, query } = responsive + const autoDrawer = useMediaQuery(query) + const resolvedMode: MenuDisplayMode = + mode === 'drawer' || mode === 'dropdown' + ? mode + : autoDrawer + ? 'drawer' + : 'dropdown' + + const rootCtxValue: ActionMenuRootContextValue = { + open, + onOpenChange: setOpen, + onOpenToggle: () => setOpen((v) => !v), + anchorRef, + modal, + debug, + responsive, + shellClassNames, + shellSlotProps, + } + + const content = + resolvedMode === 'dropdown' ? ( + setOpen(false)} + > + {children} + + ) : ( + {children} + ) + + return ( + + + + {content} + + + + ) +} + +/* ================================================================================================ + * Trigger + * ============================================================================================== */ + +export interface ActionMenuTriggerProps extends ButtonProps {} + +/** Button that toggles the menu. Also acts as the Popper anchor (dropdown) or Drawer.Trigger (drawer). */ +export const Trigger = React.forwardRef< + HTMLButtonElement, + ActionMenuTriggerProps +>( + ( + { children, disabled, onPointerDown, onKeyDown, className, ...props }, + forwardedRef, + ) => { + const root = useRootCtx() + const mode = useDisplayMode() + const ResponsiveTrigger = mode === 'drawer' ? Drawer.Trigger : Popper.Anchor + + return ( + + + { + if (!disabled && event.button === 0 && event.ctrlKey === false) { + const willOpen = !root.open + root.onOpenToggle() + if (willOpen) event.preventDefault() + } + })} + onKeyDown={composeEventHandlers(onKeyDown, (event) => { + if (disabled) return + if (event.key === 'Enter' || event.key === ' ') + root.onOpenToggle() + if (event.key === 'ArrowDown') root.onOpenChange(true) + if (['Enter', ' ', 'ArrowDown'].includes(event.key)) + event.preventDefault() + })} + aria-haspopup="menu" + aria-expanded={root.open} + > + {children} + + + + ) + }, +) +Trigger.displayName = 'ActionMenu.Trigger' + +/* ================================================================================================ + * Factory — createActionMenu + * ============================================================================================== */ + +export type CreateActionMenuResult = { + Root: React.FC + Trigger: typeof Trigger + Positioner: typeof Positioner + Surface: React.ForwardRefExoticComponent< + ActionMenuSurfaceProps & React.RefAttributes + > +} + +export function createActionMenu(opts?: { + /** Default content-level options such as `vimBindings` and `dir`. */ + defaults?: { + content?: Pick< + ActionMenuSurfaceProps, + 'vimBindings' | 'dir' | 'onCloseAutoClear' + > + } + surface?: { + slots?: Partial> + slotProps?: Partial + classNames?: Partial + } + /** NEW: Defaults for the Vaul/overlay shell. */ + shell?: { + classNames?: Partial + slotProps?: Partial + } +}): CreateActionMenuResult { + const baseSlots = { + ...defaultSlots(), + ...(opts?.surface?.slots as any), + } as Required> + const baseSurfaceSlotProps = ((opts?.surface?.slotProps as any) ?? + {}) as Partial + const baseDefaults = opts?.defaults?.content ?? {} + const baseSurfaceClassNames = opts?.surface?.classNames + const baseShellClassNames = opts?.shell?.classNames + const baseShellSlotProps = opts?.shell?.slotProps + + /** Typed Surface that merges factory defaults with per-instance props. */ + const SurfaceTyped = React.forwardRef< + HTMLDivElement, + ActionMenuSurfaceProps + >(({ slots, surfaceSlotProps, surfaceClassNames, ...rest }, ref) => { + const mergedSlots = React.useMemo>( + () => ({ ...baseSlots, ...(slots as any) }), + [slots], + ) + const mergedSlotProps = React.useMemo>( + () => ({ ...baseSurfaceSlotProps, ...(surfaceSlotProps ?? {}) }), + [surfaceSlotProps], + ) + const mergedClassNames = React.useMemo>( + () => ({ + root: cn(baseSurfaceClassNames?.root, surfaceClassNames?.root), + content: cn(baseSurfaceClassNames?.content, surfaceClassNames?.content), + input: cn(baseSurfaceClassNames?.input, surfaceClassNames?.input), + list: cn(baseSurfaceClassNames?.list, surfaceClassNames?.list), + item: cn(baseSurfaceClassNames?.item, surfaceClassNames?.item), + subtrigger: cn( + baseSurfaceClassNames?.subtrigger, + surfaceClassNames?.subtrigger, + ), + group: cn(baseSurfaceClassNames?.group, surfaceClassNames?.group), + groupHeading: cn( + baseSurfaceClassNames?.groupHeading, + surfaceClassNames?.groupHeading, + ), + }), + [surfaceClassNames, baseSurfaceClassNames], + ) + + const vimBindings = rest.vimBindings ?? baseDefaults.vimBindings ?? true + const dir = (rest.dir ?? baseDefaults.dir) as Direction | undefined + const onCloseAutoClear = + rest.onCloseAutoClear ?? baseDefaults.onCloseAutoClear ?? true + + return ( + + {...(rest as any)} + vimBindings={vimBindings} + dir={dir} + onCloseAutoClear={onCloseAutoClear} + slots={mergedSlots} + surfaceSlotProps={mergedSlotProps} + surfaceClassNames={mergedClassNames} + ref={ref} + /> + ) + }) + SurfaceTyped.displayName = 'ActionMenu.Surface' + + /** Typed ActionMenu wrapper that injects default *shell* props. */ + const ActionMenuTyped: React.FC = (p) => { + const mergedShellClassNames: Partial = { + root: cn(baseShellClassNames?.root, p.shellClassNames?.root), + overlay: cn(baseShellClassNames?.overlay, p.shellClassNames?.overlay), + drawerContent: cn( + baseShellClassNames?.drawerContent, + p.shellClassNames?.drawerContent, + ), + drawerHandle: cn( + baseShellClassNames?.drawerHandle, + p.shellClassNames?.drawerHandle, + ), + trigger: cn(baseShellClassNames?.trigger, p.shellClassNames?.trigger), + } + const mergedShellSlotProps: Partial = { + ...(baseShellSlotProps ?? {}), + ...(p.shellSlotProps ?? {}), + } + return ( + + ) + } + + return { + Root: ActionMenuTyped, + Trigger, + Positioner, + Surface: SurfaceTyped, + } +} diff --git a/packages/action-menu/src/cn.ts b/packages/action-menu/src/cn.ts new file mode 100644 index 00000000..d32b0fe6 --- /dev/null +++ b/packages/action-menu/src/cn.ts @@ -0,0 +1,6 @@ +import { type ClassValue, clsx } from 'clsx' +import { twMerge } from 'tailwind-merge' + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} diff --git a/packages/action-menu/src/command-score.ts b/packages/action-menu/src/command-score.ts new file mode 100644 index 00000000..c49534fd --- /dev/null +++ b/packages/action-menu/src/command-score.ts @@ -0,0 +1,194 @@ +/** biome-ignore-all lint/style/useSingleVarDeclarator: from cmdk */ +/** biome-ignore-all lint/complexity/noUselessEscapeInRegex: from cmdk */ + +// The scores are arranged so that a continuous match of characters will +// result in a total score of 1. +// +// The best case, this character is a match, and either this is the start +// of the string, or the previous character was also a match. +var SCORE_CONTINUE_MATCH = 1, + // A new match at the start of a word scores better than a new match + // elsewhere as it's more likely that the user will type the starts + // of fragments. + // NOTE: We score word jumps between spaces slightly higher than slashes, brackets + // hyphens, etc. + SCORE_SPACE_WORD_JUMP = 0.9, + SCORE_NON_SPACE_WORD_JUMP = 0.8, + // Any other match isn't ideal, but we include it for completeness. + SCORE_CHARACTER_JUMP = 0.17, + // If the user transposed two letters, it should be significantly penalized. + // + // i.e. "ouch" is more likely than "curtain" when "uc" is typed. + SCORE_TRANSPOSITION = 0.1, + // The goodness of a match should decay slightly with each missing + // character. + // + // i.e. "bad" is more likely than "bard" when "bd" is typed. + // + // This will not change the order of suggestions based on SCORE_* until + // 100 characters are inserted between matches. + PENALTY_SKIPPED = 0.999, + // The goodness of an exact-case match should be higher than a + // case-insensitive match by a small amount. + // + // i.e. "HTML" is more likely than "haml" when "HM" is typed. + // + // This will not change the order of suggestions based on SCORE_* until + // 1000 characters are inserted between matches. + PENALTY_CASE_MISMATCH = 0.9999, + // If the word has more characters than the user typed, it should + // be penalised slightly. + // + // i.e. "html" is more likely than "html5" if I type "html". + // + // However, it may well be the case that there's a sensible secondary + // ordering (like alphabetical) that it makes sense to rely on when + // there are many prefix matches, so we don't make the penalty increase + // with the number of tokens. + PENALTY_NOT_COMPLETE = 0.99 + +var IS_GAP_REGEXP = /[\\\/_+.#"@\[\(\{&]/, + COUNT_GAPS_REGEXP = /[\\\/_+.#"@\[\(\{&]/g, + IS_SPACE_REGEXP = /[\s-]/, + COUNT_SPACE_REGEXP = /[\s-]/g + +function commandScoreInner( + string: string, + abbreviation: string, + lowerString: string, + lowerAbbreviation: string, + stringIndex: number, + abbreviationIndex: number, + memoizedResults: Record, + // string: string, + // abbreviation, + // lowerString, + // lowerAbbreviation, + // stringIndex, + // abbreviationIndex, + // memoizedResults, +) { + if (abbreviationIndex === abbreviation.length) { + if (stringIndex === string.length) { + return SCORE_CONTINUE_MATCH + } + return PENALTY_NOT_COMPLETE + } + + var memoizeKey = `${stringIndex},${abbreviationIndex}` + if (memoizedResults[memoizeKey] !== undefined) { + return memoizedResults[memoizeKey]! + } + + var abbreviationChar = lowerAbbreviation.charAt(abbreviationIndex) + var index = lowerString.indexOf(abbreviationChar, stringIndex) + var highScore = 0 + + var score: number, + transposedScore: number, + wordBreaks: RegExpMatchArray | null, + spaceBreaks: RegExpMatchArray | null + + while (index >= 0) { + score = commandScoreInner( + string, + abbreviation, + lowerString, + lowerAbbreviation, + index + 1, + abbreviationIndex + 1, + memoizedResults, + ) + if (score > highScore) { + if (index === stringIndex) { + score *= SCORE_CONTINUE_MATCH + } else if (IS_GAP_REGEXP.test(string.charAt(index - 1))) { + score *= SCORE_NON_SPACE_WORD_JUMP + wordBreaks = string + .slice(stringIndex, index - 1) + .match(COUNT_GAPS_REGEXP) + if (wordBreaks && stringIndex > 0) { + score *= PENALTY_SKIPPED ** wordBreaks.length + } + } else if (IS_SPACE_REGEXP.test(string.charAt(index - 1))) { + score *= SCORE_SPACE_WORD_JUMP + spaceBreaks = string + .slice(stringIndex, index - 1) + .match(COUNT_SPACE_REGEXP) + if (spaceBreaks && stringIndex > 0) { + score *= PENALTY_SKIPPED ** spaceBreaks.length + } + } else { + score *= SCORE_CHARACTER_JUMP + if (stringIndex > 0) { + score *= PENALTY_SKIPPED ** (index - stringIndex) + } + } + + if (string.charAt(index) !== abbreviation.charAt(abbreviationIndex)) { + score *= PENALTY_CASE_MISMATCH + } + } + + if ( + (score < SCORE_TRANSPOSITION && + lowerString.charAt(index - 1) === + lowerAbbreviation.charAt(abbreviationIndex + 1)) || + (lowerAbbreviation.charAt(abbreviationIndex + 1) === + lowerAbbreviation.charAt(abbreviationIndex) && // allow duplicate letters. Ref #7428 + lowerString.charAt(index - 1) !== + lowerAbbreviation.charAt(abbreviationIndex)) + ) { + transposedScore = commandScoreInner( + string, + abbreviation, + lowerString, + lowerAbbreviation, + index + 1, + abbreviationIndex + 2, + memoizedResults, + ) + + if (transposedScore * SCORE_TRANSPOSITION > score) { + score = transposedScore * SCORE_TRANSPOSITION + } + } + + if (score > highScore) { + highScore = score + } + + index = lowerString.indexOf(abbreviationChar, index + 1) + } + + memoizedResults[memoizeKey] = highScore + return highScore +} + +function formatInput(string: string) { + // convert all valid space characters to space so they match each other + return string.toLowerCase().replace(COUNT_SPACE_REGEXP, ' ') +} + +export function commandScore( + string: string, + abbreviation: string, + aliases?: string[], +): number { + /* NOTE: + * in the original, we used to do the lower-casing on each recursive call, but this meant that toLowerCase() + * was the dominating cost in the algorithm, passing both is a little ugly, but considerably faster. + */ + const transformedString = + aliases && aliases.length > 0 ? `${string} ${aliases.join(' ')}` : string + + return commandScoreInner( + transformedString, + abbreviation, + formatInput(transformedString), + formatInput(abbreviation), + 0, + 0, + {}, + ) +} diff --git a/packages/action-menu/src/index.ts b/packages/action-menu/src/index.ts new file mode 100644 index 00000000..3f2c5dd2 --- /dev/null +++ b/packages/action-menu/src/index.ts @@ -0,0 +1,24 @@ +/** biome-ignore-all assist/source/organizeImports: manual order */ +'use client' + +export type { + ActionMenuProps, + ActionMenuTriggerProps, + ActionMenuPositionerProps, + ActionMenuSurfaceProps, +} from './action-menu.js' + +export type { + MenuDef, + GroupDef, + ItemDef, + SubmenuDef, + Menu, + GroupNode, + ItemNode, + SubmenuNode, + MenuNodeKind, + CreateActionMenuResult, +} from './action-menu.js' + +export { renderIcon, createActionMenu } from './action-menu.js' diff --git a/packages/action-menu/src/use-media-query.tsx b/packages/action-menu/src/use-media-query.tsx new file mode 100644 index 00000000..2e48c88d --- /dev/null +++ b/packages/action-menu/src/use-media-query.tsx @@ -0,0 +1,14 @@ +import * as React from 'react' + +export function useMediaQuery(query: string, fallback = false) { + const [matches, setMatches] = React.useState(fallback) + React.useEffect(() => { + if (typeof window === 'undefined' || !('matchMedia' in window)) return + const mq = window.matchMedia(query) + const onChange = () => setMatches(mq.matches) + onChange() + mq.addEventListener('change', onChange) + return () => mq.removeEventListener('change', onChange) + }, [query]) + return matches +} diff --git a/packages/action-menu/tsconfig.json b/packages/action-menu/tsconfig.json new file mode 100644 index 00000000..1d6a51b1 --- /dev/null +++ b/packages/action-menu/tsconfig.json @@ -0,0 +1,35 @@ +{ + "include": ["src"], + "exclude": ["node_modules"], + "compilerOptions": { + /* Base Options: */ + "esModuleInterop": true, + "skipLibCheck": true, + "target": "es2022", + "allowJs": true, + "jsx": "react-jsx", + "resolveJsonModule": true, + "moduleDetection": "force", + "isolatedModules": true, + "verbatimModuleSyntax": true, + + /* Strictness */ + "strict": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + + /* If transpiling with TypeScript: */ + "module": "NodeNext", + "outDir": "dist", + "rootDir": "src", + "sourceMap": true, + + /* AND if you're building for a library: */ + "declaration": true, + + /* AND if you're building for a library in a monorepo: */ + "declarationMap": true, + + "lib": ["es2022", "dom", "dom.iterable"] + } +} diff --git a/packages/action-menu/tsup.config.ts b/packages/action-menu/tsup.config.ts new file mode 100644 index 00000000..8853a67a --- /dev/null +++ b/packages/action-menu/tsup.config.ts @@ -0,0 +1,19 @@ +import { defineConfig, type Options } from 'tsup' + +export default defineConfig((options: Options) => ({ + entry: { + index: './src/index.ts', + }, + format: ['esm'], + dts: true, + minify: !options.watch, // Don't minify in watch mode for faster builds + sourcemap: true, + clean: true, + splitting: false, + external: ['react', 'react-dom'], + // Explicitly exclude test files + ignoreWatch: ['src/__tests__/**/*'], + outDir: 'dist/', + onSuccess: options.watch ? 'echo "✅ Package rebuilt"' : undefined, + ...options, +})) diff --git a/packages/action-menu/vitest.config.mts b/packages/action-menu/vitest.config.mts new file mode 100644 index 00000000..2755213c --- /dev/null +++ b/packages/action-menu/vitest.config.mts @@ -0,0 +1,20 @@ +import path from 'node:path' +import react from '@vitejs/plugin-react' +import tsConfigPaths from 'vite-tsconfig-paths' +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + plugins: [tsConfigPaths(), react()], + test: { + include: ['./**/*.test.?(c|m)[jt]s?(x)'], + exclude: ['./node_modules/**/*'], + globals: true, + environment: 'jsdom', + }, + + resolve: { + alias: { + '@': path.resolve(__dirname, '.'), + }, + }, +}) diff --git a/packages/filters/scripts/publish.ts b/packages/filters/scripts/publish.ts index 0920999b..7465aca0 100644 --- a/packages/filters/scripts/publish.ts +++ b/packages/filters/scripts/publish.ts @@ -96,8 +96,13 @@ async function publishPackage({ // Tag git commit if (!noGitTag) { try { - execSync(`git tag v${packageJson.version}`, { stdio: 'pipe' }) - execSync(`git push origin v${packageJson.version}`, { stdio: 'pipe' }) + execSync(`git tag ${packageJson.name}@${packageJson.version}`, { + stdio: 'pipe', + }) + execSync( + `git push origin ${packageJson.name}@${packageJson.version}`, + { stdio: 'pipe' }, + ) console.log(`🏷 Created git tag v${packageJson.version}`) } catch (error) { console.warn('! Failed to create git tag:', error) @@ -147,7 +152,7 @@ async function main() { } } -// @ts-ignore +// @ts-expect-error if (import.meta.main) { main() } diff --git a/turbo.json b/turbo.json index 29374412..ea90272d 100644 --- a/turbo.json +++ b/turbo.json @@ -15,6 +15,13 @@ "inputs": ["$TURBO_DEFAULT$", ".env*", "registry.json"], "outputs": ["public/r/**"] }, + "types:meta": { + "outputs": ["apps/web/.types/**"] + }, + "types:meta:watch": { + "cache": false, + "persistent": true + }, "dev": { "cache": false, "persistent": true
= { CollapsibleCodeBlock, TypeTable, IssuesTableWrapper, + // @ts-expect-error + Examples, + ComponentCode: ComponentCode, + ComponentFrame, + CodeInline, + PropRow, + PropsTable, + ComponentsList, } diff --git a/apps/web/components/prop-row.tsx b/apps/web/components/prop-row.tsx new file mode 100644 index 00000000..5df831b6 --- /dev/null +++ b/apps/web/components/prop-row.tsx @@ -0,0 +1,81 @@ +import * as Collapsible from '@radix-ui/react-collapsible' +import { ChevronDown } from 'lucide-react' // or any icon you prefer +import { cn } from '@/lib/utils' +import CodeInline from './code-inline' // the inline Shiki component from earlier + +type Props = { + name: string + type: string + required?: boolean + defaultValue?: string + description?: string + className?: string +} + +export default function PropRow({ + name, + type, + required = false, + defaultValue, + description, + className, +}: Props) { + return ( + + +
+ + {name} + + {required && ( + required + )} +
+
+ {/* short type preview */} +
+ +
+ +
+
+ + +
+
Name
+
+ {name} +
+
Type
+
+ +
+ +
Required
+
{required ? 'Yes' : 'No'}
+ + {defaultValue !== undefined && defaultValue !== '' ? ( + <> +
Default
+
+ +
+ + ) : null} + + {description ? ( + <> +
Description
+
{description}
+ + ) : null} +
+
+
+ ) +} diff --git a/apps/web/components/props-table.tsx b/apps/web/components/props-table.tsx new file mode 100644 index 00000000..9750ad50 --- /dev/null +++ b/apps/web/components/props-table.tsx @@ -0,0 +1,35 @@ +import { cn } from '@/lib/utils' + +interface PropsTableProps extends React.ComponentProps<'div'> { + name?: string +} + +export const PropsTable = ({ + name, + children, + className, + ...props +}: PropsTableProps) => { + return ( +
+ {name && ( +
+ {name} +
+ )} +
+
+ Name + Type +
+ {children} +
+
+ ) +} diff --git a/apps/web/components/ui/button.tsx b/apps/web/components/ui/button.tsx index 1d4676a7..25fc5717 100644 --- a/apps/web/components/ui/button.tsx +++ b/apps/web/components/ui/button.tsx @@ -1,23 +1,24 @@ import { Slot } from '@radix-ui/react-slot' -import { type VariantProps, cva } from 'class-variance-authority' +import { cva, type VariantProps } from 'class-variance-authority' import type * as React from 'react' import { cn } from '@/lib/utils' const buttonVariants = cva( - "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-[color,box-shadow] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-[color,box-shadow] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring focus-visible:ring-offset-background focus-visible:ring-offset-[3px] focus-visible:ring-[2px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive select-none", { variants: { variant: { default: - 'bg-primary text-primary-foreground shadow-xs hover:bg-primary/90', + 'bg-primary text-primary-foreground shadow-xs hover:bg-primary/90 aria-expanded:bg-primary/90', destructive: - 'bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40', + 'bg-destructive text-white shadow-xs hover:bg-destructive/90 aria-expanded:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40', outline: - 'border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground', + 'border border-input bg-background shadow-xs hover:bg-accent aria-expanded:bg-accent hover:text-accent-foreground', secondary: - 'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80', - ghost: 'hover:bg-accent hover:text-accent-foreground', + 'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80 aria-expanded:bg-secondary/80', + ghost: + 'hover:bg-accent hover:text-accent-foreground has-[+_[data-action-menu-surface][data-state=open]]:bg-accent aria-expanded:bg-accent', link: 'text-primary underline-offset-4 hover:underline', }, size: { diff --git a/apps/web/components/ui/hover-card.tsx b/apps/web/components/ui/hover-card.tsx new file mode 100644 index 00000000..54e3062e --- /dev/null +++ b/apps/web/components/ui/hover-card.tsx @@ -0,0 +1,44 @@ +'use client' + +import * as HoverCardPrimitive from '@radix-ui/react-hover-card' +import type * as React from 'react' + +import { cn } from '@/lib/utils' + +function HoverCard({ + ...props +}: React.ComponentProps) { + return +} + +function HoverCardTrigger({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function HoverCardContent({ + className, + align = 'center', + sideOffset = 4, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +export { HoverCard, HoverCardTrigger, HoverCardContent } diff --git a/apps/web/components/ui/scroll-area.tsx b/apps/web/components/ui/scroll-area.tsx new file mode 100644 index 00000000..9d293684 --- /dev/null +++ b/apps/web/components/ui/scroll-area.tsx @@ -0,0 +1,58 @@ +'use client' + +import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area' +import type * as React from 'react' + +import { cn } from '@/lib/utils' + +function ScrollArea({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + + {children} + + + + + ) +} + +function ScrollBar({ + className, + orientation = 'vertical', + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +export { ScrollArea, ScrollBar } diff --git a/apps/web/components/ui/sidebar.tsx b/apps/web/components/ui/sidebar.tsx index 9096104b..92ef83ec 100644 --- a/apps/web/components/ui/sidebar.tsx +++ b/apps/web/components/ui/sidebar.tsx @@ -1,7 +1,7 @@ 'use client' import { Slot } from '@radix-ui/react-slot' -import { type VariantProps, cva } from 'class-variance-authority' +import { cva, type VariantProps } from 'class-variance-authority' import { PanelLeftIcon } from 'lucide-react' import * as React from 'react' @@ -236,7 +236,7 @@ function Sidebar({ // Adjust the padding for floating and inset variants. variant === 'floating' || variant === 'inset' ? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]' - : 'group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l', + : 'group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l border-sidebar-border/50', className, )} {...props} @@ -474,7 +474,7 @@ function SidebarMenuItem({ className, ...props }: React.ComponentProps<'li'>) { } export const sidebarMenuButtonVariants = cva( - 'peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0', + 'peer/menu-button flex w-full items-center gap-2 rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0', { variants: { variant: { diff --git a/apps/web/components/ui/sonner.tsx b/apps/web/components/ui/sonner.tsx new file mode 100644 index 00000000..6d468566 --- /dev/null +++ b/apps/web/components/ui/sonner.tsx @@ -0,0 +1,25 @@ +'use client' + +import { useTheme } from 'next-themes' +import { Toaster as Sonner, type ToasterProps } from 'sonner' + +const Toaster = ({ ...props }: ToasterProps) => { + const { theme = 'system' } = useTheme() + + return ( + + ) +} + +export { Toaster } diff --git a/apps/web/components/ui/table.tsx b/apps/web/components/ui/table.tsx index 3a4c6ce4..becaea57 100644 --- a/apps/web/components/ui/table.tsx +++ b/apps/web/components/ui/table.tsx @@ -55,7 +55,7 @@ function TableRow({ className, ...props }: React.ComponentProps<'tr'>) {