@baseline-ui/mcp 0.0.0-nightly-20251125000500 → 0.0.0-nightly-20251203000617
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -1
- package/dist/data/component-source.json +1 -1
- package/dist/guidelines.md +88 -0
- package/dist/index.js +20 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"Group": "import React from \"react\";\nimport { useHover } from \"react-aria\";\n\nimport type { GroupProps } from \"./Group.types\";\n\nexport const Group = React.forwardRef<HTMLDivElement, GroupProps>(\n (\n {\n className,\n style,\n children,\n \"data-block-id\": dataBlockId,\n \"data-block-class\": dataBlock,\n role = \"group\",\n isDisabled,\n ...ariaLabelling\n },\n ref,\n ) => {\n const { hoverProps, isHovered } = useHover({ isDisabled });\n\n return (\n <div\n {...hoverProps}\n role={role}\n data-hovered={isHovered}\n data-disabled={isDisabled}\n data-block-id={dataBlockId}\n data-block-class={dataBlock}\n className={className}\n style={style}\n {...ariaLabelling}\n ref={ref}\n >\n {children}\n </div>\n );\n },\n);\n\nGroup.displayName = \"Group\";\n",
|
|
28
28
|
"I18nProvider": "import { I18nProvider as Provider } from \"react-aria\";\nimport React, { useEffect, useRef, useState } from \"react\";\n\nimport type { LocalizedStrings } from \"react-aria\";\nimport type { I18nProviderProps } from \"./I18Provider.types\";\n\nexport const IntlMessagesProvider = React.createContext<{\n messages: LocalizedStrings;\n shouldLogMissingMessages: boolean;\n hasMissingErrorLoggedFor: React.MutableRefObject<Set<string>>;\n setLang: (lang: string) => void;\n}>({\n messages: {},\n shouldLogMissingMessages: true,\n hasMissingErrorLoggedFor: { current: new Set() },\n setLang: () => {},\n});\n\nexport const I18nProvider: React.FC<I18nProviderProps> = ({\n children,\n messages = {},\n shouldLogMissingMessages = true,\n ...props\n}) => {\n const hasMissingErrorLoggedFor = useRef<Set<string>>(new Set());\n\n const [lang, setLang] = useState(props.locale || \"\");\n\n useEffect(() => {\n hasMissingErrorLoggedFor.current.clear();\n }, [lang, props.locale]);\n\n const messagesValue = React.useMemo(\n () => ({\n messages,\n shouldLogMissingMessages,\n hasMissingErrorLoggedFor,\n setLang,\n }),\n [messages, shouldLogMissingMessages, setLang],\n );\n\n return (\n <Provider {...props}>\n <IntlMessagesProvider.Provider value={messagesValue}>\n {children}\n </IntlMessagesProvider.Provider>\n </Provider>\n );\n};\n\nI18nProvider.displayName = \"I18nProvider\";\n",
|
|
29
29
|
"Icon": "import * as Dompurify from \"dompurify\";\nimport { getOwnerDocument, mergeRefs } from \"@react-aria/utils\";\nimport React, { useId } from \"react\";\n\nimport { invariant } from \"../../utils\";\n\nimport type { IconComponentProps } from \"./Icon.types\";\n\nconst setSizeAttributes = (svgElement: SVGElement, size: number | string) => {\n svgElement.setAttribute(\n \"height\",\n typeof size === \"number\" ? `${size}px` : size,\n );\n svgElement.setAttribute(\n \"width\",\n typeof size === \"number\" ? `${size}px` : size,\n );\n};\n\nconst updateSvgTitle = (svgElement: SVGElement, title: string, id: string) => {\n const titleElement = svgElement.querySelector(\"title\");\n const ownerDocument = getOwnerDocument(svgElement);\n\n if (titleElement) {\n titleElement.textContent = title;\n titleElement.setAttribute(\"id\", id);\n } else {\n const newTitleElement = ownerDocument.createElement(\"title\");\n newTitleElement.textContent = title;\n newTitleElement.setAttribute(\"id\", id);\n svgElement.prepend(newTitleElement);\n }\n};\n\nconst setClassName = (svgElement: SVGElement, className?: string) => {\n if (className) {\n svgElement.classList.add(className);\n }\n};\n\nconst getSanitizedSvgElement = (_svg: string, ownerDocument: Document) => {\n const span = ownerDocument.createElement(\"span\");\n span.innerHTML = Dompurify.default.sanitize(_svg);\n const svgElement = span.querySelector(\"svg\");\n invariant(svgElement, \"Icon: svg prop must be a valid svg string\");\n return svgElement;\n};\n\nconst Icon = React.forwardRef<HTMLSpanElement, IconComponentProps>(\n ({ size, title, className, style, color, ...rest }, ref) => {\n const id = useId();\n const svgRef = React.useRef<HTMLSpanElement>(null);\n\n const _svg = \"svg\" in rest ? rest.svg : undefined;\n\n const svgString = React.useMemo(() => {\n if (_svg) {\n const ownerDocument = getOwnerDocument(svgRef.current);\n\n const svgElement = getSanitizedSvgElement(_svg, ownerDocument);\n if (size) {\n setSizeAttributes(svgElement, size);\n }\n\n if (title) {\n updateSvgTitle(svgElement, title, id);\n }\n\n setClassName(svgElement, className);\n\n return svgElement.outerHTML;\n }\n }, [_svg, size, title, className, id]);\n\n return (\n <span\n className={className}\n ref={mergeRefs(ref, svgRef)}\n style={{ display: \"contents\", ...style, color }}\n dangerouslySetInnerHTML={svgString ? { __html: svgString } : undefined}\n />\n );\n },\n);\n\nIcon.displayName = \"Icon\";\n\nexport { Icon };\n",
|
|
30
|
-
"ImageDropZone": "import { useControlledState } from \"@react-stately/utils\";\nimport React, { useMemo } from \"react\";\nimport {\n VisuallyHidden,\n mergeProps,\n useClipboard,\n useDrop,\n useField,\n useFocusRing,\n useHover,\n} from \"react-aria\";\n\nimport messages from \"./intl\";\nimport { classNames, filterTruthyValues, invariant } from \"../../utils\";\nimport { buttonCn } from \"../ActionButton/ActionButton.css\";\nimport { Box } from \"../Box\";\nimport { freehandCanvasFooterCn } from \"../FreehandCanvas/FreehandCanvas.css\";\nimport { ProgressSpinner } from \"../ProgressSpinner\";\nimport { onDropOrPaste } from \"../FileUpload/hooks/useFileUpload\";\nimport { imageDropZoneCn, opacityNoneCn, previewCn } from \"./ImageDropZone.css\";\nimport { ActionButton } from \"../ActionButton\";\nimport { useI18n, useImage } from \"../../hooks\";\n\nimport type { ActionButtonProps } from \"../ActionButton\";\nimport type { ChangeEventHandler } from \"react\";\nimport type { ImageDropZoneProps } from \"./ImageDropZone.types\";\n\nexport const ImageDropZone = React.forwardRef<\n HTMLDivElement,\n ImageDropZoneProps\n>(\n (\n {\n className,\n style,\n isInline = true,\n accept = \"image/*\",\n defaultImageSrc,\n imageSrc,\n imageAlt,\n pickerButtonLabel,\n isDisabled,\n onDrop,\n onPaste,\n includeCheckeredBackground = true,\n \"data-block-id\": dataBlockId,\n \"data-block-class\": dataBlockClass,\n pickerButtonStyle,\n pickerButtonClassName,\n footerClassName,\n footerStyle,\n clearLabel,\n placeholder,\n labelStyle,\n labelClassName,\n ...rest\n },\n ref,\n ) => {\n const elementRef = React.useRef<HTMLLabelElement>(null);\n const inputRef = React.useRef<HTMLInputElement>(null);\n const { formatMessage } = useI18n(messages);\n\n const [url, setUrl] = useControlledState(\n imageSrc,\n defaultImageSrc || null,\n (value) => {\n invariant(typeof value !== \"string\");\n\n rest.onValueChange?.(value ? [value] : []);\n },\n );\n\n const _url = useMemo(() => getImageSrc(url || undefined), [url]);\n\n const { fieldProps, labelProps } = useField({\n ...rest,\n ref: elementRef,\n });\n\n const { dropProps, isDropTarget } = useDrop({\n ref: elementRef,\n onDrop: async (e) => {\n if (isDisabled) return;\n const files = await onDropOrPaste({ items: e.items });\n if (files.length) {\n setUrl(files[0]);\n }\n onDrop?.(e);\n },\n ...rest,\n });\n\n const { clipboardProps } = useClipboard({\n onPaste: async (items) => {\n if (isDisabled) return;\n const files = await onDropOrPaste({ items });\n if (files.length) {\n setUrl(files[0]);\n }\n onPaste?.(items);\n },\n ...rest,\n });\n\n const reset = () => {\n setUrl(null);\n if (inputRef.current) {\n inputRef.current.value = \"\";\n }\n };\n\n const inputProps = {\n type: \"file\",\n onChange: ((e) => {\n const files = [...(e.target.files as FileList)];\n setUrl(files[0]);\n\n rest.onChange?.(e);\n }) as ChangeEventHandler<HTMLInputElement>,\n accept,\n disabled: isDisabled,\n } as React.ComponentProps<\"input\">;\n\n const { focusProps, isFocused, isFocusVisible } = useFocusRing({\n within: true,\n });\n const { hoverProps, isHovered } = useHover({ isDisabled });\n\n const { imgProps, isLoading, isLoaded } = useImage({\n src: _url,\n alt: imageAlt || \"\",\n });\n\n const chooseFileButtonProps = {\n label: pickerButtonLabel || formatMessage(\"selectImage\"),\n onPress: () => elementRef.current?.click(),\n variant: \"secondary\",\n isDisabled,\n excludeFromTabOrder: true,\n className: classNames(\n buttonCn({ isFocusVisible }),\n {\n [opacityNoneCn]: isDropTarget && !isDisabled,\n },\n pickerButtonClassName,\n ),\n style: pickerButtonStyle,\n } as ActionButtonProps;\n\n const dataAttrs = filterTruthyValues({\n \"data-loaded\": isLoaded,\n \"data-block-id\": dataBlockId,\n \"data-block-class\": dataBlockClass,\n \"data-hovered\": isHovered,\n \"data-focused\": isFocused,\n \"data-drop-target\": isDropTarget,\n \"data-disabled\": isDisabled,\n \"data-focus-visible\": isFocusVisible,\n });\n\n return (\n <Box\n {...dataAttrs}\n className={classNames(\"BaselineUI-ImageDropZone\", className)}\n display=\"flex\"\n flexDirection=\"column\"\n alignItems=\"center\"\n style={style}\n ref={ref}\n >\n <label\n {...mergeProps(dropProps, hoverProps, focusProps, labelProps)}\n className={classNames(\n imageDropZoneCn({\n isInline,\n isHovered,\n isDisabled,\n hasLoadedImage: !!_url && isLoaded,\n showFocusRing: isDropTarget || (!!_url && isFocusVisible),\n }),\n labelClassName,\n )}\n style={labelStyle}\n ref={elementRef}\n >\n <VisuallyHidden>\n <input\n {...mergeProps(fieldProps, clipboardProps, inputProps)}\n ref={inputRef}\n />\n </VisuallyHidden>\n\n {!_url && <ActionButton {...chooseFileButtonProps} />}\n\n {!!_url &&\n (isLoading ? (\n <ProgressSpinner />\n ) : (\n <img\n className={previewCn({\n isInline,\n isDisabled,\n hasLoadedImage:\n !!_url && isLoaded && includeCheckeredBackground,\n })}\n {...imgProps}\n />\n ))}\n </label>\n\n {placeholder || clearLabel ? (\n <div\n style={footerStyle}\n className={classNames(\n freehandCanvasFooterCn({ isInline, isDisabled }),\n footerClassName,\n )}\n >\n {(!!_url || !placeholder) && clearLabel ? (\n <ActionButton\n size=\"sm\"\n variant=\"ghost\"\n label={clearLabel}\n isDisabled={isDisabled}\n onPress={reset}\n />\n ) : (\n <ActionButton\n size=\"sm\"\n variant=\"ghost\"\n label={placeholder as string}\n isDisabled={true}\n />\n )}\n </div>\n ) : null}\n </Box>\n );\n },\n);\n\nImageDropZone.displayName = \"ImageDropZone\";\n\nfunction getImageSrc(imageSrc: string | File | undefined) {\n if (imageSrc
|
|
30
|
+
"ImageDropZone": "import { useControlledState } from \"@react-stately/utils\";\nimport React, { useMemo } from \"react\";\nimport {\n VisuallyHidden,\n mergeProps,\n useClipboard,\n useDrop,\n useField,\n useFocusRing,\n useHover,\n} from \"react-aria\";\n\nimport messages from \"./intl\";\nimport { classNames, filterTruthyValues, invariant } from \"../../utils\";\nimport { buttonCn } from \"../ActionButton/ActionButton.css\";\nimport { Box } from \"../Box\";\nimport { freehandCanvasFooterCn } from \"../FreehandCanvas/FreehandCanvas.css\";\nimport { ProgressSpinner } from \"../ProgressSpinner\";\nimport { onDropOrPaste } from \"../FileUpload/hooks/useFileUpload\";\nimport { imageDropZoneCn, opacityNoneCn, previewCn } from \"./ImageDropZone.css\";\nimport { ActionButton } from \"../ActionButton\";\nimport { useI18n, useImage } from \"../../hooks\";\n\nimport type { ActionButtonProps } from \"../ActionButton\";\nimport type { ChangeEventHandler } from \"react\";\nimport type { ImageDropZoneProps } from \"./ImageDropZone.types\";\n\nexport const ImageDropZone = React.forwardRef<\n HTMLDivElement,\n ImageDropZoneProps\n>(\n (\n {\n className,\n style,\n isInline = true,\n accept = \"image/*\",\n defaultImageSrc,\n imageSrc,\n imageAlt,\n pickerButtonLabel,\n isDisabled,\n onDrop,\n onPaste,\n includeCheckeredBackground = true,\n \"data-block-id\": dataBlockId,\n \"data-block-class\": dataBlockClass,\n pickerButtonStyle,\n pickerButtonClassName,\n footerClassName,\n footerStyle,\n clearLabel,\n placeholder,\n labelStyle,\n labelClassName,\n ...rest\n },\n ref,\n ) => {\n const elementRef = React.useRef<HTMLLabelElement>(null);\n const inputRef = React.useRef<HTMLInputElement>(null);\n const { formatMessage } = useI18n(messages);\n\n const [url, setUrl] = useControlledState(\n imageSrc,\n defaultImageSrc || null,\n (value) => {\n invariant(typeof value !== \"string\");\n\n rest.onValueChange?.(value ? [value] : []);\n },\n );\n\n const _url = useMemo(() => getImageSrc(url || undefined), [url]);\n\n const { fieldProps, labelProps } = useField({\n ...rest,\n ref: elementRef,\n });\n\n const { dropProps, isDropTarget } = useDrop({\n ref: elementRef,\n onDrop: async (e) => {\n if (isDisabled) return;\n const files = await onDropOrPaste({ items: e.items });\n if (files.length) {\n setUrl(files[0]);\n }\n onDrop?.(e);\n },\n ...rest,\n });\n\n const { clipboardProps } = useClipboard({\n onPaste: async (items) => {\n if (isDisabled) return;\n const files = await onDropOrPaste({ items });\n if (files.length) {\n setUrl(files[0]);\n }\n onPaste?.(items);\n },\n ...rest,\n });\n\n const reset = () => {\n setUrl(null);\n if (inputRef.current) {\n inputRef.current.value = \"\";\n }\n };\n\n const inputProps = {\n type: \"file\",\n onChange: ((e) => {\n const files = [...(e.target.files as FileList)];\n setUrl(files[0]);\n\n rest.onChange?.(e);\n }) as ChangeEventHandler<HTMLInputElement>,\n accept,\n disabled: isDisabled,\n } as React.ComponentProps<\"input\">;\n\n const { focusProps, isFocused, isFocusVisible } = useFocusRing({\n within: true,\n });\n const { hoverProps, isHovered } = useHover({ isDisabled });\n\n const { imgProps, isLoading, isLoaded } = useImage({\n src: _url,\n alt: imageAlt || \"\",\n });\n\n const chooseFileButtonProps = {\n label: pickerButtonLabel || formatMessage(\"selectImage\"),\n onPress: () => elementRef.current?.click(),\n variant: \"secondary\",\n isDisabled,\n excludeFromTabOrder: true,\n className: classNames(\n buttonCn({ isFocusVisible }),\n {\n [opacityNoneCn]: isDropTarget && !isDisabled,\n },\n pickerButtonClassName,\n ),\n style: pickerButtonStyle,\n } as ActionButtonProps;\n\n const dataAttrs = filterTruthyValues({\n \"data-loaded\": isLoaded,\n \"data-block-id\": dataBlockId,\n \"data-block-class\": dataBlockClass,\n \"data-hovered\": isHovered,\n \"data-focused\": isFocused,\n \"data-drop-target\": isDropTarget,\n \"data-disabled\": isDisabled,\n \"data-focus-visible\": isFocusVisible,\n });\n\n return (\n <Box\n {...dataAttrs}\n className={classNames(\"BaselineUI-ImageDropZone\", className)}\n display=\"flex\"\n flexDirection=\"column\"\n alignItems=\"center\"\n style={style}\n ref={ref}\n >\n <label\n {...mergeProps(dropProps, hoverProps, focusProps, labelProps)}\n className={classNames(\n imageDropZoneCn({\n isInline,\n isHovered,\n isDisabled,\n hasLoadedImage: !!_url && isLoaded,\n showFocusRing: isDropTarget || (!!_url && isFocusVisible),\n }),\n labelClassName,\n )}\n style={labelStyle}\n ref={elementRef}\n >\n <VisuallyHidden>\n <input\n {...mergeProps(fieldProps, clipboardProps, inputProps)}\n ref={inputRef}\n />\n </VisuallyHidden>\n\n {!_url && <ActionButton {...chooseFileButtonProps} />}\n\n {!!_url &&\n (isLoading ? (\n <ProgressSpinner />\n ) : (\n <img\n className={previewCn({\n isInline,\n isDisabled,\n hasLoadedImage:\n !!_url && isLoaded && includeCheckeredBackground,\n })}\n {...imgProps}\n />\n ))}\n </label>\n\n {placeholder || clearLabel ? (\n <div\n style={footerStyle}\n className={classNames(\n freehandCanvasFooterCn({ isInline, isDisabled }),\n footerClassName,\n )}\n >\n {(!!_url || !placeholder) && clearLabel ? (\n <ActionButton\n size=\"sm\"\n variant=\"ghost\"\n label={clearLabel}\n isDisabled={isDisabled}\n onPress={reset}\n />\n ) : (\n <ActionButton\n size=\"sm\"\n variant=\"ghost\"\n label={placeholder as string}\n isDisabled={true}\n />\n )}\n </div>\n ) : null}\n </Box>\n );\n },\n);\n\nImageDropZone.displayName = \"ImageDropZone\";\n\nfunction getImageSrc(imageSrc: string | File | undefined) {\n if (!imageSrc || typeof imageSrc === \"string\") {\n return imageSrc;\n }\n\n return URL.createObjectURL(imageSrc);\n}\n",
|
|
31
31
|
"ImageGallery": "import { useControlledState } from \"@react-stately/utils\";\nimport React, { useCallback, useContext, useEffect, useState } from \"react\";\nimport { mergeRefs } from \"@react-aria/utils\";\nimport {\n CollectionRendererContext,\n DropIndicator,\n} from \"react-aria-components\";\nimport { sprinkles } from \"@baseline-ui/tokens\";\n\nimport { classNames } from \"../../utils\";\nimport { defineMessages, useI18n } from \"../../hooks\";\nimport { AlertDialog } from \"../AlertDialog\";\nimport { UNSAFE_ListBox as ListBox } from \"../UNSAFE_ListBox\";\nimport { Modal, ModalContent } from \"../Modal\";\nimport { DragPreviewContent } from \"../shared/components/DragPreviewContent\";\nimport {\n dropIndicatorCn,\n imageGalleryCn,\n imageGalleryOptionCn,\n} from \"./ImageGallery.css\";\nimport { ImageGalleryItem } from \"./ImageGalleryItem\";\nimport {\n getByKey,\n imageItemsToListOption,\n listOptionToImageItems,\n moveAfter,\n moveBefore,\n} from \"./utils\";\nimport { getDynamicClassNameForCollectionItem } from \"../../utils/style\";\nimport { useDragAndDrop } from \"../ListBox/hooks/useDragAndDrop\";\n\nimport type { Key } from \"react-aria\";\nimport type { UNSAFE_ListBoxProps as ListBoxProps } from \"../UNSAFE_ListBox\";\nimport type { DroppableCollectionReorderEvent } from \"@react-types/shared\";\nimport type { ImageGalleryProps } from \"./ImageGallery.types\";\n\nconst Image = {\n src: \"src\",\n alt: \"alt\",\n} as const;\n\nexport const ImageSize = {\n sm: 102,\n md: 170,\n} as const;\n\nexport const ImageGallery = React.forwardRef<HTMLDivElement, ImageGalleryProps>(\n (\n {\n className,\n style,\n fit = \"cover\",\n onReorder,\n \"data-block-id\": dataBlockId,\n \"data-block-class\": dataBlockClass,\n onDelete,\n imageWidth = ImageSize.sm,\n aspectRatio = 3 / 4,\n items,\n defaultItems = [],\n onKeyDown,\n imageContainerClassName,\n imageClassName,\n labelClassName,\n onListChange,\n renderImage,\n layoutTransition,\n imageDimensions = {\n width: imageWidth,\n aspectRatio,\n },\n imageContainerStyle,\n deleteConfirmationTitle = \"Are you sure you want to delete this item?\",\n ...rest\n },\n ref,\n ) => {\n const { isVirtualized } = useContext(CollectionRendererContext);\n const divRef = React.useRef<HTMLDivElement>(null);\n const [itemsToDelete, setItemsToDelete] = useState<Key[]>([]);\n const { formatMessage } = useI18n();\n\n useEffect(() => {\n if (aspectRatio || imageWidth) {\n console.warn(\n \"ImageGallery: aspectRatio and imageWidth are deprecated. Use imageDimensions instead.\",\n );\n }\n }, [imageWidth, aspectRatio]);\n\n const [selectedKeys, setSelectedKeys] = useControlledState(\n rest.selectedKeys,\n rest.defaultSelectedKeys ?? [],\n rest.onSelectionChange,\n );\n\n const [list, setList] = useControlledState(\n items ? imageItemsToListOption(items) : undefined,\n imageItemsToListOption(defaultItems),\n onListChange\n ? (_items) => {\n onListChange?.(listOptionToImageItems(_items));\n }\n : undefined,\n );\n\n const _onReorder = useCallback(\n (e: DroppableCollectionReorderEvent) => {\n const referenceKey = e.target.key;\n const keysToMove = e.keys;\n\n if (e.target.dropPosition === \"before\") {\n const newList = moveBefore(list, referenceKey, keysToMove);\n setList(newList);\n } else if (e.target.dropPosition === \"after\") {\n const newList = moveAfter(list, referenceKey, keysToMove);\n setList(newList);\n }\n onReorder?.(e);\n },\n [list, onReorder, setList],\n );\n\n const { dragAndDropHooks } = useDragAndDrop({\n onReorder: _onReorder,\n isDisabled: !onReorder,\n renderDragPreview: (items) => (\n <DragPreviewContent\n items={items}\n containerClassName=\".BaselineUI-ImageGallery\"\n previewClassName={imageGalleryOptionCn()}\n itemDataAttribute=\"data-image-key\"\n showCount={true}\n />\n ),\n getItems: (keys) =>\n [...keys].map((key) => {\n const item = getByKey(list, key);\n\n return {\n [Image.src]: item?.src || \"\",\n [Image.alt]: item?.alt || \"\",\n key: key as string,\n };\n }),\n renderDropIndicator(target) {\n return (\n <DropIndicator\n target={target}\n className={classNames(\n dropIndicatorCn({ isVirtualized: !!isVirtualized }),\n \"BaselineUI-DropIndicator\",\n )}\n />\n );\n },\n });\n\n const _onDelete = useCallback(() => {\n if (!onDelete) return;\n\n if (itemsToDelete.length) {\n setList(list.filter((item) => !itemsToDelete.includes(item.id)));\n\n onDelete(itemsToDelete);\n setItemsToDelete([]);\n }\n }, [itemsToDelete, list, onDelete, setList]);\n\n const renderOption = useCallback<\n Exclude<ListBoxProps[\"renderOption\"], undefined>\n >(\n (item, options) => {\n const galleryItem = {\n id: item.id,\n label: item.label,\n src: item.data?.src,\n alt: item.data?.alt,\n };\n\n return (\n <ImageGalleryItem\n key={item.id}\n item={item}\n options={options}\n fit={fit}\n onDelete={\n onDelete\n ? (id: string) => {\n setItemsToDelete([id]);\n }\n : undefined\n }\n renderImage={renderImage}\n className={getDynamicClassNameForCollectionItem(\n imageContainerClassName,\n galleryItem,\n options,\n )}\n imageClassName={imageClassName}\n imageLabelClassName={labelClassName}\n imageDimensions={imageDimensions}\n style={imageContainerStyle}\n layoutTransition={layoutTransition}\n />\n );\n },\n [\n fit,\n onDelete,\n renderImage,\n imageContainerClassName,\n imageClassName,\n labelClassName,\n imageDimensions,\n imageContainerStyle,\n layoutTransition,\n ],\n );\n\n useEffect(() => {\n const container = divRef.current;\n if (!container) return;\n\n const handleKeyDown = (e: KeyboardEvent) => {\n onKeyDown?.(e);\n\n if (e.key === \"Backspace\" || e.key === \"Delete\") {\n if (!onDelete) return;\n\n const _selectedKeys = [...selectedKeys];\n // If there are selected items, delete them\n // Otherwise, delete the item that was focused\n if (_selectedKeys.length) {\n setItemsToDelete([...selectedKeys]);\n } else {\n const key = (e.target as HTMLUListElement).dataset.key as string;\n setItemsToDelete([key]);\n }\n }\n };\n\n container.addEventListener(\"keydown\", handleKeyDown);\n\n return () => {\n container.removeEventListener(\"keydown\", handleKeyDown);\n };\n }, [onDelete, onKeyDown, selectedKeys]);\n\n return (\n <>\n <ListBox\n {...rest}\n ref={mergeRefs(ref, divRef)}\n style={style}\n className={classNames(\n imageGalleryCn,\n \"BaselineUI-ImageGallery\",\n className,\n )}\n data-block-id={dataBlockId}\n data-block-class={dataBlockClass}\n selectedKeys={selectedKeys}\n items={list}\n dragAndDropHooks={dragAndDropHooks}\n layout=\"grid\"\n orientation=\"vertical\"\n renderOption={renderOption}\n onSelectionChange={setSelectedKeys}\n optionClassName={\n onReorder && !isVirtualized\n ? sprinkles({\n marginLeft: \"sm\",\n })\n : undefined\n }\n />\n <Modal\n isOpen={!![...itemsToDelete].length}\n onOpenChange={(open) => {\n if (!open) {\n setItemsToDelete([]);\n }\n }}\n >\n <ModalContent isDismissable={true}>\n <AlertDialog\n showCloseButton={false}\n title={\n typeof deleteConfirmationTitle === \"function\"\n ? deleteConfirmationTitle(\n selectedKeys === \"all\" ? \"all\" : new Set(selectedKeys),\n )\n : deleteConfirmationTitle\n }\n primaryActionLabel={formatMessage(messages.delete)}\n cancelLabel={formatMessage(messages.cancel)}\n onCancel={() => {\n setItemsToDelete([]);\n }}\n onPrimaryAction={_onDelete}\n autoFocusButton=\"cancel\"\n />\n </ModalContent>\n </Modal>\n </>\n );\n },\n);\n\nImageGallery.displayName = \"ImageGallery\";\n\nexport const messages = defineMessages({\n delete: {\n id: \"delete\",\n defaultMessage: \"Delete\",\n },\n cancel: {\n id: \"cancel\",\n defaultMessage: \"Cancel\",\n },\n loading: {\n id: \"loading\",\n defaultMessage: \"Loading\",\n },\n});\n",
|
|
32
32
|
"InlineAlert": "import {\n CheckmarkCircleFilledIcon,\n ErrorAltCircleFilledIcon,\n InfoCircleFilledIcon,\n WarningFilledIcon,\n XIcon,\n} from \"@baseline-ui/icons/16\";\nimport {\n CheckCircleFilledIcon as CheckFilledIcon20,\n ErrorAltCircleFilledIcon as ErrorAltFilledIcon20,\n InfoCircleFilledIcon as InfoFilledIcon20,\n WarningFilledIcon as WarningFilledIcon20,\n} from \"@baseline-ui/icons/20\";\nimport React from \"react\";\n\nimport messages from \"./intl\";\nimport { classNames } from \"../../utils\";\nimport { ActionButton, ActionIconButton } from \"../\";\nimport {\n contentCn,\n inlineAlertCn,\n inlineAlertDescriptionCn,\n inlineAlertEndCn,\n inlineAlertIconCn,\n inlineAlertTitleCn,\n inlineStartCn,\n} from \"./InlineAlert.css\";\nimport { useI18n } from \"../../hooks\";\n\nimport type { InlineAlertProps } from \"./InlineAlert.types\";\nimport type Messages from \"./intl/en.json\";\n\nconst icons = {\n error: { sm: ErrorAltCircleFilledIcon, md: ErrorAltFilledIcon20 },\n info: { sm: InfoCircleFilledIcon, md: InfoFilledIcon20 },\n success: { sm: CheckmarkCircleFilledIcon, md: CheckFilledIcon20 },\n warning: { sm: WarningFilledIcon, md: WarningFilledIcon20 },\n};\n\nexport const InlineAlert = React.forwardRef<HTMLDivElement, InlineAlertProps>(\n (\n {\n className,\n description,\n actionLabel,\n onAction,\n style,\n variant = \"info\",\n title,\n arrangement = \"single\",\n onClose,\n size = \"sm\",\n \"data-block-id\": dataBlockId,\n \"data-block-class\": dataBlockClass,\n elementProps,\n },\n ref,\n ) => {\n const intl = useI18n<typeof Messages>(messages);\n\n const actionButton = actionLabel && (\n <ActionButton\n variant={arrangement === \"compact\" ? \"tertiary\" : \"ghost\"}\n label={actionLabel}\n onPress={onAction}\n />\n );\n\n const Icon = icons[variant][size];\n\n return (\n <div\n className={classNames(\n inlineAlertCn({\n variant,\n arrangement,\n hasButton: !!actionLabel || !!onClose,\n hasCloseButton: !!onClose,\n }),\n \"BaselineUI-InlineAlert\",\n className,\n )}\n data-block-id={dataBlockId}\n data-block-class={dataBlockClass}\n style={style}\n role=\"alert\"\n ref={ref}\n {...elementProps?.root}\n >\n <div className={inlineStartCn({ arrangement })}>\n <Icon className={inlineAlertIconCn({ variant })} size={16} />\n\n <div\n className={contentCn({ arrangement })}\n {...elementProps?.content}\n >\n <h3\n className={inlineAlertTitleCn({ size })}\n {...elementProps?.title}\n >\n {title}\n </h3>\n <section\n className={inlineAlertDescriptionCn({ size })}\n {...elementProps?.description}\n >\n {description}\n </section>\n </div>\n\n {arrangement === \"compact\" && actionButton}\n </div>\n\n <div className={inlineAlertEndCn({ arrangement })}>\n {arrangement !== \"compact\" && actionButton}\n {onClose ? (\n <ActionIconButton\n variant=\"secondary\"\n icon={XIcon}\n size=\"sm\"\n onPress={onClose}\n aria-label={intl.formatMessage(\"close\")}\n {...elementProps?.close}\n />\n ) : null}\n </div>\n </div>\n );\n },\n);\n\nInlineAlert.displayName = \"InlineAlert\";\n",
|
|
33
33
|
"InlineToolbar": "import { themeVars } from \"@baseline-ui/tokens\";\nimport {\n getActiveElement,\n getOwnerDocument,\n getOwnerWindow,\n mergeRefs,\n nodeContains,\n useViewportSize,\n} from \"@react-aria/utils\";\nimport { useControlledState } from \"@react-stately/utils\";\nimport { useGranularEffect, useGranularLayoutEffect } from \"granular-hooks\";\nimport React, {\n Fragment,\n useCallback,\n useEffect,\n useLayoutEffect,\n useMemo,\n} from \"react\";\nimport {\n FocusScope,\n mergeProps,\n useInteractOutside,\n useKeyboard,\n useOverlayPosition,\n} from \"react-aria\";\nimport { useOverlayTriggerState } from \"react-stately\";\nimport { useToolbar } from \"@react-aria/toolbar\";\n\nimport { classNames, findFocusableElements, invariant } from \"../../utils\";\nimport { arrowInlineCn, inlineToolbarContentCn } from \"./InlineToolbar.css\";\nimport { InlineToolbarButton } from \"./InlineToolbarButton\";\nimport { Portal } from \"../Portal\";\nimport { Separator } from \"../Separator\";\nimport { useTextSelection } from \"../../hooks\";\n\nimport type { InlineToolbarProps, Item } from \"./InlineToolbar.types\";\n\nexport const InlineToolbar = React.forwardRef<\n HTMLDivElement,\n InlineToolbarProps\n>(\n (\n {\n className,\n onAction,\n items,\n scrollRef,\n placement = \"top\",\n children,\n \"data-block-id\": dataBlockId,\n \"data-block-class\": dataBlockClass,\n disabledKeys,\n isDisabled,\n anchorRect,\n ...rest\n },\n ref,\n ) => {\n const divRef = React.useRef<HTMLDivElement>(null);\n const overlayRef = React.useRef<HTMLDivElement>(null);\n const triggerRef = React.useRef<HTMLDivElement>(null);\n const selectionRef = React.useRef<Selection | null>(null);\n const [selectionRect, setSelectionRect] = useControlledState(\n anchorRect,\n null,\n );\n\n const state = useOverlayTriggerState(rest);\n\n const {\n overlayProps,\n arrowProps,\n placement: placementAxis,\n updatePosition,\n } = useOverlayPosition({\n arrowSize: 13,\n targetRef: triggerRef,\n arrowBoundaryOffset: 8,\n offset: 12,\n overlayRef,\n isOpen: state.isOpen,\n placement,\n scrollRef,\n ...rest,\n });\n\n const size = useViewportSize();\n\n useGranularEffect(\n () => {\n if (selectionRef.current) {\n setSelectionRect(\n selectionRef.current.getRangeAt(0).getBoundingClientRect(),\n );\n }\n },\n [size],\n [setSelectionRect],\n );\n\n const toolbarRef = React.useRef<HTMLDivElement>(null);\n\n const { toolbarProps } = useToolbar(\n {\n ...rest,\n isSingleTabStop: false,\n },\n toolbarRef,\n );\n\n useInteractOutside({\n ref: toolbarRef,\n onInteractOutside() {\n state.close();\n setSelectionRect(null);\n },\n });\n\n useEffect(() => {\n if (isDisabled && state.isOpen) {\n state.close();\n }\n }, [isDisabled, state]);\n\n useTextSelection({\n ref: divRef,\n isDisabled,\n onSelectionChange: (selection) => {\n if (selection.isCollapsed) {\n selectionRef.current = null;\n setSelectionRect(null);\n } else {\n selectionRef.current = selection;\n\n setSelectionRect(selection.getRangeAt(0).getBoundingClientRect());\n }\n },\n });\n\n const handleScroll = useCallback(() => {\n const selection = selectionRef.current;\n if (selection) {\n setSelectionRect(selection.getRangeAt(0).getBoundingClientRect());\n }\n }, [setSelectionRect]);\n\n useGranularEffect(\n () => {\n const ownerDocument = getOwnerDocument(divRef.current);\n\n const _scrollRef = scrollRef?.current ?? ownerDocument.body;\n\n _scrollRef?.addEventListener(\"scroll\", handleScroll);\n\n return () => {\n _scrollRef?.removeEventListener(\"scroll\", handleScroll);\n };\n },\n [scrollRef],\n [updatePosition],\n );\n\n useLayoutEffect(() => {\n if (selectionRect && !state.isOpen) {\n state.open();\n } else if (!selectionRect) {\n state.close();\n }\n }, [selectionRect, state]);\n\n useGranularLayoutEffect(updatePosition, [selectionRect], [updatePosition]);\n\n const _placement =\n placementAxis === \"center\" ? \"top\" : placementAxis || \"top\";\n\n const { keyboardProps } = useKeyboard({\n onKeyDown(e) {\n if (e.key === \"Escape\") {\n state.close();\n setSelectionRect(null);\n } else {\n e.continuePropagation();\n }\n },\n });\n\n const handleToolbarPress = useCallback(\n (item: Item) => {\n invariant(selectionRef.current, \"No selection\");\n onAction?.(item.id, selectionRef.current);\n setSelectionRect(null);\n selectionRef.current?.removeAllRanges();\n },\n [onAction, setSelectionRect],\n );\n\n const _items =\n typeof items === \"function\" ? items(selectionRef.current) : items;\n\n const overlayPositionStyle = useMemo<React.CSSProperties>(() => {\n const ownerWindow = getOwnerWindow(triggerRef.current);\n\n return {\n position: \"absolute\",\n top: (selectionRect?.top || 0) + ownerWindow.scrollY,\n left: (selectionRect?.left || 0) + ownerWindow.scrollX,\n width: selectionRect?.width,\n height: selectionRect?.height,\n visibility: \"hidden\",\n };\n }, [selectionRect]);\n\n const _disabledKeys =\n typeof disabledKeys === \"function\"\n ? disabledKeys(selectionRef.current)\n : disabledKeys;\n\n useEffect(\n function moveFocusToToolbarOnTab() {\n if (!state.isOpen) return;\n\n const ownerDocument = getOwnerDocument(divRef.current);\n\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"Tab\" && state.isOpen && toolbarRef.current) {\n const focusableEls = findFocusableElements(toolbarRef.current);\n\n const first = focusableEls[0] as HTMLElement;\n\n if (!first) return;\n\n const activeElement = getActiveElement(ownerDocument);\n\n const isCurrentlyFocusedElementOutsideToolbar = !nodeContains(\n toolbarRef.current,\n activeElement,\n );\n\n if (isCurrentlyFocusedElementOutsideToolbar) {\n e.preventDefault();\n first.focus();\n }\n }\n };\n\n ownerDocument.addEventListener(\"keydown\", handleKeyDown);\n\n return () => {\n ownerDocument.removeEventListener(\"keydown\", handleKeyDown);\n };\n },\n [state.isOpen],\n );\n\n return (\n <>\n {React.cloneElement(children, { ref: divRef })}\n\n <Portal>\n <div ref={mergeRefs(triggerRef, ref)} style={overlayPositionStyle} />\n </Portal>\n\n {state.isOpen && selectionRect ? (\n <div\n {...overlayProps}\n ref={overlayRef}\n data-block-id={dataBlockId}\n data-block-class={dataBlockClass}\n className=\"BaselineUI-InlineToolbar\"\n >\n <FocusScope contain={true} restoreFocus={true}>\n <div\n className={classNames(inlineToolbarContentCn, className)}\n {...mergeProps(toolbarProps, keyboardProps)}\n aria-keyshortcuts=\"Tab Shift+Tab ArrowRight ArrowLeft\"\n ref={toolbarRef}\n >\n {_items.map((item, i) => {\n const Icon = item.icon;\n\n return (\n <Fragment key={item.id}>\n <InlineToolbarButton\n onPress={() => {\n handleToolbarPress(item);\n }}\n aria-label={item[\"aria-label\"]}\n isDisabled={new Set(_disabledKeys).has(item.id)}\n >\n {item.label || (Icon && <Icon size={24} />)}\n </InlineToolbarButton>\n {i !== items.length - 1 && (\n <Separator\n orientation=\"vertical\"\n variant=\"secondary\"\n style={{\n backgroundColor: themeVars.color.border.strong,\n }}\n />\n )}\n </Fragment>\n );\n })}\n </div>\n </FocusScope>\n\n <div\n {...arrowProps}\n className={arrowInlineCn({ placement: _placement })}\n />\n </div>\n ) : null}\n </>\n );\n },\n);\n\nInlineToolbar.displayName = \"InlineToolbar\";\n",
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Baseline UI MCP Server Guidelines
|
|
2
|
+
|
|
3
|
+
This MCP server provides AI assistants with access to Baseline UI's component documentation, icons, and design resources.
|
|
4
|
+
|
|
5
|
+
- Baseline UI is a design system for building accessible and consistent user interfaces.
|
|
6
|
+
- It is built with React and TypeScript and styled with Vanilla Extract.
|
|
7
|
+
- It is also known as BUI.
|
|
8
|
+
- It is a client side design system that depends on global variables like document, window, etc.
|
|
9
|
+
|
|
10
|
+
## Available Tools
|
|
11
|
+
|
|
12
|
+
### Component Documentation
|
|
13
|
+
|
|
14
|
+
**`list_baseline_components`**
|
|
15
|
+
|
|
16
|
+
- Lists all available components in the Baseline UI design system
|
|
17
|
+
- Optional parameter: `includeDescription` (boolean) - includes component descriptions
|
|
18
|
+
- Use this when you need to discover what components are available
|
|
19
|
+
|
|
20
|
+
**`get_baseline_component_info`**
|
|
21
|
+
|
|
22
|
+
- Returns high-level component information: name, description, and available documentation sections
|
|
23
|
+
- Requires: `componentName` (string)
|
|
24
|
+
- Use this before diving into detailed documentation to understand what sections are available
|
|
25
|
+
|
|
26
|
+
**`get_baseline_component_docs`**
|
|
27
|
+
|
|
28
|
+
- Returns complete or section-specific component documentation in markdown format
|
|
29
|
+
- Requires: `componentName` (string)
|
|
30
|
+
- Optional: `sectionName` (string) - get a specific section (e.g., "Usage", "Examples")
|
|
31
|
+
- Use this to read component documentation, examples, and usage patterns
|
|
32
|
+
|
|
33
|
+
**`get_baseline_component_types`**
|
|
34
|
+
|
|
35
|
+
- Returns TypeScript type definitions and prop interfaces for a component
|
|
36
|
+
- Requires: `componentName` (string)
|
|
37
|
+
- Use this when you need to understand component props, their types, and constraints
|
|
38
|
+
|
|
39
|
+
**`get_baseline_component_source`**
|
|
40
|
+
|
|
41
|
+
- Returns the source code implementation of a component
|
|
42
|
+
- Requires: `componentName` (string)
|
|
43
|
+
- Use this to understand how a component is implemented
|
|
44
|
+
|
|
45
|
+
### Icon System
|
|
46
|
+
|
|
47
|
+
**`search_baseline_icons`**
|
|
48
|
+
|
|
49
|
+
- Searches the Baseline UI icon set by name
|
|
50
|
+
- Requires: `terms` (string or array of strings)
|
|
51
|
+
- Returns icon names matching the search terms
|
|
52
|
+
- Import syntax: `import { IconName } from '@baseline-ui/icons/[size]'`
|
|
53
|
+
|
|
54
|
+
### Getting Started
|
|
55
|
+
|
|
56
|
+
**`get_baseline_getting_started`**
|
|
57
|
+
|
|
58
|
+
- Returns complete setup and installation guide for Baseline UI
|
|
59
|
+
- Use this when helping users set up Baseline UI in a new project
|
|
60
|
+
- Always use this for setup-related questions or issues
|
|
61
|
+
|
|
62
|
+
## Recommended Workflow
|
|
63
|
+
|
|
64
|
+
1. **For component discovery**: Use `list_baseline_components` to see available components
|
|
65
|
+
2. **For understanding a component**: Use `get_baseline_component_info` to see available sections
|
|
66
|
+
3. **For detailed documentation**: Use `get_baseline_component_docs` to read full documentation or specific sections
|
|
67
|
+
4. **For implementation details**: Use `get_baseline_component_types` and `get_baseline_component_source`
|
|
68
|
+
5. **For icon selection**: Use `search_baseline_icons` with relevant terms
|
|
69
|
+
6. **For setup**: Use `get_baseline_getting_started` for new projects or setup issues
|
|
70
|
+
|
|
71
|
+
## Styling Guidelines
|
|
72
|
+
|
|
73
|
+
When recommending styling or customization approaches:
|
|
74
|
+
|
|
75
|
+
- **Always prioritize using sprinkles from `@baseline-ui/tokens`** for styling
|
|
76
|
+
- Sprinkles provides a type-safe, zero-runtime CSS utility layer built with Vanilla Extract
|
|
77
|
+
- Use sprinkles for spacing, colors, typography, shadows, and other design tokens
|
|
78
|
+
- Example: `<div className={sprinkles({ padding: 'md', color: 'text.primary' })}>Content</div>`
|
|
79
|
+
- Only use custom CSS when sprinkles doesn't provide the necessary utilities
|
|
80
|
+
- This ensures consistency with the design system and maintains design token governance
|
|
81
|
+
|
|
82
|
+
## Tips for Better Results
|
|
83
|
+
|
|
84
|
+
- When presenting component information to users, format tool results as readable lists or tables, not raw JSON
|
|
85
|
+
- For component documentation, render markdown content with proper formatting and syntax highlighting
|
|
86
|
+
- When suggesting components, mention available sections that users can explore
|
|
87
|
+
- For props and types, highlight required vs optional properties and their constraints
|
|
88
|
+
- When searching icons, try multiple terms if the first search doesn't yield results (e.g., "search", "find", "magnifying")
|
package/dist/index.js
CHANGED
|
@@ -164,6 +164,26 @@ async function startServer() {
|
|
|
164
164
|
name: "baseline-ui-docs-server",
|
|
165
165
|
version: packageJson.version,
|
|
166
166
|
});
|
|
167
|
+
// Register guidelines resource
|
|
168
|
+
server.registerResource("guidelines", "resource://baseline-ui/guidelines.md", {
|
|
169
|
+
description: "Guidelines for using the Baseline UI MCP server",
|
|
170
|
+
mimeType: "text/markdown",
|
|
171
|
+
}, () => {
|
|
172
|
+
const guidelinesPath = path.resolve(__dirname, "guidelines.md");
|
|
173
|
+
if (!fs.existsSync(guidelinesPath)) {
|
|
174
|
+
throw new Error("Guidelines file not found");
|
|
175
|
+
}
|
|
176
|
+
const content = fs.readFileSync(guidelinesPath, "utf8");
|
|
177
|
+
return {
|
|
178
|
+
contents: [
|
|
179
|
+
{
|
|
180
|
+
uri: "resource://baseline-ui/guidelines.md",
|
|
181
|
+
mimeType: "text/markdown",
|
|
182
|
+
text: content,
|
|
183
|
+
},
|
|
184
|
+
],
|
|
185
|
+
};
|
|
186
|
+
});
|
|
167
187
|
// List components tool
|
|
168
188
|
server.registerTool("list_baseline_components", {
|
|
169
189
|
title: "List Baseline UI components",
|
package/package.json
CHANGED