Token导航 LogoToken导航TokenDH.com
研究检索需要联网github未标认证来源可访问许可证需确认审计通过

advanced-components先进组件

Agent Skill

advanced-components 用于查找、检索和筛选相关信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要根据关键词、任务场景或来源线索快速定位候选结果时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

总安装

188

周安装

8

GitHub Stars

11

下载量

66
CodexClaudeCursorGemini CLI

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

2

许可证

unknown

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:advanced-components(先进组件)
来源仓库:https://github.com/lobbi-docs/claude
仓库路径:skills/advanced-components
安装命令:
npx skills add https://github.com/lobbi-docs/claude --skill advanced-components
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/lobbi-docs/claude --skill advanced-components

简介

advanced-components 提供高级 MUI 组件模式,支持异步自动完成、服务端选项加载等复杂交互场景。

  • 适用于需要构建复杂表单控件和动态数据输入的前端项目,尤其适合 React + MUI 技术栈。
  • 通过模块化方式组织组件逻辑,结合 useRef、useEffect 实现防抖和状态同步,支持自定义渲染函数。
  • 安装前需确认是否涉及文件读写或网络请求,建议检查仓库维护状态及权限边界后再部署使用。
  • 适用宿主包括 Codex、Claude、Cursor、Gemini CLI,接入前应确认版本、权限和运行环境要求。

SKILL.md

Advanced MUI Component Patterns

Deep patterns for complex MUI components that go beyond basic usage.


Autocomplete — Advanced Patterns

Async / Server-Side Options

import Autocomplete from '@mui/material/Autocomplete';
import TextField from '@mui/material/TextField';
import CircularProgress from '@mui/material/CircularProgress';
import { useState, useEffect, useRef } from 'react';

interface User {
  id: number;
  name: string;
  email: string;
}

function AsyncAutocomplete() {
  const [open, setOpen] = useState(false);
  const [options, setOptions] = useState<User[]>([]);
  const [loading, setLoading] = useState(false);
  const [inputValue, setInputValue] = useState('');
  const debounceRef = useRef<NodeJS.Timeout>();

  useEffect(() => {
    if (!open) return;
    if (!inputValue) {
      setOptions([]);
      return;
    }

    // Debounce API calls
    clearTimeout(debounceRef.current);
    debounceRef.current = setTimeout(async () => {
      setLoading(true);
      try {
        const res = await fetch(`/api/users?search=${encodeURIComponent(inputValue)}`);
        const data: User[] = await res.json();
        setOptions(data);
      } finally {
        setLoading(false);
      }
    }, 300);

    return () => clearTimeout(debounceRef.current);
  }, [inputValue, open]);

  return (
    <Autocomplete
      open={open}
      onOpen={() => setOpen(true)}
      onClose={() => setOpen(false)}
      options={options}
      loading={loading}
      getOptionLabel={(option) => option.name}
      isOptionEqualToValue={(option, value) => option.id === value.id}
      onInputChange={(_, value) => setInputValue(value)}
      filterOptions={(x) => x} // disable client-side filtering — server handles it
      renderInput={(params) => (
        <TextField
          {...params}
          label="Search users"
          slotProps={{
            input: {
              ...params.InputProps,
              endAdornment: (
                <>
                  {loading && <CircularProgress color="inherit" size={20} />}
                  {params.InputProps.endAdornment}
                </>
              ),
            },
          }}
        />
      )}
      renderOption={(props, option) => (
        <li {...props} key={option.id}>
          <Box>
            <Typography variant="body1">{option.name}</Typography>
            <Typography variant="caption" color="text.secondary">{option.email}</Typography>
          </Box>
        </li>
      )}
    />
  );
}

createFilterOptions — Custom Filtering

import { createFilterOptions } from '@mui/material/Autocomplete';

interface Film {
  title: string;
  year: number;
  inputValue?: string; // for "create" option
}

// Custom filter: match start of title, limit to 10
const filter = createFilterOptions<Film>({
  matchFrom: 'start',    // 'start' | 'any' (default: 'any')
  limit: 10,             // max options shown
  stringify: (option) => option.title, // what to search against
  ignoreAccents: true,   // normalize accents
  ignoreCase: true,      // case-insensitive (default: true)
  trim: true,            // trim whitespace
});

// "Create" option pattern — add user-typed value as new option
<Autocomplete
  freeSolo
  selectOnFocus
  clearOnBlur
  handleHomeEndKeys
  options={films}
  filterOptions={(options, params) => {
    const filtered = filter(options, params);

    const { inputValue } = params;
    // Suggest creating a new value
    const isExisting = options.some((option) => inputValue === option.title);
    if (inputValue !== '' && !isExisting) {
      filtered.push({
        inputValue,
        title: `Add "${inputValue}"`,
        year: new Date().getFullYear(),
      });
    }
    return filtered;
  }}
  getOptionLabel={(option) => {
    if (typeof option === 'string') return option;
    if (option.inputValue) return option.inputValue;
    return option.title;
  }}
  onChange={(_, newValue) => {
    if (newValue && typeof newValue !== 'string' && newValue.inputValue) {
      // User chose "Add ..." — create the new item
      handleCreate(newValue.inputValue);
    }
  }}
  renderInput={(params) => <TextField {...params} label="Film" />}
/>

Virtualized Autocomplete (10,000+ options)

import { FixedSizeList, ListChildComponentProps } from 'react-window';
import { forwardRef, HTMLAttributes } from 'react';

const ITEM_HEIGHT = 36;
const LISTBOX_PADDING = 8;

function renderRow(props: ListChildComponentProps) {
  const { data, index, style } = props;
  const dataSet = data[index];
  const inlineStyle = {
    ...style,
    top: (style.top as number) + LISTBOX_PADDING,
  };
  return (
    <li {...dataSet[0]} style={inlineStyle} key={dataSet[1].id}>
      {dataSet[1].label}
    </li>
  );
}

const ListboxComponent = forwardRef<HTMLDivElement, HTMLAttributes<HTMLElement>>(
  function ListboxComponent(props, ref) {
    const { children, ...other } = props;
    const items = children as [HTMLAttributes<HTMLElement>, { label: string; id: string }][];
    const itemCount = items.length;
    const height = Math.min(8, itemCount) * ITEM_HEIGHT + 2 * LISTBOX_PADDING;

    return (
      <div ref={ref} {...other}>
        <FixedSizeList
          height={height}
          width="100%"
          itemSize={ITEM_HEIGHT}
          itemCount={itemCount}
          itemData={items}
          overscanCount={5}
        >
          {renderRow}
        </FixedSizeList>
      </div>
    );
  }
);

<Autocomplete
  disableListWrap
  slots={{ listbox: ListboxComponent }}
  options={tenThousandOptions}
  renderInput={(params) => <TextField {...params} label="10,000 options" />}
/>

Grouped Options

<Autocomplete
  options={countries.sort((a, b) => -b.continent.localeCompare(a.continent))}
  groupBy={(option) => option.continent}
  getOptionLabel={(option) => option.name}
  renderGroup={(params) => (
    <li key={params.key}>
      <GroupHeader>{params.group}</GroupHeader>
      <GroupItems>{params.children}</GroupItems>
    </li>
  )}
  renderInput={(params) => <TextField {...params} label="Country" />}
/>

Multiple with Chip Limit

<Autocomplete
  multiple
  limitTags={2}           // show max 2 chips, "+N" for overflow
  disableCloseOnSelect    // keep dropdown open after selection
  options={allTags}
  getOptionLabel={(option) => option.label}
  renderTags={(value, getTagProps, ownerState) =>
    value.map((option, index) => {
      const { key, ...tagProps } = getTagProps({ index });
      return (
        <Chip
          key={key}
          label={option.label}
          size="small"
          color="primary"
          variant="outlined"
          {...tagProps}
        />
      );
    })
  }
  renderInput={(params) => <TextField {...params} label="Tags" placeholder="Add..." />}
/>

Highlight Matching Text

import parse from 'autosuggest-highlight/parse';
import match from 'autosuggest-highlight/match';

<Autocomplete
  options={options}
  renderOption={(props, option, { inputValue }) => {
    const matches = match(option.label, inputValue, { insideWords: true });
    const parts = parse(option.label, matches);
    return (
      <li {...props}>
        {parts.map((part, index) => (
          <span key={index} style={{ fontWeight: part.highlight ? 700 : 400 }}>
            {part.text}
          </span>
        ))}
      </li>
    );
  }}
  renderInput={(params) => <TextField {...params} label="Highlight demo" />}
/>

Stepper — Advanced Patterns

Horizontal Stepper with Validation

import Stepper from '@mui/material/Stepper';
import Step from '@mui/material/Step';
import StepLabel from '@mui/material/StepLabel';
import StepContent from '@mui/material/StepContent';
import Button from '@mui/material/Button';

const steps = ['Account', 'Profile', 'Confirm'];

function HorizontalStepper() {
  const [activeStep, setActiveStep] = useState(0);
  const [errors, setErrors] = useState<Record<number, string>>({});

  const validateStep = (step: number): boolean => {
    // Per-step validation logic
    switch (step) {
      case 0: return !!formData.email && !!formData.password;
      case 1: return !!formData.name;
      default: return true;
    }
  };

  const handleNext = () => {
    if (validateStep(activeStep)) {
      setErrors((prev) => ({ ...prev, [activeStep]: '' }));
      setActiveStep((prev) => prev + 1);
    } else {
      setErrors((prev) => ({ ...prev, [activeStep]: 'Please fill all fields' }));
    }
  };

  return (
    <>
      <Stepper activeStep={activeStep} alternativeLabel>
        {steps.map((label, index) => (
          <Step key={label}>
            <StepLabel
              error={!!errors[index]}
              optional={errors[index] ? (
                <Typography variant="caption" color="error">{errors[index]}</Typography>
              ) : undefined}
            >
              {label}
            </StepLabel>
          </Step>
        ))}
      </Stepper>
      {/* Step content */}
      {activeStep === 0 && <AccountForm />}
      {activeStep === 1 && <ProfileForm />}
      {activeStep === 2 && <ConfirmStep />}
      <Box sx={{ display: 'flex', gap: 1, mt: 2 }}>
        <Button disabled={activeStep === 0} onClick={() => setActiveStep((p) => p - 1)}>
          Back
        </Button>
        <Button variant="contained" onClick={handleNext}>
          {activeStep === steps.length - 1 ? 'Finish' : 'Next'}
        </Button>
      </Box>
    </>
  );
}

Vertical Stepper (Step Content)

<Stepper activeStep={activeStep} orientation="vertical">
  {steps.map((step, index) => (
    <Step key={step.label}>
      <StepLabel
        optional={index === steps.length - 1 ? (
          <Typography variant="caption">Last step</Typography>
        ) : undefined}
      >
        {step.label}
      </StepLabel>
      <StepContent TransitionProps={{ unmountOnExit: false }}>
        <Typography>{step.description}</Typography>
        {step.content}
        <Box sx={{ mt: 2 }}>
          <Button variant="contained" onClick={handleNext} sx={{ mr: 1 }}>
            {index === steps.length - 1 ? 'Finish' : 'Continue'}
          </Button>
          <Button disabled={index === 0} onClick={handleBack}>Back</Button>
        </Box>
      </StepContent>
    </Step>
  ))}
</Stepper>

Non-Linear Stepper

function NonLinearStepper() {
  const [activeStep, setActiveStep] = useState(0);
  const [completed, setCompleted] = useState<Record<number, boolean>>({});

  const handleStep = (step: number) => () => {
    setActiveStep(step); // Jump to any step
  };

  const handleComplete = () => {
    setCompleted((prev) => ({ ...prev, [activeStep]: true }));
    // Move to next incomplete step
    const next = steps.findIndex((_, i) => !completed[i] && i !== activeStep);
    if (next !== -1) setActiveStep(next);
  };

  const allCompleted = Object.keys(completed).length === steps.length;

  return (
    <Stepper nonLinear activeStep={activeStep}>
      {steps.map((label, index) => (
        <Step key={label} completed={completed[index]}>
          <StepButton color="inherit" onClick={handleStep(index)}>
            {label}
          </StepButton>
        </Step>
      ))}
    </Stepper>
  );
}

Custom Step Icons & Connectors

import StepConnector, { stepConnectorClasses } from '@mui/material/StepConnector';
import { StepIconProps } from '@mui/material/StepIcon';
import { styled } from '@mui/material/styles';
import Check from '@mui/icons-material/Check';

const QontoConnector = styled(StepConnector)(({ theme }) => ({
  [`&.${stepConnectorClasses.alternativeLabel}`]: {
    top: 10,
    left: 'calc(-50% + 16px)',
    right: 'calc(50% + 16px)',
  },
  [`&.${stepConnectorClasses.active}`]: {
    [`& .${stepConnectorClasses.line}`]: {
      borderColor: theme.palette.primary.main,
    },
  },
  [`&.${stepConnectorClasses.completed}`]: {
    [`& .${stepConnectorClasses.line}`]: {
      borderColor: theme.palette.primary.main,
    },
  },
  [`& .${stepConnectorClasses.line}`]: {
    borderColor: theme.palette.divider,
    borderTopWidth: 3,
    borderRadius: 1,
  },
}));

function QontoStepIcon(props: StepIconProps) {
  const { active, completed, className } = props;
  return (
    <Box
      className={className}
      sx={{
        color: active || completed ? 'primary.main' : 'text.disabled',
        display: 'flex',
        height: 22,
        alignItems: 'center',
      }}
    >
      {completed ? (
        <Check sx={{ fontSize: 18 }} />
      ) : (
        <Box
          sx={{
            width: 8,
            height: 8,
            borderRadius: '50%',
            backgroundColor: 'currentColor',
          }}
        />
      )}
    </Box>
  );
}

<Stepper alternativeLabel activeStep={activeStep} connector={<QontoConnector />}>
  {steps.map((label) => (
    <Step key={label}>
      <StepLabel StepIconComponent={QontoStepIcon}>{label}</StepLabel>
    </Step>
  ))}
</Stepper>

MobileStepper (Dots / Progress / Text)

import MobileStepper from '@mui/material/MobileStepper';
import KeyboardArrowLeft from '@mui/icons-material/KeyboardArrowLeft';
import KeyboardArrowRight from '@mui/icons-material/KeyboardArrowRight';

<MobileStepper
  variant="dots"          // 'dots' | 'progress' | 'text'
  steps={maxSteps}
  position="static"       // 'static' | 'top' | 'bottom'
  activeStep={activeStep}
  nextButton={
    <Button size="small" onClick={handleNext} disabled={activeStep === maxSteps - 1}>
      Next <KeyboardArrowRight />
    </Button>
  }
  backButton={
    <Button size="small" onClick={handleBack} disabled={activeStep === 0}>
      <KeyboardArrowLeft /> Back
    </Button>
  }
/>

Popover

import Popover from '@mui/material/Popover';

// Anchor to element
const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);

<Button onClick={(e) => setAnchorEl(e.currentTarget)}>Open Popover</Button>
<Popover
  open={Boolean(anchorEl)}
  anchorEl={anchorEl}
  onClose={() => setAnchorEl(null)}
  anchorOrigin={{
    vertical: 'bottom',    // 'top' | 'center' | 'bottom' | number
    horizontal: 'left',    // 'left' | 'center' | 'right' | number
  }}
  transformOrigin={{
    vertical: 'top',
    horizontal: 'left',
  }}
  slotProps={{
    paper: {
      sx: { p: 2, maxWidth: 300 },
    },
  }}
>
  <Typography>Popover content</Typography>
</Popover>

// Mouse-follow popover
<Popover
  open={Boolean(anchorEl)}
  anchorReference="anchorPosition"
  anchorPosition={
    anchorEl ? { top: mouseY, left: mouseX } : undefined
  }
>
  <Typography>Follows cursor</Typography>
</Popover>

// Virtual element (e.g., text selection)
<Popover
  open={open}
  anchorEl={{
    getBoundingClientRect: () => ({
      top: selectionRect.top,
      left: selectionRect.left,
      bottom: selectionRect.bottom,
      right: selectionRect.right,
      width: selectionRect.width,
      height: selectionRect.height,
      x: selectionRect.x,
      y: selectionRect.y,
      toJSON: () => {},
    }),
  }}
>
  <FormattingToolbar />
</Popover>

Popper

Lower-level than Popover — no backdrop, no click-away handling built in.

import Popper from '@mui/material/Popper';
import ClickAwayListener from '@mui/material/ClickAwayListener';
import Fade from '@mui/material/Fade';
import Paper from '@mui/material/Paper';

const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);
const open = Boolean(anchorEl);

<Button onClick={(e) => setAnchorEl(anchorEl ? null : e.currentTarget)}>
  Toggle
</Button>
<Popper
  open={open}
  anchorEl={anchorEl}
  placement="bottom-start"    // 12 placements: top, bottom, left, right + start/end
  transition
  modifiers={[
    { name: 'offset', options: { offset: [0, 8] } },       // [skid, distance]
    { name: 'flip', options: { fallbackPlacements: ['top-start'] } },
    { name: 'preventOverflow', options: { boundary: 'viewport' } },
  ]}
>
  {({ TransitionProps }) => (
    <Fade {...TransitionProps} timeout={200}>
      <Paper elevation={8} sx={{ p: 2, maxWidth: 300 }}>
        <ClickAwayListener onClickAway={() => setAnchorEl(null)}>
          <Box>
            <Typography>Popper content</Typography>
            <Button onClick={() => setAnchorEl(null)}>Close</Button>
          </Box>
        </ClickAwayListener>
      </Paper>
    </Fade>
  )}
</Popper>

Popper Placements

top-start    top    top-end
left-start            right-start
left                  right
left-end              right-end
bottom-start bottom bottom-end

Portal

Renders children into a different part of the DOM tree.

import Portal from '@mui/material/Portal';

// Render into document.body (default)
<Portal>
  <Box sx={{ position: 'fixed', bottom: 16, right: 16 }}>
    Floating element
  </Box>
</Portal>

// Render into a specific container
const containerRef = useRef<HTMLDivElement>(null);

<div ref={containerRef} />
<Portal container={containerRef.current}>
  <Typography>Rendered inside the div above</Typography>
</Portal>

// Disable portal (render in place)
<Portal disablePortal>
  <Typography>Rendered in the normal tree position</Typography>
</Portal>

ClickAwayListener

Detects clicks outside the wrapped element.

import ClickAwayListener from '@mui/material/ClickAwayListener';

<ClickAwayListener
  onClickAway={handleClose}
  mouseEvent="onMouseDown"    // 'onClick' | 'onMouseDown' | 'onMouseUp' | false
  touchEvent="onTouchStart"   // 'onTouchStart' | 'onTouchEnd' | false
>
  <Box sx={{ position: 'relative' }}>
    <Button onClick={toggleMenu}>Menu</Button>
    {open && (
      <Paper sx={{ position: 'absolute', top: '100%', zIndex: 1 }}>
        <MenuItem>Item 1</MenuItem>
        <MenuItem>Item 2</MenuItem>
      </Paper>
    )}
  </Box>
</ClickAwayListener>

Gotcha: ClickAwayListener only works with a single child element. If wrapping multiple elements, wrap them in a <div> or <Box>.


NoSsr

Defers rendering to client only — useful for client-dependent content.

import NoSsr from '@mui/material/NoSsr';

// Content only renders on client (prevents SSR hydration mismatch)
<NoSsr fallback={<Skeleton variant="rectangular" height={200} />}>
  <MapComponent /> {/* Uses window.navigator, fails on server */}
</NoSsr>

// Defer to second frame (avoid blocking first paint)
<NoSsr defer>
  <HeavyChart />
</NoSsr>

Composition Patterns

Component Prop (Polymorphic)

Many MUI components accept a component prop to change the rendered HTML element:

// Button as a router link
import { Link as RouterLink } from 'react-router-dom';

<Button component={RouterLink} to="/dashboard">
  Dashboard
</Button>

// ListItemButton as a router link
<ListItemButton component={RouterLink} to="/settings">
  <ListItemIcon><SettingsIcon /></ListItemIcon>
  <ListItemText primary="Settings" />
</ListItemButton>

// Card as an article
<Card component="article">
  <CardContent>Article content</CardContent>
</Card>

// Typography as a label
<Typography component="label" htmlFor="email-input">
  Email
</Typography>

Forwarding sx to Inner Components

interface CustomCardProps {
  title: string;
  children: React.ReactNode;
  sx?: SxProps<Theme>;
}

function CustomCard({ title, children, sx }: CustomCardProps) {
  return (
    <Card sx={[{ borderRadius: 2, border: '1px solid', borderColor: 'divider' }, ...(Array.isArray(sx) ? sx : [sx])]}>
      <CardContent>
        <Typography variant="h6">{title}</Typography>
        {children}
      </CardContent>
    </Card>
  );
}

// Usage — sx is properly merged
<CustomCard title="Stats" sx={{ bgcolor: 'primary.light' }}>
  Content
</CustomCard>

Render Props with MUI (Headless Patterns)

import { useAutocomplete } from '@mui/material/useAutocomplete';

function CustomCombobox({ options, label }: { options: string[]; label: string }) {
  const {
    getRootProps,
    getInputProps,
    getListboxProps,
    getOptionProps,
    groupedOptions,
    focused,
    value,
  } = useAutocomplete({
    options,
    getOptionLabel: (option) => option,
  });

  return (
    <div {...getRootProps()}>
      <label>{label}</label>
      <input {...getInputProps()} />
      {groupedOptions.length > 0 && (
        <ul {...getListboxProps()}>
          {groupedOptions.map((option, index) => (
            <li {...getOptionProps({ option, index })} key={option}>
              {option}
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

适合场景

01

用户想查找某类 Agent Skill 时

02

需要根据任务场景推荐可安装能力包时

03

需要对比不同来源的安装命令和来源信息时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

Codex

36.73%
按下载量换算24

Claude

27.89%
按下载量换算18

Cursor

17.4%
按下载量换算11

Gemini CLI

9.13%
按下载量换算6

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

需要联网

该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。当前只有一个来源,正式发布前建议补源仓库或其他目录站核验。

来源信息

继续浏览同类 Skills