alouette 19.1.0 → 19.3.0

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.
Files changed (38) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/definitions/index.d.ts +4 -0
  3. package/dist/definitions/index.d.ts.map +1 -1
  4. package/dist/definitions/ui/containers/Box.d.ts +1 -0
  5. package/dist/definitions/ui/containers/Box.d.ts.map +1 -1
  6. package/dist/definitions/ui/feedback/Badge.d.ts +119 -0
  7. package/dist/definitions/ui/feedback/Badge.d.ts.map +1 -0
  8. package/dist/definitions/ui/feedback/Badge.stories.d.ts +50 -0
  9. package/dist/definitions/ui/feedback/Badge.stories.d.ts.map +1 -0
  10. package/dist/definitions/ui/inputs/Select.d.ts +4 -0
  11. package/dist/definitions/ui/inputs/Select.d.ts.map +1 -0
  12. package/dist/definitions/ui/inputs/Select.shared.d.ts +30 -0
  13. package/dist/definitions/ui/inputs/Select.shared.d.ts.map +1 -0
  14. package/dist/definitions/ui/inputs/Select.stories.d.ts +23 -0
  15. package/dist/definitions/ui/inputs/Select.stories.d.ts.map +1 -0
  16. package/dist/definitions/ui/inputs/Select.web.d.ts +4 -0
  17. package/dist/definitions/ui/inputs/Select.web.d.ts.map +1 -0
  18. package/dist/definitions/ui/inputs/Switch.web.d.ts.map +1 -1
  19. package/dist/index-browser.es.js +279 -15
  20. package/dist/index-browser.es.js.map +1 -1
  21. package/dist/index-node22.cjs +290 -0
  22. package/dist/index-node22.cjs.map +1 -1
  23. package/dist/index-node22.mjs +291 -3
  24. package/dist/index-node22.mjs.map +1 -1
  25. package/dist/index-react-native.cjs.js +290 -0
  26. package/dist/index-react-native.cjs.js.map +1 -1
  27. package/dist/index-react-native.es.js +291 -3
  28. package/dist/index-react-native.es.js.map +1 -1
  29. package/package.json +1 -1
  30. package/src/index.ts +4 -0
  31. package/src/ui/containers/Box.tsx +24 -1
  32. package/src/ui/feedback/Badge.stories.tsx +83 -0
  33. package/src/ui/feedback/Badge.tsx +75 -0
  34. package/src/ui/inputs/Select.shared.tsx +104 -0
  35. package/src/ui/inputs/Select.stories.tsx +135 -0
  36. package/src/ui/inputs/Select.tsx +191 -0
  37. package/src/ui/inputs/Select.web.tsx +159 -0
  38. package/src/ui/inputs/Switch.web.tsx +23 -16
@@ -1,9 +1,10 @@
1
1
  import { jsx, jsxs, Fragment as Fragment$1 } from 'react/jsx-runtime';
2
2
  import { VariableContextProvider, styled as styled$1 } from 'nativewind';
3
- import { createContext, useContext, forwardRef, Fragment, Children, cloneElement, useRef, useState, useEffect, isValidElement, useCallback } from 'react';
3
+ import { createContext, useContext, forwardRef, Children, cloneElement, Fragment, useRef, useState, useEffect, isValidElement, useCallback } from 'react';
4
4
  import { useColorScheme, View as View$1, Text as Text$1, ScrollView as ScrollView$1, FlatList as FlatList$1, SectionList as SectionList$1, Pressable, Platform, TextInput, useWindowDimensions } from 'react-native-web';
5
5
  import { extendTailwindMerge, twMerge as twMerge$1 } from 'tailwind-merge';
6
6
  import { tv } from 'tailwind-variants';
7
+ import { CaretDownRegularIcon } from 'alouette-icons/phosphor-icons/CaretDownRegularIcon';
7
8
  import { CheckRegularIcon } from 'alouette-icons/phosphor-icons/CheckRegularIcon';
8
9
  import { InfoRegularIcon } from 'alouette-icons/phosphor-icons/InfoRegularIcon';
9
10
  import { WarningRegularIcon } from 'alouette-icons/phosphor-icons/WarningRegularIcon';
@@ -795,6 +796,26 @@ const InteractiveBox = forwardRef(
795
796
  }
796
797
  )
797
798
  );
799
+ const InteractiveBoxHitSlop = forwardRef(
800
+ ({ withFocusVisibleOutline, children, className, ...rest }, ref) => {
801
+ const child = Children.only(children);
802
+ return /* @__PURE__ */ jsx(
803
+ Pressable,
804
+ {
805
+ ref,
806
+ pointerEvents: "auto",
807
+ className: `flex-center ${className ?? ""}`,
808
+ ...rest,
809
+ children: cloneElement(child, {
810
+ className: interactiveBoxVariants({
811
+ withFocusVisibleOutline,
812
+ className: child.props.className
813
+ })
814
+ })
815
+ }
816
+ );
817
+ }
818
+ );
798
819
  const SafeAreaBox = forwardRef(
799
820
  (props, ref) => {
800
821
  const insets = useSafeAreaInsets();
@@ -1665,8 +1686,9 @@ const TextArea = forwardRef((props, ref) => {
1665
1686
  return /* @__PURE__ */ jsx(InputText, { ref, multiline: true, ...props });
1666
1687
  });
1667
1688
 
1668
- const TRACK_HEIGHT = 44;
1669
- const TRACK_WIDTH = 66;
1689
+ const PRESSABLE_HEIGHT = 44;
1690
+ const TRACK_HEIGHT = 36;
1691
+ const TRACK_WIDTH = 58;
1670
1692
  const THUMB_PADDING = TRACK_HEIGHT * 0.1;
1671
1693
  const THUMB_SIZE = TRACK_HEIGHT * 0.8;
1672
1694
  const TRAVEL_X = TRACK_WIDTH - THUMB_SIZE - THUMB_PADDING * 2;
@@ -1674,6 +1696,8 @@ const trackVariants = tv(
1674
1696
  {
1675
1697
  // TODO if we can fix web to use proper button, change aria-disabled to disabled
1676
1698
  base: [
1699
+ "height-[36px] w-[58px]",
1700
+ // Must be identical to TRACK_HEIGHT and TRACK_WIDTH constants above
1677
1701
  "relative rounded-full overflow-hidden shadow-lowered pointer-events-auto",
1678
1702
  "transition-background-color duration-200 ease-in",
1679
1703
  "outline-interactive-outlined-outline-focus",
@@ -1725,15 +1749,14 @@ function SwitchInner({
1725
1749
  }) {
1726
1750
  const [value, setValue] = useControllableChecked(checked, onValueChange);
1727
1751
  return /* @__PURE__ */ jsx(
1728
- InteractiveBox,
1752
+ InteractiveBoxHitSlop,
1729
1753
  {
1730
1754
  withFocusVisibleOutline: true,
1731
1755
  role: "switch",
1732
1756
  "aria-checked": value,
1733
1757
  "aria-disabled": disabled === true,
1734
1758
  disabled,
1735
- className: trackVariants({ checked: value, forceStyle }),
1736
- style: { width: TRACK_WIDTH, height: TRACK_HEIGHT },
1759
+ style: { height: PRESSABLE_HEIGHT, width: TRACK_WIDTH },
1737
1760
  onPress: () => {
1738
1761
  setValue(!value);
1739
1762
  },
@@ -1742,14 +1765,22 @@ function SwitchInner({
1742
1765
  View$1,
1743
1766
  {
1744
1767
  "aria-disabled": disabled === true,
1745
- className: thumbVariants({}),
1746
- style: {
1747
- width: THUMB_SIZE,
1748
- height: THUMB_SIZE,
1749
- top: THUMB_PADDING,
1750
- left: THUMB_PADDING,
1751
- transform: [{ translateX: value ? TRAVEL_X : 0 }]
1752
- }
1768
+ className: trackVariants({ checked: value, forceStyle }),
1769
+ style: { width: TRACK_WIDTH, height: TRACK_HEIGHT },
1770
+ children: /* @__PURE__ */ jsx(
1771
+ View$1,
1772
+ {
1773
+ "aria-disabled": disabled === true,
1774
+ className: thumbVariants({}),
1775
+ style: {
1776
+ width: THUMB_SIZE,
1777
+ height: THUMB_SIZE,
1778
+ top: THUMB_PADDING,
1779
+ left: THUMB_PADDING,
1780
+ transform: [{ translateX: value ? TRAVEL_X : 0 }]
1781
+ }
1782
+ }
1783
+ )
1753
1784
  }
1754
1785
  )
1755
1786
  }
@@ -1759,6 +1790,239 @@ function Switch({ accent, ...rest }) {
1759
1790
  return /* @__PURE__ */ jsx(AccentScope, { accent, children: /* @__PURE__ */ jsx(SwitchInner, { ...rest }) });
1760
1791
  }
1761
1792
 
1793
+ function useControllableValue(controlled, defaultValue, onValueChange) {
1794
+ const [internal, setInternal] = useState(defaultValue);
1795
+ const value = controlled ?? internal;
1796
+ const setValue = useCallback(
1797
+ (next) => {
1798
+ if (controlled === void 0) {
1799
+ setInternal(next);
1800
+ }
1801
+ if (next !== value) {
1802
+ onValueChange?.(next);
1803
+ }
1804
+ },
1805
+ [controlled, onValueChange, value]
1806
+ );
1807
+ return [value, setValue];
1808
+ }
1809
+ const triggerLabelVariants = tv({
1810
+ base: "flex-1 text-base",
1811
+ variants: {
1812
+ // Mirrors InputText: sharp value, form-placeholder, form-disabled-text.
1813
+ state: {
1814
+ value: "text-sharp",
1815
+ placeholder: "text-form-placeholder",
1816
+ disabled: "text-form-disabled-text"
1817
+ }
1818
+ },
1819
+ defaultVariants: { state: "value" }
1820
+ });
1821
+ function SelectTriggerContent({
1822
+ label,
1823
+ placeholder,
1824
+ disabled
1825
+ }) {
1826
+ const state = (() => {
1827
+ if (label === void 0) return "placeholder";
1828
+ if (disabled) return "disabled";
1829
+ return "value";
1830
+ })();
1831
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [
1832
+ /* @__PURE__ */ jsx(Text, { numberOfLines: 1, className: triggerLabelVariants({ state }), children: label ?? placeholder ?? "" }),
1833
+ /* @__PURE__ */ jsx(
1834
+ Icon,
1835
+ {
1836
+ icon: /* @__PURE__ */ jsx(CaretDownRegularIcon, {}),
1837
+ size: 18,
1838
+ className: disabled ? "text-form-disabled-text" : "text-muted"
1839
+ }
1840
+ )
1841
+ ] });
1842
+ }
1843
+
1844
+ const wrapperVariants = tv(
1845
+ {
1846
+ base: [
1847
+ "flex-row flex-1 rounded-md border min-h-11",
1848
+ "transition-[border-color,outline-color] duration-200 ease-in",
1849
+ "outline-interactive-outlined-pressable"
1850
+ // for a proper outline color transition
1851
+ ].join(" "),
1852
+ variants: {
1853
+ // bg lives in each branch (not base) so the disabled bg never competes
1854
+ // with bg-highlight: two same-specificity background utilities would let
1855
+ // stylesheet order, not className order, decide the winner.
1856
+ disabled: {
1857
+ true: "bg-disabled-interactive-muted border-interactive-outlined-disabled cursor-not-allowed",
1858
+ false: [
1859
+ "bg-highlight",
1860
+ "border-interactive-outlined-pressable",
1861
+ "hover:border-interactive-outlined-hover",
1862
+ "focus-within:border-interactive-outlined-focus",
1863
+ "focus-within:outline-1 focus-within:outline-interactive-outlined-focus focus-within:outline-offset-0",
1864
+ "active:border-interactive-outlined-active"
1865
+ ].join(" ")
1866
+ }
1867
+ },
1868
+ defaultVariants: { disabled: false }
1869
+ },
1870
+ { twMerge: false }
1871
+ );
1872
+ const paddingX = 16;
1873
+ const caretReserve = 26;
1874
+ const selectStyle = (disabled) => ({
1875
+ appearance: "none",
1876
+ WebkitAppearance: "none",
1877
+ boxSizing: "border-box",
1878
+ minHeight: 44,
1879
+ width: "auto",
1880
+ margin: 0,
1881
+ border: 0,
1882
+ background: "transparent",
1883
+ color: "transparent",
1884
+ font: "inherit",
1885
+ paddingTop: 0,
1886
+ paddingBottom: 0,
1887
+ paddingLeft: paddingX,
1888
+ paddingRight: paddingX + caretReserve,
1889
+ outline: "none",
1890
+ cursor: disabled ? "not-allowed" : "pointer"
1891
+ });
1892
+ function Select({
1893
+ options,
1894
+ value,
1895
+ defaultValue,
1896
+ onValueChange,
1897
+ placeholder,
1898
+ disabled,
1899
+ accent,
1900
+ testID,
1901
+ "aria-label": ariaLabel,
1902
+ "aria-labelledby": ariaLabelledby
1903
+ }) {
1904
+ const [current, setValue] = useControllableValue(
1905
+ value,
1906
+ defaultValue,
1907
+ onValueChange
1908
+ );
1909
+ const selected = options.find((option) => option.value === current);
1910
+ const [sharpColor, disabledTextColor, placeholderColor, highlightColor] = useThemeToken([
1911
+ "--color-sharp",
1912
+ "--color-form-disabled-text",
1913
+ "--color-form-placeholder",
1914
+ "--color-highlight"
1915
+ ]);
1916
+ const optionStyle = (optionDisabled) => ({
1917
+ color: optionDisabled ? disabledTextColor : sharpColor,
1918
+ backgroundColor: highlightColor
1919
+ });
1920
+ return /* @__PURE__ */ jsx(AccentScope, { accent, children: /* @__PURE__ */ jsxs(View$1, { className: wrapperVariants({ disabled }), children: [
1921
+ /* @__PURE__ */ jsxs(
1922
+ "select",
1923
+ {
1924
+ value: current ?? "",
1925
+ disabled,
1926
+ "aria-label": ariaLabel,
1927
+ "aria-labelledby": ariaLabelledby,
1928
+ "data-testid": testID,
1929
+ style: selectStyle(disabled),
1930
+ onChange: (event) => {
1931
+ setValue(event.target.value);
1932
+ },
1933
+ children: [
1934
+ placeholder === void 0 ? null : /* @__PURE__ */ jsx(
1935
+ "option",
1936
+ {
1937
+ disabled: true,
1938
+ value: "",
1939
+ style: {
1940
+ color: placeholderColor,
1941
+ backgroundColor: highlightColor
1942
+ },
1943
+ children: placeholder
1944
+ }
1945
+ ),
1946
+ options.map((option) => /* @__PURE__ */ jsx(
1947
+ "option",
1948
+ {
1949
+ value: option.value,
1950
+ disabled: option.disabled,
1951
+ style: optionStyle(option.disabled),
1952
+ children: option.label
1953
+ },
1954
+ option.value
1955
+ ))
1956
+ ]
1957
+ }
1958
+ ),
1959
+ /* @__PURE__ */ jsx(
1960
+ View$1,
1961
+ {
1962
+ pointerEvents: "none",
1963
+ className: "absolute inset-0 flex-row items-center justify-between gap-xs px-m",
1964
+ children: /* @__PURE__ */ jsx(
1965
+ SelectTriggerContent,
1966
+ {
1967
+ label: selected?.label,
1968
+ placeholder,
1969
+ disabled
1970
+ }
1971
+ )
1972
+ }
1973
+ )
1974
+ ] }) });
1975
+ }
1976
+
1977
+ const badgeVariants = tv(
1978
+ {
1979
+ slots: {
1980
+ frame: "flex-row items-center self-start rounded-full",
1981
+ text: "font-body-bold",
1982
+ icon: ""
1983
+ },
1984
+ variants: {
1985
+ size: {
1986
+ sm: { frame: "gap-xxs px-xs py-xxs", text: "text-xs", icon: "" },
1987
+ md: { frame: "gap-xs px-sm py-xxs", text: "text-sm", icon: "" }
1988
+ },
1989
+ variant: {
1990
+ solid: {
1991
+ frame: "bg-highlight-accent",
1992
+ text: "text-sharp",
1993
+ icon: "text-sharp"
1994
+ },
1995
+ "solid.enabled": {
1996
+ frame: "bg-enabled",
1997
+ text: "text-sharp",
1998
+ icon: "text-sharp"
1999
+ },
2000
+ outlined: {
2001
+ frame: "border border-accent",
2002
+ text: "text-accent",
2003
+ icon: "text-accent"
2004
+ }
2005
+ }
2006
+ },
2007
+ defaultVariants: { size: "md", variant: "solid" }
2008
+ },
2009
+ { twMerge: false }
2010
+ );
2011
+ const ICON_SIZE$1 = { sm: 12, md: 16 };
2012
+ function Badge({
2013
+ accent = "brand",
2014
+ size = "md",
2015
+ variant = "solid",
2016
+ icon,
2017
+ children
2018
+ }) {
2019
+ const styles = badgeVariants({ size, variant });
2020
+ return /* @__PURE__ */ jsx(AccentScope, { accent, children: /* @__PURE__ */ jsxs(Box, { className: styles.frame(), children: [
2021
+ icon ? /* @__PURE__ */ jsx(Icon, { icon, size: ICON_SIZE$1[size], className: styles.icon() }) : null,
2022
+ /* @__PURE__ */ jsx(Text, { className: styles.text(), children })
2023
+ ] }) });
2024
+ }
2025
+
1762
2026
  const messageFrameVariants = tv(
1763
2027
  {
1764
2028
  base: "flex-row items-center bg-highlight-accent overflow-hidden",
@@ -2005,5 +2269,5 @@ function ExternalLink({
2005
2269
  );
2006
2270
  }
2007
2271
 
2008
- export { AccentScope, AlouetteDecorator, AlouetteProvider, Box, BreakpointNameEnum, Breakpoints, Button, ConfirmationMessage, ExternalLink, ExternalLinkButton, FlatList, GradientBackground, GradientScrollView, HStack, Icon, IconButton, InfoMessage, InputText, InteractiveBox, InternalLinkButton, Message, Paragraph, PresenceList, PresenceOne, PressableBox, PressableListItem, SafeAreaBox, SafeAreaProvider, ScopedTheme, ScrollView, SectionList, Separator, Stack, Story, StoryContainer, StoryDecorator, StoryGrid, StoryTitle, Surface, Switch, SwitchBreakpointsUsingDisplayNone, SwitchBreakpointsUsingNull, Text, TextArea, VStack, View, WarningMessage, animationDurationsMs, styled, themeVariables, useCurrentBreakpointName, useCurrentBreakpointNameFiltered, useCurrentMode, useCurrentTheme, useSafeAreaInsets, useThemeToken };
2272
+ export { AccentScope, AlouetteDecorator, AlouetteProvider, Badge, Box, BreakpointNameEnum, Breakpoints, Button, ConfirmationMessage, ExternalLink, ExternalLinkButton, FlatList, GradientBackground, GradientScrollView, HStack, Icon, IconButton, InfoMessage, InputText, InteractiveBox, InternalLinkButton, Message, Paragraph, PresenceList, PresenceOne, PressableBox, PressableListItem, SafeAreaBox, SafeAreaProvider, ScopedTheme, ScrollView, SectionList, Select, Separator, Stack, Story, StoryContainer, StoryDecorator, StoryGrid, StoryTitle, Surface, Switch, SwitchBreakpointsUsingDisplayNone, SwitchBreakpointsUsingNull, Text, TextArea, VStack, View, WarningMessage, animationDurationsMs, styled, themeVariables, useCurrentBreakpointName, useCurrentBreakpointNameFiltered, useCurrentMode, useCurrentTheme, useSafeAreaInsets, useThemeToken };
2009
2273
  //# sourceMappingURL=index-browser.es.js.map