{"version":3,"file":"Select.cjs","sources":["../../../src/components/Select/Select.tsx"],"sourcesContent":["'use client'\n\nimport {\n  type ChangeEvent,\n  type ComponentPropsWithoutRef,\n  type ForwardedRef,\n  type OptgroupHTMLAttributes,\n  type OptionHTMLAttributes,\n  type PropsWithChildren,\n  memo,\n  useCallback,\n  useMemo,\n} from 'react'\nimport { tv } from 'tailwind-variants'\n\nimport { isIOS, isMobileSafari } from '../../libs/ua'\nimport { genericsForwardRef } from '../../libs/util'\nimport { FaAngleDownIcon } from '../Icon'\n\ntype Option<T extends string> = {\n  value: T\n} & Omit<OptionHTMLAttributes<HTMLOptionElement>, 'value'>\ntype Optgroup<T extends string> = {\n  label: string\n  options: Array<Option<T>>\n} & OptgroupHTMLAttributes<HTMLOptGroupElement>\n\ntype AbstractProps<T extends string> = {\n  /** 選択肢のデータの配列 */\n  options: Array<Option<T> | Optgroup<T>>\n  /** フォームの値が変わったときに発火するコールバック関数 */\n  onChangeValue?: (value: T) => void\n  /** フォームの値にエラーがあるかどうか */\n  error?: boolean\n  /** コンポーネントの幅 */\n  width?: number | string\n  /** コンポーネントの大きさ */\n  size?: 'M' | 'S'\n  /** 空の選択肢を表示するかどうか */\n  hasBlank?: boolean\n  /** 空の選択肢のラベル */\n  blankLabel?: string\n}\n\ntype Props<T extends string> = AbstractProps<T> &\n  Omit<ComponentPropsWithoutRef<'select'>, keyof AbstractProps<string> | 'children'>\n\nconst classNameGenerator = tv({\n  slots: {\n    wrapper: 'smarthr-ui-Select shr-relative shr-inline-block',\n    select: [\n      'shr-peer shr-border-shorthand shr-w-full shr-cursor-pointer shr-appearance-none shr-rounded-m shr-bg-white shr-text-base shr-leading-tight shr-text-black',\n      'hover:shr-bg-white-darken',\n      'focus-visible:shr-focus-indicator',\n      'disabled:shr-pointer-events-none disabled:shr-bg-white-darken disabled:shr-text-disabled disabled:shr-opacity-100',\n      'contrast-more:shr-border-high-contrast',\n      /* padding に依る積み上げでは文字が見切れてしまうため */\n      'shr-min-h-[calc(theme(fontSize.base)_+_theme(spacing[0.75])_*_2_+_theme(spacing.px)_*_2)]',\n      'shr-border-default disabled:shr-border-disabled',\n      'aria-[invalid]:shr-border-danger',\n    ],\n    iconWrap: [\n      'shr-pointer-events-none shr-absolute shr-inset-y-0 shr-inline-flex shr-items-center shr-text-grey',\n      'peer-focus-visible:shr-text-black peer-disabled:shr-text-disabled',\n    ],\n    blankOptgroup: 'shr-hidden',\n  },\n  variants: {\n    size: {\n      M: {\n        select: 'shr-py-0.5 shr-pe-2 shr-ps-0.5',\n        // ((右 padding - アイコン幅) / 2) + 右 border\n        iconWrap: 'shr-end-[calc(theme(spacing[0.5])_+_theme(spacing.px))]',\n      },\n      S: {\n        select: [\n          'shr-px-0.5 shr-py-0.25 shr-pe-1.5 shr-text-sm',\n          /* padding に依る積み上げでは文字が見切れてしまうため */\n          'shr-min-h-[calc(theme(fontSize.sm)_+_theme(spacing[0.5])_*_2_+_theme(spacing.px)_*_2)]',\n        ],\n        iconWrap: 'shr-end-0.5 shr-text-sm',\n      },\n    },\n  },\n})\n\nconst ActualSelect = <T extends string>(\n  {\n    options,\n    onChange,\n    onChangeValue,\n    error,\n    width,\n    hasBlank,\n    blankLabel,\n    size,\n    className,\n    disabled,\n    required,\n    ...rest\n  }: Props<T>,\n  ref: ForwardedRef<HTMLSelectElement>,\n) => {\n  const handleChange = useCallback(\n    (e: ChangeEvent<HTMLSelectElement>) => {\n      onChange?.(e)\n\n      if (onChangeValue) {\n        const flattenOptions = options.reduce(\n          (pre, cur) => pre.concat('value' in cur ? cur : cur.options),\n          [] as Array<Option<T>>,\n        )\n        const selectedOption = flattenOptions.find((option) => option.value === e.target.value)\n\n        if (selectedOption) {\n          onChangeValue(selectedOption.value)\n        }\n      }\n    },\n    [onChange, onChangeValue, options],\n  )\n\n  const classNames = useMemo(() => {\n    const { wrapper, select, iconWrap, blankOptgroup } = classNameGenerator()\n    const sizeProps = {\n      size: size || 'M',\n    }\n\n    return {\n      wrapper: wrapper({ className }),\n      select: select(sizeProps),\n      iconWrap: iconWrap(sizeProps),\n      blankOptGroup: blankOptgroup(),\n    }\n  }, [className, size])\n  const wrapperStyle = useMemo(\n    () => ({\n      width: typeof width === 'number' ? `${width}px` : width,\n    }),\n    [width],\n  )\n\n  const actualBlankLabel = blankLabel ?? ''\n\n  return (\n    <span className={classNames.wrapper} style={wrapperStyle}>\n      <select\n        {...rest}\n        data-smarthr-ui-input=\"true\"\n        onChange={handleChange}\n        aria-invalid={error || undefined}\n        disabled={disabled}\n        // HINT: required属性を設定すると、iOS端末で以下の問題が発生します\n        //  - フォームのsubmit時にバリデーションは行われるが、ユーザーにフィードバックがない\n        //    - エラーメッセージが表示されない\n        //    - 問題のある入力フィールドまでスクロールしない\n        // 歴史的に一部の端末ではrequired属性が無視されることがあるため、HTMLのバリデーションのみとすることは少ないです\n        // そのため、iOS端末ではrequired属性を設定しない方がユーザーがsubmitできない理由をエラーメッセージなどで正しく理解できるようになります\n        required={isIOS ? undefined : required}\n        ref={ref}\n        className={classNames.select}\n      >\n        <BlankOption hasBlank={hasBlank}>{actualBlankLabel}</BlankOption>\n        {options.map((option, index) => (\n          <Option {...option} key={index} />\n        ))}\n        <NotOmittingLabelsInMobileSafari className={classNames.blankOptGroup} />\n      </select>\n      <span className={classNames.iconWrap}>\n        <FaAngleDownIcon />\n      </span>\n    </span>\n  )\n}\n\nconst BlankOption = memo<\n  PropsWithChildren<{\n    hasBlank: boolean | undefined\n  }>\n>(({ hasBlank, children }) => hasBlank && <option value=\"\">{children}</option>)\n\nconst Option = memo<Props<string>['options'][number]>((option) => {\n  if ('value' in option) {\n    return <option {...option}>{option.label}</option>\n  }\n\n  const { options: groupedOptions, label, ...rest } = option\n\n  return (\n    <optgroup {...rest} key={label} label={label}>\n      {groupedOptions.map((groupedOption) => (\n        <option {...groupedOption} key={groupedOption.value}>\n          {groupedOption.label}\n        </option>\n      ))}\n    </optgroup>\n  )\n})\n\n// Support for not omitting labels in Mobile Safari\nconst NotOmittingLabelsInMobileSafari = memo<{ className: string }>(\n  ({ className }) => isMobileSafari && <optgroup className={className} />,\n)\n\nexport const Select = genericsForwardRef(ActualSelect)\n"],"names":[],"mappings":";;;;;;;;;;;;AA+CA;AACE;AACE;AACA;;;;;;;;;;AAUC;AACD;;;AAGC;AACD;AACD;AACD;AACE;AACE;AACE;;AAEA;AACD;AACD;AACE;;;;AAIC;AACD;AACD;AACF;AACF;AACF;AAED;AAiBE;AAEI;;AAGE;;;AAOE;;;;AAOR;AACE;AACA;;;;AAKE;AACA;AACA;;;AAGJ;AACA;AAEI;AACD;AAIH;;;;;;;;AAgBM;AAeR;AAEA;AAMA;AACE;AACE;;AAGF;AAEA;AASF;AAEA;AACA;;;"}