UNPKG

strapi-plugin-tagsinput

Version:
332 lines (319 loc) 9.04 kB
import { jsx, Fragment, jsxs } from "react/jsx-runtime"; import { useRef, useState, useEffect } from "react"; import PropTypes from "prop-types"; import axios from "axios"; import { Field, Flex } from "@strapi/design-system"; import { useIntl } from "react-intl"; import TagsInput from "react-tagsinput"; import Autosuggest from "react-autosuggest"; import { css, createGlobalStyle } from "styled-components"; const light = css` :root { --primary: #7b79ff; --secondary: rgb(255, 255, 255); --text: #32324d; --input-background: #ffffff; --input-border: #dcdce4; --tag-background: #f0f0ff; --tag-text: #4945ff; --suggestion-background: #ffffff; --suggestion-hover: #f6f6f9; } `; const dark = css` :root { --primary: #7b79ff; --secondary: rgb(33, 33, 52); --text: #ffffff; --input-background: #181826; --input-border: #4a4a6a; --tag-background: #7b79ff; --tag-text: #ffffff; --suggestion-background: #181826; --suggestion-hover: #212134; } `; const styles = css` .react-tagsinput { width: 100%; border: 1px solid var(--input-border); border-radius: 4px; overflow: hidden; padding-left: 5px; padding-top: 5px; } .react-tagsinput--focused { outline: 3px solid var(--primary); } .react-tagsinput-tag { background-color: var(--tag-background); border-radius: 2px; border: 1px solid var(--tag-background); color: var(--tag-text); display: inline-block; font-family: sans-serif; font-size: 13px; font-weight: 400; margin-bottom: 5px; margin-right: 5px; padding: 5px; } .react-tagsinput-remove { cursor: pointer; font-weight: bold; } .react-tagsinput-tag a::before { content: " ×"; } .react-tagsinput-input { background: transparent; border: 0; color: var(--text); font-family: sans-serif; font-size: 13px; font-weight: 400; margin-bottom: 6px; margin-top: 1px; outline: none; padding: 5px; width: 100%; } .react-tagsinput > span { display: flex; flex-flow: wrap; } .react-autosuggest__container { display: flex; flex-direction: column; flex: auto; } .react-autosuggest__suggestions-container { position: absolute; z-index: 200; width: 280px; margin: 0; padding: 0; list-style-type: none; background-color: var(--suggestion-background); } .react-autosuggest__suggestions-container--open { border: 1px solid var(--input-border); } .react-autosuggest__suggestion { cursor: pointer; padding: 10px 20px; } .react-autosuggest__suggestion > span { font-size: 13px; font-weight: 400; } .react-autosuggest__suggestion--highlighted, .react-autosuggest__suggestion--focused { background-color: var(--suggestion-hover); } `; const getStyling = (theme) => { let themeStyle = light; switch (theme) { case "dark": themeStyle = dark; break; default: themeStyle = light; } return createGlobalStyle` ${themeStyle} ${styles} `; }; const ThemeStyle = getStyling(localStorage.getItem("STRAPI_THEME")); const Tags = ({ attribute, description, error, label, labelAction, name, onChange, required, value }) => { const { formatMessage } = useIntl(); const apiUrl = attribute?.options?.apiUrl || ""; const attrName = apiUrl.slice(apiUrl.lastIndexOf("=") + 1) || "name"; const inputEle = useRef(null); const [tags, setTags] = useState(() => { try { const values = typeof value === "string" ? JSON.parse(value) : value; return Array.isArray(values) ? values.map((v) => v[attrName] || v.name || v) : []; } catch (e) { return []; } }); const [suggestions, setSuggestions] = useState([]); useEffect(() => { const suggestionsContainer = document.querySelector( ".react-autosuggest__suggestions-container" ); if (suggestionsContainer && inputEle.current) { suggestionsContainer.style.top = `${inputEle.current.offsetHeight + 5}px`; } const handleClickOutside = (event) => { const tagsInput = document.querySelector(".react-tagsinput"); if (tagsInput) { tagsInput.classList.toggle( "react-tagsinput--focused", inputEle.current?.contains(event.target) ); } }; document.addEventListener("mousedown", handleClickOutside); return () => document.removeEventListener("mousedown", handleClickOutside); }, []); const handleTagsChange = async (newTags) => { const lastTag = newTags[newTags.length - 1]; const suggestionsArray = suggestions.data || []; const existingTag = suggestionsArray.find( (s) => s[attrName]?.toLowerCase() === lastTag.toLowerCase() ); if (!existingTag) { if (apiUrl) { try { const response = await axios.post(apiUrl, { data: { [attrName]: lastTag } }); setSuggestions((prevSuggestions) => { const newSuggestionsData = [...prevSuggestions.data || [], response.data.data]; return { ...prevSuggestions, data: newSuggestionsData }; }); newTags[newTags.length - 1] = response.data.data[attrName]; } catch (error2) { console.error("Error creating new tag:", error2); } } else { setSuggestions((prevSuggestions) => { const newSuggestion = { id: Date.now(), [attrName]: lastTag }; const newSuggestionsData = [...prevSuggestions.data || [], newSuggestion]; return { ...prevSuggestions, data: newSuggestionsData }; }); } } setTags(newTags); const value2 = JSON.stringify(newTags.map((tag) => ({ [attrName]: tag }))); onChange({ target: { name, value: value2, type: attribute.type } }); }; const getSuggestions = async () => { if (!apiUrl) return; try { const res = await axios.get(apiUrl); setSuggestions(res.data); } catch (err) { setSuggestions({ data: [] }); } }; const autocompleteRenderInput = (props) => { const handleOnChange = (e, { newValue, method }) => { if (method === "enter") { e.preventDefault(); } else { props.onChange(e); } }; const inputValue = props.value && props.value.trim() || ""; const inputLength = inputValue.length; let s = suggestions.data || []; if (s.length <= 0) { getSuggestions(); } if (inputLength > 0) { s = s.filter((state) => { const suggestionName = state[attrName] || ""; return suggestionName.toLowerCase().slice(0, inputLength) === inputValue; }).map((state) => ({ id: state.id, [attrName]: state[attrName] || "" })); } return /* @__PURE__ */ jsx( Autosuggest, { ref: props.ref, suggestions: s, shouldRenderSuggestions: (value2) => value2 && value2.trim().length > 0, getSuggestionValue: (s2) => s2[attrName], renderSuggestion: (s2) => /* @__PURE__ */ jsx("span", { children: s2[attrName] }), inputProps: { ...props, onChange: handleOnChange }, onSuggestionSelected: (_, { suggestion }) => props.addTag(suggestion[attrName]), onSuggestionsFetchRequested: () => { } } ); }; return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx( Field.Root, { name, id: name, error, hint: description && formatMessage({ id: description }), required, children: /* @__PURE__ */ jsxs( Flex, { direction: "column", alignItems: "stretch", gap: 1, style: { position: "relative" }, ref: inputEle, children: [ label && /* @__PURE__ */ jsx(Field.Label, { action: labelAction, children: formatMessage({ id: label, defaultMessage: "Tags" }) }), /* @__PURE__ */ jsx(ThemeStyle, {}), /* @__PURE__ */ jsx(Flex, { direction: "column", children: /* @__PURE__ */ jsx( TagsInput, { value: tags, onChange: handleTagsChange, onlyUnique: true, renderInput: autocompleteRenderInput } ) }), /* @__PURE__ */ jsx(Field.Hint, {}), /* @__PURE__ */ jsx(Field.Error, {}) ] } ) } ) }); }; Tags.defaultProps = { description: null, disabled: false, error: null, labelAction: null, required: false, value: "" }; Tags.propTypes = { label: PropTypes.object.isRequired, onChange: PropTypes.func.isRequired, attribute: PropTypes.object.isRequired, name: PropTypes.string.isRequired, description: PropTypes.object, error: PropTypes.string, labelAction: PropTypes.object, required: PropTypes.bool, value: PropTypes.string }; export { Tags as default }; //# sourceMappingURL=Input-DUnG8vg6.mjs.map