@sarunyu/system-one 4.9.27 → 4.9.30

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/AGENTS.md CHANGED
@@ -24,6 +24,7 @@ in this package.** This file is the short version: the rules you must follow.
24
24
  - Custom toast / snackbar notifications → use `<Toast>` / `<ToastStack>` in a `fixed` portal.
25
25
  - Filter buttons with count badges → use `<Badge>` (`variant="button"`).
26
26
  - Notification bell + list panel → use `<Notification>`. Never use `<Badge variant="notification">` standalone or wire it to a custom `onClick` (toast, popover, etc.) — `<Notification>` handles both the bell and the panel.
27
+ - User profile photos, initials circles, or silhouette placeholders → use `<Avatar>`. For grouped users, use `<AvatarStack>`. Never hand-roll `<img>` or `<div>` avatar shapes.
27
28
 
28
29
  2. **Use token-backed Tailwind classes for color.** Never emit hard-coded colors:
29
30
  - Hex (`#3b82f6`), arbitrary (`bg-[#...]`), and palette utilities
package/DESIGN.md CHANGED
@@ -248,9 +248,9 @@ Use sparingly. Corporate UIs prefer border separation over heavy elevation.
248
248
 
249
249
  ## Component library
250
250
 
251
- This design system ships 24 pre-built components. **Always use them — never recreate with raw HTML.**
251
+ This design system ships 26 pre-built components. **Always use them — never recreate with raw HTML.**
252
252
 
253
- Key components: `Button`, `Input`, `TextArea`, `SearchInput`, `Dropdown`, `DropdownMultiple`, `Checkbox`, `Radio`, `Toggle`, `DateInput`, `TimeInput`, `Tag`, `StatusTag`, `Chip`, `TabGroup`, `Card`, `Table` + `TableRow` + `TableHeaderCell` + `TableCell`, `Modal`, `BottomSheet`, `Alert`, `Toast`, `ToastStack`, `Notification`, `Badge`.
253
+ Key components: `Button`, `Input`, `TextArea`, `SearchInput`, `Dropdown`, `DropdownMultiple`, `Checkbox`, `Radio`, `Toggle`, `DateInput`, `TimeInput`, `Avatar`, `AvatarStack`, `Tag`, `StatusTag`, `Chip`, `TabGroup`, `Card`, `Table` + `TableRow` + `TableHeaderCell` + `TableCell`, `Modal`, `BottomSheet`, `Alert`, `Toast`, `ToastStack`, `Notification`, `Badge`.
254
254
 
255
255
  For props, variants, and usage rules → read `AGENTS.md` and `llms.txt` in this package.
256
256
 
package/dist/index.cjs CHANGED
@@ -1534,11 +1534,20 @@ function BottomSheet({
1534
1534
  className,
1535
1535
  contentClassName
1536
1536
  }) {
1537
- return /* @__PURE__ */ jsxRuntime.jsxs(Drawer, { direction: "bottom", open, onOpenChange, children: [
1537
+ const [everOpened, setEverOpened] = React.useState(!!open);
1538
+ React.useEffect(() => {
1539
+ if (open) setEverOpened(true);
1540
+ }, [open]);
1541
+ const handleOpenChange = (next) => {
1542
+ if (next) setEverOpened(true);
1543
+ onOpenChange == null ? void 0 : onOpenChange(next);
1544
+ };
1545
+ return /* @__PURE__ */ jsxRuntime.jsxs(Drawer, { direction: "bottom", open, onOpenChange: handleOpenChange, children: [
1538
1546
  trigger ? /* @__PURE__ */ jsxRuntime.jsx(DrawerTrigger, { asChild: true, children: trigger }) : null,
1539
- /* @__PURE__ */ jsxRuntime.jsxs(
1547
+ everOpened && /* @__PURE__ */ jsxRuntime.jsxs(
1540
1548
  DrawerContent,
1541
1549
  {
1550
+ "aria-describedby": void 0,
1542
1551
  className: cn(
1543
1552
  "[&>div:first-child]:hidden rounded-t-[24px] border-t-0 px-4 pb-6 pt-2",
1544
1553
  className
@@ -1575,21 +1584,37 @@ function Header({
1575
1584
  rightIcon,
1576
1585
  onActionClick
1577
1586
  }) {
1578
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex items-center gap-3", rightSide === "action" ? "pr-2" : void 0), children: [
1579
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [
1580
- /* @__PURE__ */ jsxRuntime.jsx(Leading, { headerType, imageSrc, leftIcon }),
1581
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-base leading-6 font-bold text-foreground", children: title })
1582
- ] }),
1583
- /* @__PURE__ */ jsxRuntime.jsx(
1584
- Trailing,
1585
- {
1586
- rightSide,
1587
- actionLabel,
1588
- rightIcon,
1589
- onActionClick
1590
- }
1591
- )
1592
- ] });
1587
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1588
+ "div",
1589
+ {
1590
+ className: cn(
1591
+ "flex items-center gap-3",
1592
+ rightSide === "action" ? "pr-2" : void 0
1593
+ ),
1594
+ children: [
1595
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [
1596
+ /* @__PURE__ */ jsxRuntime.jsx(
1597
+ Leading,
1598
+ {
1599
+ headerType,
1600
+ imageSrc,
1601
+ leftIcon
1602
+ }
1603
+ ),
1604
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-base leading-6 font-bold text-foreground", children: title })
1605
+ ] }),
1606
+ /* @__PURE__ */ jsxRuntime.jsx(
1607
+ Trailing,
1608
+ {
1609
+ rightSide,
1610
+ actionLabel,
1611
+ rightIcon,
1612
+ onActionClick
1613
+ }
1614
+ )
1615
+ ]
1616
+ }
1617
+ );
1593
1618
  }
1594
1619
  function Leading({
1595
1620
  headerType,
@@ -1597,7 +1622,14 @@ function Leading({
1597
1622
  leftIcon
1598
1623
  }) {
1599
1624
  if (headerType === "image") {
1600
- return imageSrc ? /* @__PURE__ */ jsxRuntime.jsx("img", { alt: "", className: "size-8 shrink-0 rounded-md object-cover", src: imageSrc }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "size-8 shrink-0 rounded-md bg-muted" });
1625
+ return imageSrc ? /* @__PURE__ */ jsxRuntime.jsx(
1626
+ "img",
1627
+ {
1628
+ alt: "",
1629
+ className: "size-8 shrink-0 rounded-md object-cover",
1630
+ src: imageSrc
1631
+ }
1632
+ ) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "size-8 shrink-0 rounded-md bg-muted" });
1601
1633
  }
1602
1634
  if (headerType === "icon") {
1603
1635
  return /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", className: "text-foreground", children: leftIcon ?? /* @__PURE__ */ jsxRuntime.jsx(react.Circle, { size: 22 }) });
@@ -1614,7 +1646,7 @@ function Trailing({
1614
1646
  return /* @__PURE__ */ jsxRuntime.jsx(
1615
1647
  Button,
1616
1648
  {
1617
- className: "px-0 py-0 text-base leading-6 font-bold",
1649
+ className: "text-base leading-6 font-bold",
1618
1650
  onClick: onActionClick,
1619
1651
  size: "md",
1620
1652
  variant: "plain",
@@ -5957,6 +5989,138 @@ const TimeInput = React.forwardRef(
5957
5989
  }
5958
5990
  );
5959
5991
  TimeInput.displayName = "TimeInput";
5992
+ const avatarPlaceholder = "data:image/png;base64,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";
5993
+ const TEXT_GRADIENT = "linear-gradient(to top right, #B5D3F8, #E6F6FB)";
5994
+ const TEXT_COLOR = "#00A2D9";
5995
+ const SIZE_CONFIG = {
5996
+ xxs: { container: "size-4", fontSize: "text-[11px]", dotSize: "size-[5px]", dotRing: "ring-[1.5px]" },
5997
+ xs: { container: "size-5", fontSize: "text-sm", dotSize: "size-[5px]", dotRing: "ring-[1.5px]" },
5998
+ s: { container: "size-6", fontSize: "text-base", dotSize: "size-[6px]", dotRing: "ring-[1.5px]" },
5999
+ m: { container: "size-8", fontSize: "text-xl", dotSize: "size-[7px]", dotRing: "ring-2" },
6000
+ l: { container: "size-10", fontSize: "text-[26px]", dotSize: "size-[9px]", dotRing: "ring-2" },
6001
+ xl: { container: "size-12", fontSize: "text-[32px]", dotSize: "size-[11px]", dotRing: "ring-2" },
6002
+ xxl: { container: "size-13", fontSize: "text-[37px]", dotSize: "size-[13px]", dotRing: "ring-2" }
6003
+ };
6004
+ function Avatar({
6005
+ type = "photo",
6006
+ size = "m",
6007
+ status = false,
6008
+ src,
6009
+ alt = "",
6010
+ initials,
6011
+ className
6012
+ }) {
6013
+ const { container, fontSize, dotSize, dotRing } = SIZE_CONFIG[size];
6014
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("relative inline-flex shrink-0", container, className), children: [
6015
+ type === "photo" && /* @__PURE__ */ jsxRuntime.jsx(
6016
+ "img",
6017
+ {
6018
+ src,
6019
+ alt,
6020
+ className: "size-full rounded-full object-cover bg-bg-default-tertiary"
6021
+ }
6022
+ ),
6023
+ type === "placeholder" && /* @__PURE__ */ jsxRuntime.jsx(
6024
+ "img",
6025
+ {
6026
+ src: avatarPlaceholder,
6027
+ alt: "",
6028
+ className: "size-full rounded-full object-cover",
6029
+ "aria-hidden": "true"
6030
+ }
6031
+ ),
6032
+ type === "text" && /* @__PURE__ */ jsxRuntime.jsx(
6033
+ "div",
6034
+ {
6035
+ className: "relative size-full rounded-full overflow-hidden",
6036
+ style: { background: TEXT_GRADIENT },
6037
+ children: /* @__PURE__ */ jsxRuntime.jsx(
6038
+ "span",
6039
+ {
6040
+ className: cn(
6041
+ "absolute left-1/2 top-1/2 font-bold leading-none select-none whitespace-nowrap",
6042
+ fontSize
6043
+ ),
6044
+ style: {
6045
+ color: TEXT_COLOR,
6046
+ transform: "translate(-50%, calc(-50% + 0.07em))"
6047
+ },
6048
+ children: (initials ?? "").slice(0, 2).toUpperCase()
6049
+ }
6050
+ )
6051
+ }
6052
+ ),
6053
+ status && type === "photo" && /* @__PURE__ */ jsxRuntime.jsx(
6054
+ "span",
6055
+ {
6056
+ "aria-label": "Online",
6057
+ className: cn(
6058
+ "absolute bottom-0 right-0 block rounded-full bg-bg-success",
6059
+ dotSize,
6060
+ dotRing,
6061
+ "ring-white"
6062
+ )
6063
+ }
6064
+ )
6065
+ ] });
6066
+ }
6067
+ const STACK_CONFIG = {
6068
+ small: { avatarSize: "xxs", avatarPx: 16, overlapPx: 4 },
6069
+ medium: { avatarSize: "xs", avatarPx: 20, overlapPx: 5 },
6070
+ large: { avatarSize: "s", avatarPx: 24, overlapPx: 5 }
6071
+ };
6072
+ function subtractMask(avatarPx, overlapPx) {
6073
+ const r = avatarPx / 2;
6074
+ const spacingPx = avatarPx - overlapPx;
6075
+ const biteX = spacingPx + r;
6076
+ const biteY = r;
6077
+ const biteR = r + 1;
6078
+ const self = `radial-gradient(circle at ${r}px ${r}px, white ${r}px, transparent ${r}px)`;
6079
+ const bite = `radial-gradient(circle at ${biteX}px ${biteY}px, white ${biteR}px, transparent ${biteR}px)`;
6080
+ return {
6081
+ maskImage: `${self}, ${bite}`,
6082
+ maskComposite: "subtract",
6083
+ WebkitMaskImage: `${bite}, ${self}`,
6084
+ WebkitMaskComposite: "destination-out"
6085
+ };
6086
+ }
6087
+ function AvatarStack({
6088
+ items,
6089
+ size = "small",
6090
+ max = 5,
6091
+ className
6092
+ }) {
6093
+ const { avatarSize, avatarPx, overlapPx } = STACK_CONFIG[size];
6094
+ const visible = items.slice(0, max);
6095
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("flex shrink-0", className), children: visible.map((item, i) => {
6096
+ const isLast = i === visible.length - 1;
6097
+ return /* @__PURE__ */ jsxRuntime.jsx(
6098
+ "div",
6099
+ {
6100
+ className: "relative flex shrink-0",
6101
+ style: {
6102
+ width: avatarPx,
6103
+ height: avatarPx,
6104
+ marginLeft: i > 0 ? -overlapPx : 0,
6105
+ // Leftmost = highest z-index: its transparent bite reveals the avatar below.
6106
+ zIndex: visible.length - i,
6107
+ ...isLast ? {} : subtractMask(avatarPx, overlapPx)
6108
+ },
6109
+ children: /* @__PURE__ */ jsxRuntime.jsx(
6110
+ Avatar,
6111
+ {
6112
+ type: item.type ?? "photo",
6113
+ size: avatarSize,
6114
+ src: item.src,
6115
+ alt: item.alt,
6116
+ initials: item.initials
6117
+ }
6118
+ )
6119
+ },
6120
+ i
6121
+ );
6122
+ }) });
6123
+ }
5960
6124
  const index = {
5961
6125
  Button,
5962
6126
  Badge,
@@ -5990,10 +6154,14 @@ const index = {
5990
6154
  TableCell,
5991
6155
  TextArea,
5992
6156
  TimeInput,
6157
+ Avatar,
6158
+ AvatarStack,
5993
6159
  cn,
5994
6160
  useIsMobile
5995
6161
  };
5996
6162
  exports.Alert = Alert;
6163
+ exports.Avatar = Avatar;
6164
+ exports.AvatarStack = AvatarStack;
5997
6165
  exports.Badge = Badge;
5998
6166
  exports.BottomSheet = BottomSheet;
5999
6167
  exports.Button = Button;