add rotating placeholders

merge-requests/3361/merge^2
Siddharth Singh 2025-04-12 03:55:37 +05:30
rodzic a2ba5f85b8
commit cd9a326cae
Nie znaleziono w bazie danych klucza dla tego podpisu
2 zmienionych plików z 75 dodań i 5 usunięć

Wyświetl plik

@ -1,8 +1,11 @@
import FuzzySearch from 'fuzzy-search';
import React, { useState, useRef, useCallback, useEffect, useId } from 'react';
import { useIntl, MessageDescriptor } from 'react-intl';
import Input from 'soapbox/components/ui/input.tsx';
type PlaceholderText = string | MessageDescriptor;
interface FuzzySearchInputProps<T> {
/** The array of objects or strings to search through. */
data: T[];
@ -12,8 +15,12 @@ interface FuzzySearchInputProps<T> {
onSelection: (selection: T | null, clearField: () => void) => void;
/** Optional: The key to display in the suggestion list. Defaults to the first key in the `keys` prop or the item itself if data is string[]. */
displayKey?: keyof T;
/** Optional: Placeholder text for the input field. */
placeholder?: string;
/** Optional: Placeholder text for the input field. If a string is provided, it will be used as a static placeholder. */
placeholder?: PlaceholderText;
/** Optional: Array of placeholders to rotate through. Takes precedence over placeholder if both are provided. */
placeholders?: PlaceholderText[];
/** Optional: Interval in milliseconds to change placeholders. Defaults to 5000ms (5 seconds). */
placeholderChangeInterval?: number;
/**
* Optional: Custom search function to override the default fuzzy search. */
searchFn?: SearchImpl;
@ -51,17 +58,26 @@ function FuzzySearchInput<T extends Record<string, any> | string>({
onSelection,
displayKey,
placeholder = 'Search...',
placeholders,
placeholderChangeInterval = 5000,
searchFn = defaultSearch,
className = '',
baseId,
inputClassName = '',
renderSuggestion: FuzzySearchSuggestion,
}: FuzzySearchInputProps<T>) {
const intl = useIntl();
const [inputValue, setInputValue] = useState('');
const [suggestions, setSuggestions] = useState<T[]>([]);
const [showSuggestions, setShowSuggestions] = useState(false);
const [activeIndex, setActiveIndex] = useState<number>(-1);
// dynamic placeholder state
const [currentPlaceholder, setCurrentPlaceholder] = useState<string>(
typeof placeholder === 'string' ? placeholder : intl.formatMessage(placeholder),
);
const placeholderIntervalRef = useRef<NodeJS.Timeout | null>(null);
const containerRef = useRef<HTMLDivElement>(null);
// Generate unique IDs for ARIA attributes if no baseId is provided
const generatedId = useId();
@ -70,6 +86,49 @@ function FuzzySearchInput<T extends Record<string, any> | string>({
const listboxId = `${componentBaseId}-listbox`;
const getOptionId = (index: number) => `${componentBaseId}-option-${index}`;
// Helper function to format a placeholder (either string or MessageDescriptor)
const formatPlaceholder = useCallback((text: PlaceholderText): string => {
if (typeof text === 'string') {
return text;
}
return intl.formatMessage(text);
}, [intl]);
// Handle placeholder rotation if placeholders array is provided
useEffect(() => {
// Clear any existing interval
if (placeholderIntervalRef.current) {
clearInterval(placeholderIntervalRef.current);
placeholderIntervalRef.current = null;
}
// If we have multiple placeholders, set up rotation
if (placeholders && placeholders.length > 1) {
// Set initial placeholder
const randomIndex = Math.floor(Math.random() * placeholders.length);
setCurrentPlaceholder(formatPlaceholder(placeholders[randomIndex]));
// Set up interval to change placeholder
placeholderIntervalRef.current = setInterval(() => {
const randomIndex = Math.floor(Math.random() * placeholders.length);
setCurrentPlaceholder(formatPlaceholder(placeholders[randomIndex]));
}, placeholderChangeInterval);
} else if (placeholders && placeholders.length === 1) {
// If just one placeholder in the array, use it statically
setCurrentPlaceholder(formatPlaceholder(placeholders[0]));
} else if (placeholder) {
// Fall back to the single placeholder prop
setCurrentPlaceholder(formatPlaceholder(placeholder));
}
// Clean up interval on unmount
return () => {
if (placeholderIntervalRef.current) {
clearInterval(placeholderIntervalRef.current);
}
};
}, [placeholder, placeholders, placeholderChangeInterval, formatPlaceholder]);
const getDisplayText = useCallback((item: T): string => {
if (typeof item === 'string') {
return item;
@ -180,7 +239,7 @@ function FuzzySearchInput<T extends Record<string, any> | string>({
onKeyDown={handleKeyDown}
onFocus={handleFocus}
onBlur={handleBlur}
placeholder={placeholder}
placeholder={currentPlaceholder}
autoComplete='off'
aria-autocomplete='list'
aria-controls={showSuggestions && suggestions.length > 0 ? listboxId : undefined}

Wyświetl plik

@ -24,7 +24,7 @@ const messages = defineMessages({
welcomeTitle: { id: 'admin.policies.welcome.title', defaultMessage: 'Welcome to Policy Manager' },
welcomeGetStarted: { id: 'admin.policies.welcome.get_started', defaultMessage: 'Get Started' },
helpTitle: { id: 'admin.policies.help.title', defaultMessage: 'Help' },
helpButton: { id: 'admin.policies.help.button', defaultMessage: 'Help...' },
helpButton: { id: 'admin.policies.help.button', defaultMessage: 'Help' },
okay: { id: 'admin.policies.help.okay', defaultMessage: 'Okay' },
});
@ -45,6 +45,17 @@ const PolicyManager: FC = () => {
// get the current set of policies out of the API response
const initialPolicies = storedPolicies?.spec?.policies ?? [];
// Generate dynamic placeholders from policy names
const dynamicPlaceholders = useMemo(() => {
if (allPolicies.length === 0) {
return [messages.searchPlaceholder];
}
return [
...allPolicies.map(policy => policy.name.toLowerCase()),
];
}, [allPolicies]);
// initialFields is used to set up the reducer. stores the initial value of
// all the fields from the current policy and falls back to the default if
// the value isn't present.
@ -256,7 +267,7 @@ const PolicyManager: FC = () => {
keys={['name', 'description']}
onSelection={handleSelection}
displayKey='name'
placeholder={intl.formatMessage(messages.searchPlaceholder)}
placeholders={dynamicPlaceholders}
className='w-full'
renderSuggestion={PolicySuggestion}
/>