@shoplflow/base 0.45.13 → 0.45.15

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/dist/index.cjs CHANGED
@@ -426,7 +426,9 @@ var spacing02 = "var(--spacing02)";
426
426
  var spacing04 = "var(--spacing04)";
427
427
  var spacing06 = "var(--spacing06)";
428
428
  var spacing08 = "var(--spacing08)";
429
+ var spacing10 = "var(--spacing10)";
429
430
  var spacing12 = "var(--spacing12)";
431
+ var spacing14 = "var(--spacing14)";
430
432
  var spacing16 = "var(--spacing16)";
431
433
  var spacing20 = "var(--spacing20)";
432
434
  var spacing24 = "var(--spacing24)";
@@ -438,7 +440,9 @@ exports.spacingTokens = {
438
440
  spacing04,
439
441
  spacing06,
440
442
  spacing08,
443
+ spacing10,
441
444
  spacing12,
445
+ spacing14,
442
446
  spacing16,
443
447
  spacing20,
444
448
  spacing24,
@@ -795,7 +799,9 @@ var Container = styled6__default.default.div`
795
799
  ${({ hasChangeAnimation }) => hasChangeAnimation && react$1.css`
796
800
  transition:
797
801
  width 0.3s ease-in-out,
798
- max-width 0.3s ease-in-out;
802
+ max-width 0.3s ease-in-out,
803
+ height 0.3s ease-in-out,
804
+ max-height 0.3s ease-in-out;
799
805
  `}
800
806
  display: flex;
801
807
  flex-direction: column;
@@ -3261,7 +3267,9 @@ var Dropdown = ({
3261
3267
  trigger,
3262
3268
  popper,
3263
3269
  option = "CLICK",
3264
- width = "100%"
3270
+ width = "100%",
3271
+ offset: offset4 = 4,
3272
+ placement
3265
3273
  }) => {
3266
3274
  const [triggerRef, setTriggerRef] = React3.useState(null);
3267
3275
  const [size2, setSize] = React3.useState({ width: 0, height: 0 });
@@ -3283,8 +3291,9 @@ var Dropdown = ({
3283
3291
  return /* @__PURE__ */ jsxRuntime.jsx(StyledDropdown, { "data-shoplflow": "Dropdown", width, children: /* @__PURE__ */ jsxRuntime.jsx(DropdownContext.Provider, { value: __spreadProps(__spreadValues({}, size2), { isOpen, setIsOpen, option }), children: /* @__PURE__ */ jsxRuntime.jsxs(
3284
3292
  exports.Popper,
3285
3293
  {
3286
- offset: 4,
3287
- autoPlacement: {
3294
+ offset: offset4,
3295
+ placement,
3296
+ autoPlacement: placement ? void 0 : {
3288
3297
  allowedPlacements: ["bottom-start", "top-start"]
3289
3298
  },
3290
3299
  children: [
@@ -3663,6 +3672,12 @@ var StyledInput = styled6__default.default.input`
3663
3672
  box-sizing: inherit;
3664
3673
  }
3665
3674
  `;
3675
+ var LeftElementWrapper2 = styled6__default.default.div`
3676
+ padding: ${({ sizeVar }) => sizeVar === "S" ? "0 0 0 8px" : "0 0 0 12px"};
3677
+ display: flex;
3678
+ flex-direction: row;
3679
+ align-items: center;
3680
+ `;
3666
3681
  var RightElementWrapper2 = styled6__default.default.div`
3667
3682
  padding: ${({ sizeVar, type, initialType }) => {
3668
3683
  if (initialType === "password" || type === "password") {
@@ -3820,7 +3835,9 @@ var Input = React3.forwardRef(
3820
3835
  rightSource,
3821
3836
  minWidth,
3822
3837
  gap,
3823
- initIsFocused
3838
+ initIsFocused,
3839
+ rightSourceStyle,
3840
+ leftSource
3824
3841
  } = _b, rest = __objRest(_b, [
3825
3842
  "onFocus",
3826
3843
  "onBlur",
@@ -3843,7 +3860,9 @@ var Input = React3.forwardRef(
3843
3860
  "rightSource",
3844
3861
  "minWidth",
3845
3862
  "gap",
3846
- "initIsFocused"
3863
+ "initIsFocused",
3864
+ "rightSourceStyle",
3865
+ "leftSource"
3847
3866
  ]);
3848
3867
  const [text, setText] = React3.useState("");
3849
3868
  const [isFocused, setIsFocused] = React3.useState(Boolean(initIsFocused));
@@ -3960,6 +3979,7 @@ var Input = React3.forwardRef(
3960
3979
  gap,
3961
3980
  sizeVar,
3962
3981
  children: [
3982
+ leftSource && /* @__PURE__ */ jsxRuntime.jsx(LeftElementWrapper2, { sizeVar, initialType, style: rightSourceStyle, children: leftSource }),
3963
3983
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "relative", width: "100%" }, children: [
3964
3984
  /* @__PURE__ */ jsxRuntime.jsx(
3965
3985
  StyledInput,
@@ -3981,7 +4001,7 @@ var Input = React3.forwardRef(
3981
4001
  ),
3982
4002
  onClear && isHovered && Boolean(String(text).length > 0) && /* @__PURE__ */ jsxRuntime.jsx(ClearIconButton, { sizeVar, onClick: handleOnClear, styleVar: "GHOST", isHovered: false, children: /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { iconSource: assetFunction("DeleteIcon"), color: "neutral350" }) })
3983
4003
  ] }),
3984
- !(type === "number") && (Boolean(maxLength && isFocused) || Boolean(initialType === "password") || Boolean(rightSource)) && /* @__PURE__ */ jsxRuntime.jsxs(RightElementWrapper2, { type, sizeVar, initialType, children: [
4004
+ !(type === "number") && (Boolean(maxLength && isFocused) || Boolean(initialType === "password") || Boolean(rightSource)) && /* @__PURE__ */ jsxRuntime.jsxs(RightElementWrapper2, { type, sizeVar, initialType, style: rightSourceStyle, children: [
3985
4005
  maxLength && isFocused && /* @__PURE__ */ jsxRuntime.jsx(TextCounter_default, { currentLength: String(text).length, maxLength, isError }),
3986
4006
  initialType === "password" && /* @__PURE__ */ jsxRuntime.jsx(exports.IconButton, { sizeVar, onClick: handleTogglePasswordType, styleVar: "GHOST", children: /* @__PURE__ */ jsxRuntime.jsx(
3987
4007
  exports.Icon,
@@ -6673,13 +6693,20 @@ var NumberCombobox = (_a) => {
6673
6693
  sizeVar,
6674
6694
  width,
6675
6695
  ref: inputRef,
6676
- type: "number",
6696
+ type: "text",
6697
+ inputMode: "numeric",
6677
6698
  style: { textAlign: "left", paddingRight: "0px" },
6678
6699
  className: _className,
6679
6700
  gap: "4px",
6680
6701
  initIsFocused: isOpen,
6681
6702
  value,
6682
- onChange,
6703
+ onChange: (e) => {
6704
+ const numericValue = e.target.value.replace(/[^\d]/g, "");
6705
+ if (e.target.value !== numericValue) {
6706
+ e.target.value = numericValue;
6707
+ }
6708
+ onChange == null ? void 0 : onChange(e);
6709
+ },
6683
6710
  onFocus: () => {
6684
6711
  setIsOpen(true);
6685
6712
  },
@@ -6726,7 +6753,8 @@ var NumberCombobox = (_a) => {
6726
6753
  }
6727
6754
  onBlur == null ? void 0 : onBlur(event);
6728
6755
  },
6729
- rightSource: /* @__PURE__ */ jsxRuntime.jsx(exports.StackContainer.Horizontal, { padding: "0px 4px 0px 0px", children: /* @__PURE__ */ jsxRuntime.jsx(
6756
+ rightSourceStyle: { padding: "4px" },
6757
+ rightSource: /* @__PURE__ */ jsxRuntime.jsx(exports.StackContainer.Horizontal, { children: /* @__PURE__ */ jsxRuntime.jsx(
6730
6758
  exports.IconButton,
6731
6759
  {
6732
6760
  sizeVar: "XS",
package/dist/index.d.cts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as React$1 from 'react';
3
- import React__default, { ElementType, ComponentPropsWithoutRef, ReactNode, ReactElement, ImgHTMLAttributes, ForwardRefExoticComponent, PropsWithoutRef, RefAttributes, CSSProperties, HTMLAttributes, ComponentPropsWithRef, ButtonHTMLAttributes, InputHTMLAttributes, MouseEvent, Ref, ChangeEvent } from 'react';
3
+ import React__default, { ElementType, ComponentPropsWithoutRef, ReactNode, ReactElement, CSSProperties, ImgHTMLAttributes, ForwardRefExoticComponent, PropsWithoutRef, RefAttributes, HTMLAttributes, ComponentPropsWithRef, ButtonHTMLAttributes, InputHTMLAttributes, MouseEvent, Ref, ChangeEvent } from 'react';
4
4
  import { $Values } from '@shoplflow/utils';
5
5
  import { IconSource } from '@shoplflow/shopl-assets';
6
6
  import { IconSource as IconSource$1 } from '@shoplflow/hada-assets';
@@ -97,7 +97,9 @@ declare const spacingTokens: {
97
97
  spacing04: string;
98
98
  spacing06: string;
99
99
  spacing08: string;
100
+ spacing10: string;
100
101
  spacing12: string;
102
+ spacing14: string;
101
103
  spacing16: string;
102
104
  spacing20: string;
103
105
  spacing24: string;
@@ -269,6 +271,7 @@ interface RightElementProps {
269
271
  * 텍스트를 기준으로 오른쪽에 위치할 ReactElement를 설정합니다.
270
272
  */
271
273
  rightSource?: ReactElement;
274
+ rightSourceStyle?: CSSProperties;
272
275
  }
273
276
  interface LeftAndRightElementProps extends RightElementProps, LeftElementProps {
274
277
  }
@@ -954,6 +957,8 @@ interface DropdownOptionProps {
954
957
  option?: DropdownOptionVariantType;
955
958
  width?: CSSProperties['width'];
956
959
  disabled?: boolean;
960
+ placement?: Placement;
961
+ offset?: OffsetOptions;
957
962
  }
958
963
  interface DropdownContentProps extends HTMLAttributes<HTMLDivElement>, ChildrenProps {
959
964
  /**
@@ -986,7 +991,7 @@ interface DropdownTriggerButtonProps extends Omit<ButtonHTMLAttributes<HTMLButto
986
991
  }
987
992
 
988
993
  declare const Dropdown: {
989
- ({ isOpen: initialIsOpen, trigger, popper, option, width, }: DropdownProps): react_jsx_runtime.JSX.Element;
994
+ ({ isOpen: initialIsOpen, trigger, popper, option, width, offset, placement, }: DropdownProps): react_jsx_runtime.JSX.Element;
990
995
  Button: React__default.ForwardRefExoticComponent<DropdownTriggerButtonProps & React__default.RefAttributes<HTMLButtonElement>>;
991
996
  Content: ({ children, width: initialWidth, type, onClick, ...rest }: DropdownContentProps) => react_jsx_runtime.JSX.Element;
992
997
  };
@@ -1095,7 +1100,7 @@ declare const InputSizeVariants: {
1095
1100
  readonly M: "M";
1096
1101
  };
1097
1102
  declare type InputSizeVariantType = $Values<typeof InputSizeVariants>;
1098
- interface InputProps extends InputOptionProps, Omit<InputHTMLAttributes<HTMLInputElement>, 'width'>, DisableProps, RightElementProps {
1103
+ interface InputProps extends InputOptionProps, Omit<InputHTMLAttributes<HTMLInputElement>, 'width'>, DisableProps, LeftElementProps, RightElementProps {
1099
1104
  }
1100
1105
  interface InputOptionProps extends ErrorProps, SizeVariantProps<InputSizeVariantType> {
1101
1106
  width?: string;
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as React$1 from 'react';
3
- import React__default, { ElementType, ComponentPropsWithoutRef, ReactNode, ReactElement, ImgHTMLAttributes, ForwardRefExoticComponent, PropsWithoutRef, RefAttributes, CSSProperties, HTMLAttributes, ComponentPropsWithRef, ButtonHTMLAttributes, InputHTMLAttributes, MouseEvent, Ref, ChangeEvent } from 'react';
3
+ import React__default, { ElementType, ComponentPropsWithoutRef, ReactNode, ReactElement, CSSProperties, ImgHTMLAttributes, ForwardRefExoticComponent, PropsWithoutRef, RefAttributes, HTMLAttributes, ComponentPropsWithRef, ButtonHTMLAttributes, InputHTMLAttributes, MouseEvent, Ref, ChangeEvent } from 'react';
4
4
  import { $Values } from '@shoplflow/utils';
5
5
  import { IconSource } from '@shoplflow/shopl-assets';
6
6
  import { IconSource as IconSource$1 } from '@shoplflow/hada-assets';
@@ -97,7 +97,9 @@ declare const spacingTokens: {
97
97
  spacing04: string;
98
98
  spacing06: string;
99
99
  spacing08: string;
100
+ spacing10: string;
100
101
  spacing12: string;
102
+ spacing14: string;
101
103
  spacing16: string;
102
104
  spacing20: string;
103
105
  spacing24: string;
@@ -269,6 +271,7 @@ interface RightElementProps {
269
271
  * 텍스트를 기준으로 오른쪽에 위치할 ReactElement를 설정합니다.
270
272
  */
271
273
  rightSource?: ReactElement;
274
+ rightSourceStyle?: CSSProperties;
272
275
  }
273
276
  interface LeftAndRightElementProps extends RightElementProps, LeftElementProps {
274
277
  }
@@ -954,6 +957,8 @@ interface DropdownOptionProps {
954
957
  option?: DropdownOptionVariantType;
955
958
  width?: CSSProperties['width'];
956
959
  disabled?: boolean;
960
+ placement?: Placement;
961
+ offset?: OffsetOptions;
957
962
  }
958
963
  interface DropdownContentProps extends HTMLAttributes<HTMLDivElement>, ChildrenProps {
959
964
  /**
@@ -986,7 +991,7 @@ interface DropdownTriggerButtonProps extends Omit<ButtonHTMLAttributes<HTMLButto
986
991
  }
987
992
 
988
993
  declare const Dropdown: {
989
- ({ isOpen: initialIsOpen, trigger, popper, option, width, }: DropdownProps): react_jsx_runtime.JSX.Element;
994
+ ({ isOpen: initialIsOpen, trigger, popper, option, width, offset, placement, }: DropdownProps): react_jsx_runtime.JSX.Element;
990
995
  Button: React__default.ForwardRefExoticComponent<DropdownTriggerButtonProps & React__default.RefAttributes<HTMLButtonElement>>;
991
996
  Content: ({ children, width: initialWidth, type, onClick, ...rest }: DropdownContentProps) => react_jsx_runtime.JSX.Element;
992
997
  };
@@ -1095,7 +1100,7 @@ declare const InputSizeVariants: {
1095
1100
  readonly M: "M";
1096
1101
  };
1097
1102
  declare type InputSizeVariantType = $Values<typeof InputSizeVariants>;
1098
- interface InputProps extends InputOptionProps, Omit<InputHTMLAttributes<HTMLInputElement>, 'width'>, DisableProps, RightElementProps {
1103
+ interface InputProps extends InputOptionProps, Omit<InputHTMLAttributes<HTMLInputElement>, 'width'>, DisableProps, LeftElementProps, RightElementProps {
1099
1104
  }
1100
1105
  interface InputOptionProps extends ErrorProps, SizeVariantProps<InputSizeVariantType> {
1101
1106
  width?: string;
package/dist/index.js CHANGED
@@ -399,7 +399,9 @@ var spacing02 = "var(--spacing02)";
399
399
  var spacing04 = "var(--spacing04)";
400
400
  var spacing06 = "var(--spacing06)";
401
401
  var spacing08 = "var(--spacing08)";
402
+ var spacing10 = "var(--spacing10)";
402
403
  var spacing12 = "var(--spacing12)";
404
+ var spacing14 = "var(--spacing14)";
403
405
  var spacing16 = "var(--spacing16)";
404
406
  var spacing20 = "var(--spacing20)";
405
407
  var spacing24 = "var(--spacing24)";
@@ -411,7 +413,9 @@ var spacingTokens = {
411
413
  spacing04,
412
414
  spacing06,
413
415
  spacing08,
416
+ spacing10,
414
417
  spacing12,
418
+ spacing14,
415
419
  spacing16,
416
420
  spacing20,
417
421
  spacing24,
@@ -768,7 +772,9 @@ var Container = styled6.div`
768
772
  ${({ hasChangeAnimation }) => hasChangeAnimation && css`
769
773
  transition:
770
774
  width 0.3s ease-in-out,
771
- max-width 0.3s ease-in-out;
775
+ max-width 0.3s ease-in-out,
776
+ height 0.3s ease-in-out,
777
+ max-height 0.3s ease-in-out;
772
778
  `}
773
779
  display: flex;
774
780
  flex-direction: column;
@@ -3234,7 +3240,9 @@ var Dropdown = ({
3234
3240
  trigger,
3235
3241
  popper,
3236
3242
  option = "CLICK",
3237
- width = "100%"
3243
+ width = "100%",
3244
+ offset: offset4 = 4,
3245
+ placement
3238
3246
  }) => {
3239
3247
  const [triggerRef, setTriggerRef] = useState(null);
3240
3248
  const [size2, setSize] = useState({ width: 0, height: 0 });
@@ -3256,8 +3264,9 @@ var Dropdown = ({
3256
3264
  return /* @__PURE__ */ jsx(StyledDropdown, { "data-shoplflow": "Dropdown", width, children: /* @__PURE__ */ jsx(DropdownContext.Provider, { value: __spreadProps(__spreadValues({}, size2), { isOpen, setIsOpen, option }), children: /* @__PURE__ */ jsxs(
3257
3265
  Popper_default,
3258
3266
  {
3259
- offset: 4,
3260
- autoPlacement: {
3267
+ offset: offset4,
3268
+ placement,
3269
+ autoPlacement: placement ? void 0 : {
3261
3270
  allowedPlacements: ["bottom-start", "top-start"]
3262
3271
  },
3263
3272
  children: [
@@ -3636,6 +3645,12 @@ var StyledInput = styled6.input`
3636
3645
  box-sizing: inherit;
3637
3646
  }
3638
3647
  `;
3648
+ var LeftElementWrapper2 = styled6.div`
3649
+ padding: ${({ sizeVar }) => sizeVar === "S" ? "0 0 0 8px" : "0 0 0 12px"};
3650
+ display: flex;
3651
+ flex-direction: row;
3652
+ align-items: center;
3653
+ `;
3639
3654
  var RightElementWrapper2 = styled6.div`
3640
3655
  padding: ${({ sizeVar, type, initialType }) => {
3641
3656
  if (initialType === "password" || type === "password") {
@@ -3793,7 +3808,9 @@ var Input = forwardRef(
3793
3808
  rightSource,
3794
3809
  minWidth,
3795
3810
  gap,
3796
- initIsFocused
3811
+ initIsFocused,
3812
+ rightSourceStyle,
3813
+ leftSource
3797
3814
  } = _b, rest = __objRest(_b, [
3798
3815
  "onFocus",
3799
3816
  "onBlur",
@@ -3816,7 +3833,9 @@ var Input = forwardRef(
3816
3833
  "rightSource",
3817
3834
  "minWidth",
3818
3835
  "gap",
3819
- "initIsFocused"
3836
+ "initIsFocused",
3837
+ "rightSourceStyle",
3838
+ "leftSource"
3820
3839
  ]);
3821
3840
  const [text, setText] = useState("");
3822
3841
  const [isFocused, setIsFocused] = useState(Boolean(initIsFocused));
@@ -3933,6 +3952,7 @@ var Input = forwardRef(
3933
3952
  gap,
3934
3953
  sizeVar,
3935
3954
  children: [
3955
+ leftSource && /* @__PURE__ */ jsx(LeftElementWrapper2, { sizeVar, initialType, style: rightSourceStyle, children: leftSource }),
3936
3956
  /* @__PURE__ */ jsxs("div", { style: { position: "relative", width: "100%" }, children: [
3937
3957
  /* @__PURE__ */ jsx(
3938
3958
  StyledInput,
@@ -3954,7 +3974,7 @@ var Input = forwardRef(
3954
3974
  ),
3955
3975
  onClear && isHovered && Boolean(String(text).length > 0) && /* @__PURE__ */ jsx(ClearIconButton, { sizeVar, onClick: handleOnClear, styleVar: "GHOST", isHovered: false, children: /* @__PURE__ */ jsx(Icon_default, { iconSource: assetFunction("DeleteIcon"), color: "neutral350" }) })
3956
3976
  ] }),
3957
- !(type === "number") && (Boolean(maxLength && isFocused) || Boolean(initialType === "password") || Boolean(rightSource)) && /* @__PURE__ */ jsxs(RightElementWrapper2, { type, sizeVar, initialType, children: [
3977
+ !(type === "number") && (Boolean(maxLength && isFocused) || Boolean(initialType === "password") || Boolean(rightSource)) && /* @__PURE__ */ jsxs(RightElementWrapper2, { type, sizeVar, initialType, style: rightSourceStyle, children: [
3958
3978
  maxLength && isFocused && /* @__PURE__ */ jsx(TextCounter_default, { currentLength: String(text).length, maxLength, isError }),
3959
3979
  initialType === "password" && /* @__PURE__ */ jsx(IconButton_default, { sizeVar, onClick: handleTogglePasswordType, styleVar: "GHOST", children: /* @__PURE__ */ jsx(
3960
3980
  Icon_default,
@@ -6646,13 +6666,20 @@ var NumberCombobox = (_a) => {
6646
6666
  sizeVar,
6647
6667
  width,
6648
6668
  ref: inputRef,
6649
- type: "number",
6669
+ type: "text",
6670
+ inputMode: "numeric",
6650
6671
  style: { textAlign: "left", paddingRight: "0px" },
6651
6672
  className: _className,
6652
6673
  gap: "4px",
6653
6674
  initIsFocused: isOpen,
6654
6675
  value,
6655
- onChange,
6676
+ onChange: (e) => {
6677
+ const numericValue = e.target.value.replace(/[^\d]/g, "");
6678
+ if (e.target.value !== numericValue) {
6679
+ e.target.value = numericValue;
6680
+ }
6681
+ onChange == null ? void 0 : onChange(e);
6682
+ },
6656
6683
  onFocus: () => {
6657
6684
  setIsOpen(true);
6658
6685
  },
@@ -6699,7 +6726,8 @@ var NumberCombobox = (_a) => {
6699
6726
  }
6700
6727
  onBlur == null ? void 0 : onBlur(event);
6701
6728
  },
6702
- rightSource: /* @__PURE__ */ jsx(StackContainer_default.Horizontal, { padding: "0px 4px 0px 0px", children: /* @__PURE__ */ jsx(
6729
+ rightSourceStyle: { padding: "4px" },
6730
+ rightSource: /* @__PURE__ */ jsx(StackContainer_default.Horizontal, { children: /* @__PURE__ */ jsx(
6703
6731
  IconButton_default,
6704
6732
  {
6705
6733
  sizeVar: "XS",
@@ -16,7 +16,9 @@
16
16
  --spacing04: 4px;
17
17
  --spacing06: 6px;
18
18
  --spacing08: 8px;
19
+ --spacing10: 10px;
19
20
  --spacing12: 12px;
21
+ --spacing14: 14px;
20
22
  --spacing16: 16px;
21
23
  --spacing20: 20px;
22
24
  --spacing24: 24px;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shoplflow/base",
3
- "version": "0.45.13",
3
+ "version": "0.45.15",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -99,7 +99,7 @@
99
99
  "react-dom": "^18.2.0",
100
100
  "simplebar-react": "^3.2.6",
101
101
  "@shoplflow/hada-assets": "^0.1.10",
102
- "@shoplflow/shopl-assets": "^0.12.33",
102
+ "@shoplflow/shopl-assets": "^0.12.34",
103
103
  "@shoplflow/utils": "^0.7.2"
104
104
  },
105
105
  "homepage": "https://github.com/shopl/shoplflow#readme",